Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f513297ba0 | |||
| 135f8c0459 | |||
| 7a811f4152 | |||
| 2abf681d17 | |||
| 689c97f452 | |||
| 08d11a53b4 |
@@ -17,12 +17,12 @@ android {
|
||||
|
||||
val versionMajor = 2
|
||||
val versionMinor = 3
|
||||
val versionPatch = 0
|
||||
val versionPatch = 5
|
||||
val versionBuild = 0
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.zaneschepke.wireguardautotunnel"
|
||||
minSdk = 29
|
||||
minSdk = 28
|
||||
targetSdk = 34
|
||||
versionCode = versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild
|
||||
versionName = "${versionMajor}.${versionMinor}.${versionPatch}"
|
||||
@@ -83,13 +83,13 @@ dependencies {
|
||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||
|
||||
//wireguard tunnel
|
||||
implementation("com.wireguard.android:tunnel:1.0.20230427")
|
||||
implementation("com.wireguard.android:tunnel:1.0.20230706")
|
||||
|
||||
//logging
|
||||
implementation("com.jakewharton.timber:timber:5.0.1")
|
||||
|
||||
// compose navigation
|
||||
implementation("androidx.navigation:navigation-compose:2.6.0")
|
||||
implementation("androidx.navigation:navigation-compose:2.7.0")
|
||||
implementation("androidx.hilt:hilt-navigation-compose:1.0.0")
|
||||
|
||||
// hilt
|
||||
@@ -110,7 +110,7 @@ dependencies {
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.1")
|
||||
|
||||
//icons
|
||||
implementation("androidx.compose.material:material-icons-extended:1.4.3")
|
||||
implementation("androidx.compose.material:material-icons-extended:1.5.0")
|
||||
|
||||
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
|
||||
@@ -122,7 +122,7 @@ dependencies {
|
||||
implementation("com.google.firebase:firebase-analytics-ktx")
|
||||
|
||||
//barcode scanning
|
||||
implementation("com.google.android.gms:play-services-code-scanner:16.0.0")
|
||||
implementation("com.google.android.gms:play-services-code-scanner:16.1.0")
|
||||
}
|
||||
|
||||
kapt {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.zaneschepke.wireguardautotunnel
|
||||
|
||||
object Constants {
|
||||
const val VPN_CONNECTIVITY_CHECK_INTERVAL = 3000L;
|
||||
const val VPN_STATISTIC_CHECK_INTERVAL = 10000L;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.zaneschepke.wireguardautotunnel
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.zaneschepke.wireguardautotunnel.repository.Repository
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
|
||||
@@ -22,4 +24,10 @@ class WireGuardAutoTunnel : Application() {
|
||||
}
|
||||
settingsRepo.init()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun isRunningOnAndroidTv(context : Context) : Boolean {
|
||||
return context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.zaneschepke.wireguardautotunnel.module
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.service.barcode.CodeScanner
|
||||
import com.zaneschepke.wireguardautotunnel.service.barcode.QRScanner
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
|
||||
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
|
||||
@@ -12,7 +10,6 @@ import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ServiceComponent
|
||||
import dagger.hilt.android.scopes.ServiceScoped
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
|
||||
@Module
|
||||
@InstallIn(ServiceComponent::class)
|
||||
|
||||
@@ -9,10 +9,9 @@ import com.zaneschepke.wireguardautotunnel.service.foreground.Action
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceTracker
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardConnectivityWatcherService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
@@ -25,7 +24,7 @@ class BootReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
|
||||
CoroutineScope(SupervisorJob()).launch {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val settings = settingsRepo.getAll()
|
||||
if (!settings.isNullOrEmpty()) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelSer
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -23,7 +23,7 @@ class NotificationActionReceiver : BroadcastReceiver() {
|
||||
@Inject
|
||||
lateinit var settingsRepo : Repository<Settings>
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
CoroutineScope(SupervisorJob()).launch {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val settings = settingsRepo.getAll()
|
||||
if (!settings.isNullOrEmpty()) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.os.Bundle
|
||||
import android.os.PowerManager
|
||||
import android.os.SystemClock
|
||||
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
|
||||
@@ -20,8 +21,9 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
@@ -46,15 +48,14 @@ 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;
|
||||
|
||||
@@ -123,19 +124,22 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
||||
}
|
||||
|
||||
private fun startWatcherJob() {
|
||||
watcherJob = CoroutineScope(SupervisorJob()).launch {
|
||||
watcherJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
val settings = settingsRepo.getAll();
|
||||
if(!settings.isNullOrEmpty()) {
|
||||
setting = settings[0]
|
||||
}
|
||||
CoroutineScope(watcherJob).launch {
|
||||
launch {
|
||||
watchForWifiConnectivityChanges()
|
||||
}
|
||||
if(setting.isTunnelOnMobileDataEnabled) {
|
||||
CoroutineScope(watcherJob).launch {
|
||||
launch {
|
||||
watchForMobileDataConnectivityChanges()
|
||||
}
|
||||
}
|
||||
launch {
|
||||
manageVpn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,17 +153,9 @@ 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,61 +172,52 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
||||
is NetworkStatus.CapabilitiesChanged -> {
|
||||
Timber.d("Wifi capabilities changed")
|
||||
isWifiConnected = true
|
||||
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()
|
||||
}
|
||||
}
|
||||
currentNetworkSSID = wifiService.getNetworkName(it.networkCapabilities) ?: "";
|
||||
}
|
||||
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 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 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() {
|
||||
ServiceTracker.actionOnService(
|
||||
Action.START,
|
||||
this.applicationContext as Application,
|
||||
WireGuardTunnelService::class.java,
|
||||
mapOf(getString(R.string.tunnel_extras_key) to tunnelId))
|
||||
}
|
||||
private fun stopVPN() {
|
||||
if(!disconnecting) {
|
||||
disconnecting = true
|
||||
ServiceTracker.actionOnService(
|
||||
Action.STOP,
|
||||
this.applicationContext as Application,
|
||||
WireGuardTunnelService::class.java
|
||||
)
|
||||
disconnecting = false
|
||||
}
|
||||
ServiceTracker.actionOnService(
|
||||
Action.STOP,
|
||||
this.applicationContext as Application,
|
||||
WireGuardTunnelService::class.java
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
@@ -42,7 +41,7 @@ class WireGuardTunnelService : ForegroundService() {
|
||||
super.startService(extras)
|
||||
val tunnelConfigString = extras?.getString(getString(R.string.tunnel_extras_key))
|
||||
cancelJob()
|
||||
job = CoroutineScope(SupervisorJob()).launch {
|
||||
job = CoroutineScope(Dispatchers.IO).launch {
|
||||
if(tunnelConfigString != null) {
|
||||
try {
|
||||
val tunnelConfig = TunnelConfig.from(tunnelConfigString)
|
||||
|
||||
@@ -45,6 +45,7 @@ abstract class BaseNetworkService<T : BaseNetworkService<T>>(val context: Contex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
object : ConnectivityManager.NetworkCallback() {
|
||||
|
||||
@@ -77,8 +78,8 @@ abstract class BaseNetworkService<T : BaseNetworkService<T>>(val context: Contex
|
||||
|
||||
|
||||
override fun getNetworkName(networkCapabilities: NetworkCapabilities): String? {
|
||||
var ssid : String? = getWifiNameFromCapabilities(networkCapabilities)
|
||||
if((Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R)) {
|
||||
var ssid: String? = getWifiNameFromCapabilities(networkCapabilities)
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
|
||||
val info = wifiManager.connectionInfo
|
||||
if (info.supplicantState === SupplicantState.COMPLETED) {
|
||||
ssid = info.ssid
|
||||
@@ -89,14 +90,15 @@ abstract class BaseNetworkService<T : BaseNetworkService<T>>(val context: Contex
|
||||
|
||||
|
||||
companion object {
|
||||
private fun getWifiNameFromCapabilities(networkCapabilities: NetworkCapabilities) : String? {
|
||||
val info : WifiInfo
|
||||
if(networkCapabilities.transportInfo is WifiInfo) {
|
||||
info = networkCapabilities.transportInfo as WifiInfo
|
||||
} else {
|
||||
return null
|
||||
private fun getWifiNameFromCapabilities(networkCapabilities: NetworkCapabilities): String? {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val info: WifiInfo
|
||||
if (networkCapabilities.transportInfo is WifiInfo) {
|
||||
info = networkCapabilities.transportInfo as WifiInfo
|
||||
return info.ssid
|
||||
}
|
||||
}
|
||||
return info.ssid
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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
|
||||
@@ -103,7 +104,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++
|
||||
neverHadHandshakeCounter += 10
|
||||
}
|
||||
return@forEach
|
||||
}
|
||||
@@ -114,7 +115,7 @@ class WireGuardTunnel @Inject constructor(private val backend : Backend,
|
||||
}
|
||||
}
|
||||
_lastHandshake.emit(handshakeMap)
|
||||
delay(1000)
|
||||
delay(Constants.VPN_STATISTIC_CHECK_INTERVAL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ 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
|
||||
@@ -22,6 +23,9 @@ 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
|
||||
@@ -40,6 +44,8 @@ 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() {
|
||||
@@ -51,6 +57,8 @@ class MainActivity : AppCompatActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
val navController = rememberAnimatedNavController()
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
WireguardAutoTunnelTheme {
|
||||
TransparentSystemBars()
|
||||
|
||||
@@ -80,7 +88,25 @@ class MainActivity : AppCompatActivity() {
|
||||
} else requestNotificationPermission()
|
||||
}
|
||||
|
||||
Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
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
|
||||
}
|
||||
},
|
||||
bottomBar = if (vpnIntent == null && notificationPermissionState.status.isGranted) {
|
||||
{ BottomNavBar(navController, Routes.navItems) }
|
||||
} else {
|
||||
@@ -126,7 +152,7 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
}) {
|
||||
MainScreen(padding = padding, snackbarHostState = snackbarHostState, navController = navController)
|
||||
MainScreen(padding = padding, snackbarHostState = snackbarHostState, navController = navController, focusRequester = focusRequester)
|
||||
}
|
||||
composable(Routes.Settings.name, enterTransition = {
|
||||
when (initialState.destination.route) {
|
||||
@@ -147,7 +173,7 @@ class MainActivity : AppCompatActivity() {
|
||||
fadeIn(animationSpec = tween(1000))
|
||||
}
|
||||
}
|
||||
}) { SettingsScreen(padding = padding, snackbarHostState = snackbarHostState, navController = navController) }
|
||||
}) { SettingsScreen(padding = padding, snackbarHostState = snackbarHostState, navController = navController, focusRequester = focusRequester) }
|
||||
composable(Routes.Support.name, enterTransition = {
|
||||
when (initialState.destination.route) {
|
||||
Routes.Settings.name, Routes.Main.name ->
|
||||
@@ -160,10 +186,10 @@ class MainActivity : AppCompatActivity() {
|
||||
fadeIn(animationSpec = tween(1000))
|
||||
}
|
||||
}
|
||||
}) { SupportScreen(padding = padding) }
|
||||
}) { SupportScreen(padding = padding, focusRequester) }
|
||||
composable("${Routes.Config.name}/{id}", enterTransition = {
|
||||
fadeIn(animationSpec = tween(1000))
|
||||
}) { ConfigScreen(padding = padding, navController = navController, id = it.arguments?.getString("id"))}
|
||||
}) { ConfigScreen(padding = padding, navController = navController, id = it.arguments?.getString("id"), focusRequester = focusRequester)}
|
||||
composable("${Routes.Detail.name}/{id}", enterTransition = {
|
||||
fadeIn(animationSpec = tween(1000))
|
||||
}) { DetailScreen(padding = padding, id = it.arguments?.getString("id")) }
|
||||
|
||||
@@ -52,13 +52,13 @@ 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 focusRequester = remember { FocusRequester() }
|
||||
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -228,10 +228,5 @@ fun ConfigScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
if(context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
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
|
||||
@@ -52,7 +51,6 @@ 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
|
||||
@@ -73,6 +71,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.ui.Routes
|
||||
@@ -82,17 +81,17 @@ import com.zaneschepke.wireguardautotunnel.ui.theme.mint
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
viewModel: MainViewModel = hiltViewModel(), padding: PaddingValues,
|
||||
focusRequester: FocusRequester,
|
||||
snackbarHostState: SnackbarHostState, navController: NavController
|
||||
) {
|
||||
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val context = LocalContext.current
|
||||
val isVisible = rememberSaveable { mutableStateOf(true) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val sheetState = rememberModalBottomSheetState()
|
||||
@@ -246,12 +245,12 @@ fun MainScreen(
|
||||
.nestedScroll(nestedScrollConnection),) {
|
||||
items(tunnels.toList()) { tunnel ->
|
||||
RowListItem(leadingIcon = Icons.Rounded.Circle,
|
||||
leadingIconColor = when (handshakeStatus) {
|
||||
leadingIconColor = if (tunnelName == tunnel.name) when (handshakeStatus) {
|
||||
HandshakeStatus.HEALTHY -> mint
|
||||
HandshakeStatus.UNHEALTHY -> brickRed
|
||||
HandshakeStatus.NOT_STARTED -> Color.Gray
|
||||
HandshakeStatus.NEVER_CONNECTED -> brickRed
|
||||
},
|
||||
} else Color.Gray,
|
||||
text = tunnel.name,
|
||||
onHold = {
|
||||
if (state == Tunnel.State.UP && tunnel.name == tunnelName) {
|
||||
@@ -264,7 +263,7 @@ fun MainScreen(
|
||||
selectedTunnel = tunnel;
|
||||
},
|
||||
onClick = {
|
||||
if(!context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)){
|
||||
if(!WireGuardAutoTunnel.isRunningOnAndroidTv(context)){
|
||||
navController.navigate("${Routes.Detail.name}/${tunnel.id}")
|
||||
} else {
|
||||
focusRequester.requestFocus()
|
||||
@@ -288,7 +287,7 @@ fun MainScreen(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)){
|
||||
if(WireGuardAutoTunnel.isRunningOnAndroidTv(context)){
|
||||
Row() {
|
||||
IconButton(modifier = Modifier.focusRequester(focusRequester),onClick = {
|
||||
navController.navigate("${Routes.Detail.name}/${tunnel.id}")
|
||||
|
||||
@@ -144,7 +144,12 @@ class MainViewModel @Inject constructor(private val application : Application,
|
||||
@SuppressLint("Range")
|
||||
private fun getFileName(context: Context, uri: Uri): String {
|
||||
if (uri.scheme == "content") {
|
||||
val cursor = context.contentResolver.query(uri, null, null, null, null)
|
||||
val cursor = try {
|
||||
context.contentResolver.query(uri, null, null, null, null)
|
||||
} catch (e : Exception) {
|
||||
Timber.d("Exception getting config name")
|
||||
null
|
||||
}
|
||||
cursor ?: return defaultConfigName()
|
||||
cursor.use {
|
||||
if(cursor.moveToFirst()) {
|
||||
|
||||
@@ -2,8 +2,8 @@ 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.os.Build
|
||||
import android.provider.Settings
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -67,6 +68,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.ui.Routes
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton
|
||||
@@ -80,12 +82,12 @@ fun SettingsScreen(
|
||||
viewModel: SettingsViewModel = hiltViewModel(),
|
||||
padding: PaddingValues,
|
||||
navController: NavController,
|
||||
focusRequester: FocusRequester,
|
||||
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }
|
||||
) {
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val focusManager = LocalFocusManager.current
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
@@ -134,7 +136,7 @@ fun SettingsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if(!backgroundLocationState.status.isGranted) {
|
||||
if(!backgroundLocationState.status.isGranted && Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
modifier = Modifier
|
||||
@@ -147,7 +149,9 @@ fun SettingsScreen(
|
||||
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)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
modifier = if(WireGuardAutoTunnel.isRunningOnAndroidTv(context)) Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp) else Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(30.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -163,11 +167,6 @@ fun SettingsScreen(
|
||||
}) {
|
||||
Text(stringResource(id = R.string.turn_on))
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
if(context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -192,11 +191,6 @@ fun SettingsScreen(
|
||||
}) {
|
||||
Text(stringResource(id = R.string.request))
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
if(context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
@@ -219,7 +213,7 @@ fun SettingsScreen(
|
||||
}
|
||||
return
|
||||
}
|
||||
if(!isLocationServicesEnabled) {
|
||||
if(!isLocationServicesEnabled && Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
@@ -244,19 +238,21 @@ fun SettingsScreen(
|
||||
}) {
|
||||
Text(stringResource(id = R.string.check_again))
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
if(context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val screenPadding = if(WireGuardAutoTunnel.isRunningOnAndroidTv(context)) 5.dp else 15.dp
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
modifier = Modifier
|
||||
modifier = if(WireGuardAutoTunnel.isRunningOnAndroidTv(context)) Modifier
|
||||
.fillMaxHeight(.85f)
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(scrollState)
|
||||
.clickable(indication = null, interactionSource = interactionSource) {
|
||||
focusManager.clearFocus()
|
||||
}
|
||||
.padding(padding) else Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(scrollState)
|
||||
.clickable(indication = null, interactionSource = interactionSource) {
|
||||
@@ -267,7 +263,7 @@ fun SettingsScreen(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(14.dp),
|
||||
.padding(screenPadding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
@@ -282,16 +278,11 @@ fun SettingsScreen(
|
||||
}
|
||||
}
|
||||
)
|
||||
LaunchedEffect(Unit) {
|
||||
if(context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
Text(
|
||||
stringResource(id = R.string.select_tunnel),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(15.dp, bottom = 5.dp, top = 5.dp)
|
||||
modifier = Modifier.padding(screenPadding, bottom = 5.dp, top = 5.dp)
|
||||
)
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
@@ -339,12 +330,12 @@ fun SettingsScreen(
|
||||
Text(
|
||||
stringResource(R.string.trusted_ssid),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(15.dp, bottom = 5.dp, top = 5.dp)
|
||||
modifier = Modifier.padding(screenPadding, bottom = 5.dp, top = 5.dp)
|
||||
)
|
||||
FlowRow(
|
||||
modifier = Modifier.padding(15.dp),
|
||||
modifier = Modifier.padding(screenPadding),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
verticalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
trustedSSIDs.forEach { ssid ->
|
||||
ClickableIconButton(onIconClick = {
|
||||
@@ -359,7 +350,7 @@ fun SettingsScreen(
|
||||
value = currentText,
|
||||
onValueChange = { currentText = it },
|
||||
label = { Text(stringResource(R.string.add_trusted_ssid)) },
|
||||
modifier = Modifier.padding(start = 15.dp, top = 5.dp),
|
||||
modifier = Modifier.padding(start = screenPadding, top = 5.dp),
|
||||
maxLines = 1,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.None,
|
||||
@@ -385,7 +376,7 @@ fun SettingsScreen(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(14.dp),
|
||||
.padding(screenPadding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
@@ -403,7 +394,7 @@ fun SettingsScreen(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(14.dp),
|
||||
.padding(screenPadding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
|
||||
@@ -37,10 +37,9 @@ import androidx.compose.ui.unit.sp
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
|
||||
@Composable
|
||||
fun SupportScreen(padding : PaddingValues) {
|
||||
fun SupportScreen(padding : PaddingValues, focusRequester: FocusRequester) {
|
||||
|
||||
val context = LocalContext.current
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
fun openWebPage(url: String) {
|
||||
val webpage: Uri = Uri.parse(url)
|
||||
@@ -73,11 +72,6 @@ fun SupportScreen(padding : PaddingValues) {
|
||||
}) {
|
||||
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),
|
||||
|
||||
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 238 KiB |
@@ -13,7 +13,7 @@ buildscript {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("com.android.application") version "8.2.0-alpha08" apply false
|
||||
id("com.android.application") version "8.2.0-alpha15" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
|
||||
id("com.google.dagger.hilt.android") version "2.44" apply false
|
||||
kotlin("plugin.serialization") version "1.8.22" apply false
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#Mon Apr 24 22:46:45 EDT 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||