mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eeccc71469 | |||
| 0e64bbb4e1 | |||
| f513297ba0 | |||
| 135f8c0459 | |||
| 7a811f4152 | |||
| 2abf681d17 |
@@ -17,12 +17,12 @@ android {
|
||||
|
||||
val versionMajor = 2
|
||||
val versionMinor = 3
|
||||
val versionPatch = 2
|
||||
val versionPatch = 7
|
||||
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.1")
|
||||
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 {
|
||||
|
||||
@@ -64,6 +64,20 @@
|
||||
android:foregroundServiceType="remoteMessaging"
|
||||
android:exported="false">
|
||||
</service>
|
||||
<service
|
||||
android:exported="true"
|
||||
android:name=".service.TunnelControlTile"
|
||||
android:icon="@drawable/shield"
|
||||
android:label="WG Tunnel"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<meta-data android:name="android.service.quicksettings.ACTIVE_TILE"
|
||||
android:value="true" />
|
||||
<meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE"
|
||||
android:value="true" />
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name=".service.foreground.WireGuardTunnelService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
+2
-2
@@ -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()) {
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service
|
||||
|
||||
import android.os.Build
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.repository.Repository
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceState
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceTracker
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardConnectivityWatcherService
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
||||
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.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class TunnelControlTile : TileService() {
|
||||
|
||||
@Inject
|
||||
lateinit var settingsRepo : Repository<Settings>
|
||||
|
||||
@Inject
|
||||
lateinit var configRepo : Repository<TunnelConfig>
|
||||
|
||||
@Inject
|
||||
lateinit var vpnService : VpnService
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Main);
|
||||
|
||||
private lateinit var job : Job
|
||||
|
||||
override fun onStartListening() {
|
||||
job = scope.launch {
|
||||
updateTileState()
|
||||
}
|
||||
super.onStartListening()
|
||||
}
|
||||
|
||||
override fun onTileAdded() {
|
||||
super.onTileAdded()
|
||||
qsTile.contentDescription = this.resources.getString(R.string.toggle_vpn)
|
||||
scope.launch {
|
||||
updateTileState();
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTileRemoved() {
|
||||
super.onTileRemoved()
|
||||
cancelJob()
|
||||
}
|
||||
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
unlockAndRun {
|
||||
scope.launch {
|
||||
try {
|
||||
val tunnel = determineTileTunnel();
|
||||
if(tunnel != null) {
|
||||
attemptWatcherServiceToggle(tunnel.toString())
|
||||
if(vpnService.getState() == Tunnel.State.UP) {
|
||||
stopTunnelService();
|
||||
} else {
|
||||
startTunnelService(tunnel.toString())
|
||||
}
|
||||
}
|
||||
} catch (e : Exception) {
|
||||
Timber.e(e.message)
|
||||
} finally {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun determineTileTunnel() : TunnelConfig? {
|
||||
var tunnelConfig : TunnelConfig? = null;
|
||||
val settings = settingsRepo.getAll()
|
||||
if (!settings.isNullOrEmpty()) {
|
||||
val setting = settings.first()
|
||||
tunnelConfig = if (setting.defaultTunnel != null) {
|
||||
TunnelConfig.from(setting.defaultTunnel!!);
|
||||
} else {
|
||||
val config = configRepo.getAll()?.first();
|
||||
config;
|
||||
}
|
||||
}
|
||||
return tunnelConfig;
|
||||
}
|
||||
|
||||
private fun stopTunnelService() {
|
||||
ServiceTracker.actionOnService(
|
||||
Action.STOP, this.applicationContext,
|
||||
WireGuardTunnelService::class.java)
|
||||
}
|
||||
|
||||
private fun startTunnelService(tunnelConfig : String) {
|
||||
ServiceTracker.actionOnService(
|
||||
Action.START, this.applicationContext,
|
||||
WireGuardTunnelService::class.java,
|
||||
mapOf(this.applicationContext.resources.
|
||||
getString(R.string.tunnel_extras_key) to
|
||||
tunnelConfig))
|
||||
}
|
||||
|
||||
private fun startWatcherService(tunnelConfig : String) {
|
||||
ServiceTracker.actionOnService(
|
||||
Action.START, this,
|
||||
WireGuardConnectivityWatcherService::class.java, mapOf(this.resources.
|
||||
getString(R.string.tunnel_extras_key) to
|
||||
tunnelConfig))
|
||||
}
|
||||
|
||||
private fun stopWatcherService() {
|
||||
ServiceTracker.actionOnService(
|
||||
Action.STOP, this,
|
||||
WireGuardConnectivityWatcherService::class.java)
|
||||
}
|
||||
|
||||
private fun attemptWatcherServiceToggle(tunnelConfig : String) {
|
||||
scope.launch {
|
||||
val settings = settingsRepo.getAll()
|
||||
if (!settings.isNullOrEmpty()) {
|
||||
val setting = settings.first()
|
||||
if(setting.isAutoTunnelEnabled) {
|
||||
when(ServiceTracker.getServiceState( this@TunnelControlTile,
|
||||
WireGuardConnectivityWatcherService::class.java,)) {
|
||||
ServiceState.STARTED -> stopWatcherService()
|
||||
ServiceState.STOPPED -> startWatcherService(tunnelConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateTileState() {
|
||||
vpnService.state.collect {
|
||||
when(it) {
|
||||
Tunnel.State.UP -> {
|
||||
qsTile.state = Tile.STATE_ACTIVE
|
||||
}
|
||||
Tunnel.State.DOWN -> {
|
||||
qsTile.state = Tile.STATE_INACTIVE;
|
||||
}
|
||||
else -> {
|
||||
qsTile.state = Tile.STATE_UNAVAILABLE
|
||||
}
|
||||
}
|
||||
val config = determineTileTunnel();
|
||||
setTileDescription(config?.name ?: this.resources.getString(R.string.no_tunnel_available))
|
||||
qsTile.updateTile()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setTileDescription(description : String) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
qsTile.subtitle = description
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
qsTile.stateDescription = description;
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelJob() {
|
||||
if(this::job.isInitialized) {
|
||||
job.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
-3
@@ -4,10 +4,7 @@ import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import com.zaneschepke.wireguardautotunnel.repository.Repository
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
open class ForegroundService : Service() {
|
||||
|
||||
+2
@@ -24,6 +24,7 @@ object ServiceTracker {
|
||||
|
||||
fun <T : Service> actionOnService(action: Action, application: Application, cls : Class<T>, extras : Map<String,String>? = null) {
|
||||
if (getServiceState(application, cls) == ServiceState.STOPPED && action == Action.STOP) return
|
||||
if (getServiceState(application, cls) == ServiceState.STARTED && action == Action.START) return
|
||||
val intent = Intent(application, cls).also {
|
||||
it.action = action.name
|
||||
extras?.forEach {(k, v) ->
|
||||
@@ -40,6 +41,7 @@ object ServiceTracker {
|
||||
|
||||
fun <T : Service> actionOnService(action: Action, context: Context, cls : Class<T>, extras : Map<String,String>? = null) {
|
||||
if (getServiceState(context, cls) == ServiceState.STOPPED && action == Action.STOP) return
|
||||
if (getServiceState(context, cls) == ServiceState.STARTED && action == Action.START) return
|
||||
val intent = Intent(context, cls).also {
|
||||
it.action = action.name
|
||||
extras?.forEach {(k, v) ->
|
||||
|
||||
+5
-10
@@ -8,8 +8,6 @@ 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
|
||||
@@ -23,11 +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.flow.first
|
||||
import kotlinx.coroutines.flow.single
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
@@ -89,7 +85,6 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
||||
}
|
||||
}
|
||||
cancelWatcherJob()
|
||||
stopVPN()
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
@@ -128,20 +123,20 @@ 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()
|
||||
}
|
||||
}
|
||||
CoroutineScope(watcherJob).launch {
|
||||
launch {
|
||||
manageVpn()
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -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)
|
||||
|
||||
+13
-10
@@ -28,7 +28,6 @@ abstract class BaseNetworkService<T : BaseNetworkService<T>>(val context: Contex
|
||||
object : ConnectivityManager.NetworkCallback(
|
||||
FLAG_INCLUDE_LOCATION_INFO
|
||||
) {
|
||||
|
||||
override fun onAvailable(network: Network) {
|
||||
trySend(NetworkStatus.Available(network))
|
||||
}
|
||||
@@ -45,6 +44,7 @@ abstract class BaseNetworkService<T : BaseNetworkService<T>>(val context: Contex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
object : ConnectivityManager.NetworkCallback() {
|
||||
|
||||
@@ -67,6 +67,8 @@ abstract class BaseNetworkService<T : BaseNetworkService<T>>(val context: Contex
|
||||
}
|
||||
val request = NetworkRequest.Builder()
|
||||
.addTransportType(networkCapability)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
.build()
|
||||
connectivityManager.registerNetworkCallback(request, networkStatusCallback)
|
||||
|
||||
@@ -77,8 +79,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 +91,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -30,6 +30,7 @@ class WireGuardTunnel @Inject constructor(private val backend : Backend,
|
||||
override val tunnelName get() = _tunnelName.asStateFlow()
|
||||
|
||||
private val _state = MutableSharedFlow<Tunnel.State>(
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
||||
replay = 1)
|
||||
|
||||
private val _handshakeStatus = MutableSharedFlow<HandshakeStatus>(replay = 1,
|
||||
|
||||
+1
-1
@@ -11,5 +11,5 @@ data class Settings(
|
||||
var isTunnelOnMobileDataEnabled : Boolean = false,
|
||||
var trustedNetworkSSIDs : MutableList<String> = mutableListOf(),
|
||||
var defaultTunnel : String? = null,
|
||||
var isAlwaysOnVpnEnabled : Boolean = false
|
||||
var isAlwaysOnVpnEnabled : Boolean = false,
|
||||
)
|
||||
|
||||
@@ -152,7 +152,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) {
|
||||
|
||||
+39
-24
@@ -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
|
||||
@@ -19,6 +18,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.FileOpen
|
||||
@@ -52,7 +52,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 +72,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
|
||||
@@ -86,7 +86,6 @@ import kotlinx.coroutines.launch
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
viewModel: MainViewModel = hiltViewModel(), padding: PaddingValues,
|
||||
focusRequester: FocusRequester,
|
||||
snackbarHostState: SnackbarHostState, navController: NavController
|
||||
) {
|
||||
|
||||
@@ -150,7 +149,7 @@ fun MainScreen(
|
||||
})
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.End,
|
||||
floatingActionButton = {
|
||||
floatingActionButton = {
|
||||
AnimatedVisibility(
|
||||
visible = isVisible.value,
|
||||
enter = slideInVertically(initialOffsetY = { it * 2 }),
|
||||
@@ -242,16 +241,19 @@ fun MainScreen(
|
||||
.padding(padding)
|
||||
) {
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()
|
||||
.nestedScroll(nestedScrollConnection),) {
|
||||
items(tunnels.toList()) { tunnel ->
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
.nestedScroll(nestedScrollConnection),
|
||||
) {
|
||||
itemsIndexed(tunnels.toList()) { index, tunnel ->
|
||||
val focusRequester = FocusRequester();
|
||||
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,15 +266,15 @@ 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()
|
||||
}
|
||||
},
|
||||
},
|
||||
rowButton = {
|
||||
if (tunnel.id == selectedTunnel?.id) {
|
||||
Row() {
|
||||
Row {
|
||||
IconButton(onClick = {
|
||||
navController.navigate("${Routes.Config.name}/${selectedTunnel?.id}")
|
||||
}) {
|
||||
@@ -288,30 +290,43 @@ fun MainScreen(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)){
|
||||
Row() {
|
||||
IconButton(modifier = Modifier.focusRequester(focusRequester),onClick = {
|
||||
navController.navigate("${Routes.Detail.name}/${tunnel.id}")
|
||||
}) {
|
||||
if (WireGuardAutoTunnel.isRunningOnAndroidTv(context)) {
|
||||
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))
|
||||
viewModel.showSnackBarMessage(
|
||||
context.resources.getString(
|
||||
R.string.turn_off_tunnel
|
||||
)
|
||||
)
|
||||
} else {
|
||||
navController.navigate("${Routes.Config.name}/${tunnel.id}")
|
||||
}
|
||||
navController.navigate("${Routes.Config.name}/${tunnel.id}")
|
||||
}
|
||||
}) {
|
||||
Icon(Icons.Rounded.Edit, stringResource(id = R.string.edit))
|
||||
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))
|
||||
viewModel.showSnackBarMessage(
|
||||
context.resources.getString(
|
||||
R.string.turn_off_tunnel
|
||||
)
|
||||
)
|
||||
} else {
|
||||
viewModel.onDelete(tunnel)
|
||||
}
|
||||
viewModel.onDelete(tunnel)
|
||||
}
|
||||
}) {
|
||||
Icon(
|
||||
Icons.Rounded.Delete,
|
||||
|
||||
+6
-1
@@ -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()) {
|
||||
|
||||
+25
-14
@@ -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
|
||||
@@ -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,
|
||||
@@ -209,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,
|
||||
@@ -237,11 +241,18 @@ fun SettingsScreen(
|
||||
}
|
||||
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) {
|
||||
@@ -252,7 +263,7 @@ fun SettingsScreen(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(14.dp),
|
||||
.padding(screenPadding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
@@ -271,7 +282,7 @@ fun SettingsScreen(
|
||||
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,
|
||||
@@ -319,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 = {
|
||||
@@ -339,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,
|
||||
@@ -365,7 +376,7 @@ fun SettingsScreen(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(14.dp),
|
||||
.padding(screenPadding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
@@ -383,7 +394,7 @@ fun SettingsScreen(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(14.dp),
|
||||
.padding(screenPadding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
|
||||
-5
@@ -72,11 +72,6 @@ fun SupportScreen(padding : PaddingValues, focusRequester: FocusRequester) {
|
||||
}) {
|
||||
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),
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,1L3,5v6c0,5.55 3.84,10.74 9,12 5.16,-1.26 9,-6.45 9,-12V5l-9,-4z"/>
|
||||
</vector>
|
||||
@@ -84,4 +84,6 @@
|
||||
<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>
|
||||
<string name="toggle_vpn">Toggle VPN</string>
|
||||
<string name="no_tunnel_available">No tunnels available</string>
|
||||
</resources>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 238 KiB |
+1
-1
@@ -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-beta01" 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
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user