Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot] ef2411fc52 build(deps): bump accompanist from 0.37.0 to 0.37.2
Bumps `accompanist` from 0.37.0 to 0.37.2.

Updates `com.google.accompanist:accompanist-drawablepainter` from 0.37.0 to 0.37.2
- [Release notes](https://github.com/google/accompanist/releases)
- [Commits](https://github.com/google/accompanist/compare/v0.37.0...v0.37.2)

Updates `com.google.accompanist:accompanist-permissions` from 0.37.0 to 0.37.2
- [Release notes](https://github.com/google/accompanist/releases)
- [Commits](https://github.com/google/accompanist/compare/v0.37.0...v0.37.2)

---
updated-dependencies:
- dependency-name: com.google.accompanist:accompanist-drawablepainter
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.google.accompanist:accompanist-permissions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-21 13:25:00 +00:00
29 changed files with 104 additions and 340 deletions
+2 -6
View File
@@ -172,7 +172,8 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.manifest)
// tunnel
// get tunnel lib from github packages or mavenLocal
// implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
implementation(libs.tunnel)
implementation(libs.amneziawg.android)
coreLibraryDesugaring(libs.desugar.jdk.libs)
@@ -187,7 +188,6 @@ dependencies {
// hilt
implementation(libs.hilt.android)
ksp(libs.hilt.android.compiler)
ksp(libs.androidx.hilt.compiler)
// accompanist
implementation(libs.accompanist.permissions)
@@ -221,10 +221,6 @@ dependencies {
// splash
implementation(libs.androidx.core.splashscreen)
// worker
implementation(libs.androidx.work.runtime)
implementation(libs.androidx.hilt.work)
}
fun determineVersionName(): String {
+1 -35
View File
@@ -2,38 +2,4 @@
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
<fields>;
}
# Keep all classes in the org.xbill.DNS package and subpackages
-keep class org.xbill.DNS.** { *; }
-dontwarn org.xbill.DNS.**
# Preserve JNA classes if used (e.g., for IPHlpAPI on Windows)
-keep class com.sun.jna.** { *; }
-dontwarn com.sun.jna.**
# Keep DNS resolver configuration classes that might be loaded dynamically
-keep class org.xbill.DNS.config.** { *; }
-dontwarn org.xbill.DNS.config.**
-keep class org.xbill.DNS.** { *; }
# Prevent optimization issues with native or reflection-based calls
-dontoptimize
-dontshrink
# Uncomment the above if errors persist, but use sparingly as theyre broad
# Suppress warnings about missing classes if not all features are used
-dontwarn java.lang.management.**
-dontwarn sun.nio.ch.**
-keep class com.google.api.client.http.** { *; }
-dontwarn com.google.api.client.http.**
# Keep Joda-Time classes used by Tink
-keep class org.joda.time.** { *; }
-dontwarn org.joda.time.**
-keep class org.slf4j.** { *; }
-dontwarn org.slf4j.**
}
+1 -34
View File
@@ -21,37 +21,4 @@
#-renamesourcefileattribute SourceFile
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
<fields>;
}
# Keep all classes in the org.xbill.DNS package and subpackages
-keep class org.xbill.DNS.** { *; }
-dontwarn org.xbill.DNS.**
# Preserve JNA classes if used (e.g., for IPHlpAPI on Windows)
-keep class com.sun.jna.** { *; }
-dontwarn com.sun.jna.**
# Keep DNS resolver configuration classes that might be loaded dynamically
-keep class org.xbill.DNS.config.** { *; }
-dontwarn org.xbill.DNS.config.**
-keep class org.xbill.DNS.** { *; }
# Prevent optimization issues with native or reflection-based calls
-dontoptimize
-dontshrink
# Uncomment the above if errors persist, but use sparingly as theyre broad
# Suppress warnings about missing classes if not all features are used
-dontwarn java.lang.management.**
-dontwarn sun.nio.ch.**
-keep class com.google.api.client.http.** { *; }
-dontwarn com.google.api.client.http.**
# Keep Joda-Time classes used by Tink
-keep class org.joda.time.** { *; }
-dontwarn org.joda.time.**
-keep class org.slf4j.** { *; }
-dontwarn org.slf4j.**
}
-7
View File
@@ -106,13 +106,6 @@
android:resource="@xml/file_paths" />
</provider>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:multiprocess="true"
tools:node="remove">
</provider>
<service
android:name=".core.service.tile.TunnelControlTile"
android:exported="true"
@@ -40,7 +40,6 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import com.zaneschepke.wireguardautotunnel.core.shortcut.ShortcutManager
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
import com.zaneschepke.wireguardautotunnel.core.worker.ServiceWorker
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
import com.zaneschepke.wireguardautotunnel.ui.Route
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
@@ -67,6 +66,7 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.support.LogsScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@@ -120,16 +120,20 @@ class MainActivity : AppCompatActivity() {
viewModel.getEmitSplitTunnelApps(this@MainActivity)
}
LaunchedEffect(appUiState.autoTunnelActive) {
requestAutoTunnelTileServiceUpdate()
}
with(appUiState.appSettings) {
LaunchedEffect(isAutoTunnelEnabled) {
this@MainActivity.requestAutoTunnelTileServiceUpdate()
}
LaunchedEffect(isShortcutsEnabled) {
if (!isShortcutsEnabled) return@LaunchedEffect shortcutManager.removeShortcuts()
shortcutManager.addShortcuts()
}
}
// TODO could improve this to cancel when no tuns or autotun on
ServiceWorker.start(this)
CompositionLocalProvider(LocalNavController provides navController) {
SnackbarControllerProvider { host ->
WireguardAutoTunnelTheme(theme = appUiState.generalState.theme) {
@@ -214,14 +218,14 @@ class MainActivity : AppCompatActivity() {
composable<Route.Logs> {
LogsScreen()
}
composable<Route.Config> { backStack ->
val args = backStack.toRoute<Route.Config>()
composable<Route.Config> {
val args = it.toRoute<Route.Config>()
val config =
appUiState.tunnels.firstOrNull { it.id == args.id }
ConfigScreen(config, viewModel)
}
composable<Route.TunnelOptions> { backStack ->
val args = backStack.toRoute<Route.TunnelOptions>()
composable<Route.TunnelOptions> {
val args = it.toRoute<Route.TunnelOptions>()
val config = appUiState.tunnels.first { it.id == args.id }
OptionsScreen(config)
}
@@ -234,13 +238,13 @@ class MainActivity : AppCompatActivity() {
composable<Route.KillSwitch> {
KillSwitchScreen(appUiState, viewModel)
}
composable<Route.SplitTunnel> { backStack ->
val args = backStack.toRoute<Route.SplitTunnel>()
composable<Route.SplitTunnel> {
val args = it.toRoute<Route.SplitTunnel>()
val config = appUiState.tunnels.first { it.id == args.id }
SplitTunnelScreen(config, viewModel)
}
composable<Route.TunnelAutoTunnel> { backStack ->
val args = backStack.toRoute<Route.TunnelOptions>()
composable<Route.TunnelAutoTunnel> {
val args = it.toRoute<Route.TunnelOptions>()
val config = appUiState.tunnels.first { it.id == args.id }
TunnelAutoTunnelScreen(config, appUiState.appSettings)
}
@@ -3,11 +3,9 @@ package com.zaneschepke.wireguardautotunnel
import android.app.Application
import android.os.StrictMode
import android.os.StrictMode.ThreadPolicy
import androidx.hilt.work.HiltWorkerFactory
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.Configuration
import com.wireguard.android.backend.GoBackend
import com.zaneschepke.logcatter.LogReader
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
@@ -28,15 +26,7 @@ import timber.log.Timber
import javax.inject.Inject
@HiltAndroidApp
class WireGuardAutoTunnel : Application(), Configuration.Provider {
@Inject
lateinit var workerFactory: HiltWorkerFactory
override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
class WireGuardAutoTunnel : Application() {
@Inject
@ApplicationScope
@@ -36,7 +36,7 @@ class AppUpdateReceiver : BroadcastReceiver() {
with(appDataRepository.settings.get()) {
if (isRestoreOnBootEnabled) {
// If auto tunnel is enabled, just start it and let auto tunnel start appropriate tun
if (isAutoTunnelEnabled && !serviceManager.autoTunnelActive.value) return@launch serviceManager.startAutoTunnel(true)
if (isAutoTunnelEnabled) return@launch serviceManager.startAutoTunnel(true)
tunnelManager.restorePreviousState()
}
}
@@ -35,7 +35,7 @@ class BootReceiver : BroadcastReceiver() {
with(appDataRepository.settings.get()) {
if (isRestoreOnBootEnabled) {
// If auto tunnel is enabled, just start it and let auto tunnel start appropriate tun
if (isAutoTunnelEnabled && !serviceManager.autoTunnelActive.value) return@launch serviceManager.startAutoTunnel(true)
if (isAutoTunnelEnabled) return@launch serviceManager.startAutoTunnel(true)
tunnelManager.restorePreviousState()
}
}
@@ -32,8 +32,8 @@ class KernelReceiver : BroadcastReceiver() {
val action = intent.action ?: return
applicationScope.launch {
if (action == REFRESH_TUNNELS_ACTION) {
tunnelManager.runningTunnelNames().forEach { name ->
val tunnel = tunnelRepository.findByTunnelName(name)
tunnelManager.runningTunnelNames().forEach {
val tunnel = tunnelRepository.findByTunnelName(it)
tunnel?.let {
tunnelRepository.save(it.copy(isActive = true))
}
@@ -187,7 +187,7 @@ class AutoTunnelService : LifecycleService() {
buildNetworkState(it)
}.distinctUntilChanged(),
) { double, networkState ->
AutoTunnelState(tunnelManager.activeTunnels.value, networkState, double.first, double.second)
AutoTunnelState(tunnelManager.activeTunnels().value, networkState, double.first, double.second)
}.collect { state ->
autoTunnelStateFlow.update {
it.copy(activeTunnels = state.activeTunnels, networkState = state.networkState, settings = state.settings, tunnels = state.tunnels)
@@ -52,14 +52,9 @@ class TunnelControlTile : TileService() {
}
fun updateTileState() = applicationScope.launch {
val tunnels = appDataRepository.tunnels.getAll()
if (tunnels.isEmpty()) return@launch setUnavailable()
with(tunnelManager.activeTunnels.value) {
if (isNotEmpty()) if (size == 1) {
tunnels.firstOrNull { it.id == keys.first() }?.let { return@launch updateTile(it.tunName, true) }
} else {
return@launch updateTile(getString(R.string.multiple), true)
}
if (appDataRepository.tunnels.getAll().isEmpty()) return@launch setUnavailable()
with(tunnelManager.activeTunnels().value) {
if (isNotEmpty()) return@launch updateTile(if (size == 1) first().tunName else getString(R.string.multiple), true)
}
appDataRepository.getStartTunnelConfig()?.let {
updateTile(it.tunName, false)
@@ -70,7 +65,7 @@ class TunnelControlTile : TileService() {
super.onClick()
unlockAndRun {
applicationScope.launch {
if (tunnelManager.activeTunnels.value.isNotEmpty()) return@launch tunnelManager.stopTunnel()
if (tunnelManager.activeTunnels().value.isNotEmpty()) return@launch tunnelManager.stopTunnel()
appDataRepository.getStartTunnelConfig()?.let {
tunnelManager.startTunnel(it)
}
@@ -14,7 +14,6 @@ import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
import com.zaneschepke.wireguardautotunnel.util.Constants
@@ -46,8 +45,6 @@ open class BaseTunnel(
internal val tunnels = MutableStateFlow<List<TunnelConf>>(emptyList())
private val _activeTunnels = MutableStateFlow<Map<Int, TunnelState>>(emptyMap())
private val tunnelJobs = mutableMapOf<TunnelConf, Job>()
private val isNetworkAvailable = AtomicBoolean(false)
@@ -70,9 +67,8 @@ open class BaseTunnel(
removedItems.forEach { tun ->
tunnelJobs[tun]?.cancelWithMessage("Canceling tunnel jobs for tunnel: ${tun.name}")
tunnelJobs.remove(tun)
_activeTunnels.update { it - tun.id }
serviceManager.updateTunnelTile()
}
serviceManager.updateTunnelTile()
}
}
}
@@ -87,9 +83,6 @@ open class BaseTunnel(
launch {
startTunnelConfigChangeJob(tunnel)
}
launch {
startStateJob(tunnel)
}
}
override suspend fun bounceTunnel(tunnelConf: TunnelConf) {
@@ -106,13 +99,14 @@ open class BaseTunnel(
return emptySet()
}
override val activeTunnels: StateFlow<Map<Int, TunnelState>>
get() = _activeTunnels.asStateFlow()
override suspend fun activeTunnels(): StateFlow<List<TunnelConf>> {
return tunnels.asStateFlow()
}
override suspend fun startTunnel(tunnelConf: TunnelConf) {
if (tunnels.value.any { it.id == tunnelConf.id }) return Timber.w("Tunnel already running")
serviceManager.startBackgroundService(tunnelConf)
appDataRepository.tunnels.save(tunnelConf.copy(isActive = true))
addToActiveTunnels(tunnelConf)
}
override suspend fun stopTunnel(tunnelConf: TunnelConf?) {
@@ -137,7 +131,7 @@ open class BaseTunnel(
}
}
private fun addToActiveTunnels(conf: TunnelConf) {
internal fun addToActiveTunnels(conf: TunnelConf) {
tunnels.update {
it.toMutableList().apply {
add(conf)
@@ -159,22 +153,13 @@ open class BaseTunnel(
}
}
private suspend fun startStateJob(tunnel: TunnelConf) {
tunnel.state.collect { state ->
_activeTunnels.update {
it + (tunnel.id to state)
}
serviceManager.updateTunnelTile()
}
}
private suspend fun startPingJob(tunnel: TunnelConf) = coroutineScope {
while (isActive) {
if (isNetworkAvailable.get() && tunnel.isActive) {
val pingResult = tunnel.pingTunnel(ioDispatcher)
handlePingResult(tunnel, pingResult)
}
delay(tunnel.pingInterval ?: Constants.PING_INTERVAL)
delay(CHECK_INTERVAL)
}
}
@@ -215,7 +200,7 @@ open class BaseTunnel(
private suspend fun startTunnelConfigChangeJob(tunnel: TunnelConf) = coroutineScope {
appDataRepository.tunnels.flow.collect { storageTuns ->
storageTuns.firstOrNull { it.id == tunnel.id }?.let { storageTun ->
if (!tunnel.isQuickConfigMatching(storageTun) || !tunnel.isPingConfigMatching(storageTun)) {
if (tunnel.isQuickConfigChanged(storageTun) || tunnel.isPingConfigMatching(storageTun)) {
bounceTunnel(tunnel)
}
}
@@ -33,10 +33,10 @@ class KernelTunnel @Inject constructor(
override suspend fun startTunnel(tunnelConf: TunnelConf) {
withContext(ioDispatcher) {
if (tunnels.value.any { it.id == tunnelConf.id }) return@withContext Timber.w("Tunnel already running")
super.startTunnel(tunnelConf)
runCatching {
backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toWgConfig())
super.startTunnel(tunnelConf)
addToActiveTunnels(tunnelConf)
}.onFailure {
onTunnelStop(tunnelConf)
if (it is BackendException) {
@@ -66,8 +66,8 @@ class KernelTunnel @Inject constructor(
}
}
override suspend fun toggleTunnel(tunnelConf: TunnelConf, state: TunnelStatus) {
when (state) {
override suspend fun toggleTunnel(tunnelConf: TunnelConf, status: TunnelStatus) {
when (status) {
TunnelStatus.UP -> backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toWgConfig())
TunnelStatus.DOWN -> backend.setState(tunnelConf, Tunnel.State.DOWN, tunnelConf.toWgConfig())
}
@@ -11,12 +11,10 @@ import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.util.extensions.withData
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext
@@ -36,21 +34,14 @@ class TunnelManager @Inject constructor(
initialValue = null,
)
@OptIn(ExperimentalCoroutinesApi::class)
override val activeTunnels = appSettings
.filterNotNull()
.flatMapLatest { settings ->
if (settings.isKernelEnabled) {
kernelTunnel.activeTunnels
} else {
userspaceTunnel.activeTunnels
override suspend fun activeTunnels(): StateFlow<List<TunnelConf>> {
return withContext(ioDispatcher) {
appSettings.filterNotNull().first().let {
if (it.isKernelEnabled) return@withContext kernelTunnel.activeTunnels()
userspaceTunnel.activeTunnels()
}
}
.stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
initialValue = emptyMap(),
)
}
override suspend fun startTunnel(tunnelConf: TunnelConf) {
appSettings.withData {
@@ -93,14 +84,14 @@ class TunnelManager @Inject constructor(
if (isRestoreOnBootEnabled) {
val previouslyActiveTuns = appDataRepository.tunnels.getActive()
// handle kernel mode
val tunsToStart = previouslyActiveTuns.filterNot { tun -> activeTunnels.value.any { tun.id == it.key } }
val tunsToStart = previouslyActiveTuns.filterNot { tun -> activeTunnels().value.any { tun.id == it.id } }
if (isKernelEnabled) {
return@withContext tunsToStart.forEach {
startTunnel(it)
}
}
// handle userspace
if (activeTunnels.value.isEmpty()) tunsToStart.firstOrNull()?.let { startTunnel(it) }
if (activeTunnels().value.isEmpty()) tunsToStart.firstOrNull()?.let { startTunnel(it) }
}
}
}
@@ -2,12 +2,11 @@ package com.zaneschepke.wireguardautotunnel.core.tunnel
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
import kotlinx.coroutines.flow.StateFlow
interface TunnelProvider {
val activeTunnels: StateFlow<Map<Int, TunnelState>>
suspend fun activeTunnels(): StateFlow<List<TunnelConf>>
suspend fun startTunnel(tunnelConf: TunnelConf)
@@ -34,13 +34,10 @@ class UserspaceTunnel @Inject constructor(
override suspend fun startTunnel(tunnelConf: TunnelConf) {
withContext(ioDispatcher) {
if (tunnels.value.any { it.id == tunnelConf.id }) return@withContext Timber.w("Tunnel already running")
if (tunnels.value.isNotEmpty()) {
stopAllTunnels()
}
super.startTunnel(tunnelConf)
runCatching {
backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toAmConfig())
super.startTunnel(tunnelConf)
addToActiveTunnels(tunnelConf)
}.onFailure {
onTunnelStop(tunnelConf)
if (it is BackendException) {
@@ -1,60 +0,0 @@
package com.zaneschepke.wireguardautotunnel.core.worker
import android.content.Context
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.concurrent.TimeUnit
@HiltWorker
class ServiceWorker @AssistedInject constructor(
@Assisted private val context: Context,
@Assisted private val params: WorkerParameters,
private val serviceManager: ServiceManager,
private val appDataRepository: AppDataRepository,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
private val tunnelManager: TunnelManager,
) : CoroutineWorker(context, params) {
companion object {
private const val TAG = "service_worker"
fun stop(context: Context) {
WorkManager.getInstance(context).cancelAllWorkByTag(TAG)
}
fun start(context: Context) {
val periodicWorkRequest = PeriodicWorkRequestBuilder<ServiceWorker>(
repeatInterval = 15,
repeatIntervalTimeUnit = TimeUnit.MINUTES,
).build()
WorkManager.getInstance(context)
.enqueueUniquePeriodicWork(
TAG,
ExistingPeriodicWorkPolicy.KEEP,
periodicWorkRequest,
)
}
}
override suspend fun doWork(): Result = withContext(ioDispatcher) {
Timber.i("Service worker started")
with(appDataRepository.settings.get()) {
if (isAutoTunnelEnabled && !serviceManager.autoTunnelActive.value) return@with serviceManager.startAutoTunnel(true)
if (tunnelManager.activeTunnels.value.isEmpty()) tunnelManager.restorePreviousState()
}
Result.success()
}
}
@@ -62,9 +62,9 @@ data class TunnelConf(
}
}
fun isQuickConfigMatching(updatedConf: TunnelConf): Boolean {
return updatedConf.wgQuick == wgQuick ||
updatedConf.amQuick == amQuick
fun isQuickConfigChanged(updatedConf: TunnelConf): Boolean {
return updatedConf.wgQuick != wgQuick ||
updatedConf.amQuick != amQuick
}
fun isPingConfigMatching(updatedConf: TunnelConf): Boolean {
@@ -1,6 +0,0 @@
package com.zaneschepke.wireguardautotunnel.domain.enums
enum class ConfigType {
AMNEZIA,
WG,
}
@@ -7,7 +7,7 @@ import com.zaneschepke.wireguardautotunnel.domain.events.AutoTunnelEvent
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
data class AutoTunnelState(
val activeTunnels: Map<Int, TunnelState> = emptyMap(),
val activeTunnels: List<TunnelConf> = emptyList(),
val networkState: NetworkState = NetworkState(),
val settings: AppSettings = AppSettings(),
val tunnels: List<TunnelConf> = emptyList(),
@@ -20,12 +20,12 @@ data class AutoTunnelState(
private fun isMobileTunnelDataChangeNeeded(): Boolean {
val preferredTunnel = preferredMobileDataTunnel()
return preferredTunnel != null &&
activeTunnels.isNotEmpty() && !activeTunnels.any { it.key == preferredTunnel.id }
activeTunnels.isNotEmpty() && !activeTunnels.any { it.id == preferredTunnel.id }
}
private fun isEthernetTunnelChangeNeeded(): Boolean {
val preferredTunnel = preferredEthernetTunnel()
return preferredTunnel != null && activeTunnels.isNotEmpty() && !activeTunnels.any { it.key == preferredTunnel.id }
return preferredTunnel != null && activeTunnels.isNotEmpty() && !activeTunnels.any { it.id == preferredTunnel.id }
}
private fun preferredMobileDataTunnel(): TunnelConf? {
@@ -62,7 +62,7 @@ data class AutoTunnelState(
return settings.isVpnKillSwitchEnabled && (!settings.isDisableKillSwitchOnTrustedEnabled || !isCurrentSSIDTrusted())
}
private fun isNoConnectivity(): Boolean {
fun isNoConnectivity(): Boolean {
return !networkState.isEthernetConnected && !networkState.isWifiConnected && !networkState.isMobileDataConnected
}
@@ -100,7 +100,7 @@ data class AutoTunnelState(
private fun isWifiTunnelPreferred(): Boolean {
val preferred = preferredWifiTunnel()
return activeTunnels.any { it.key == preferred?.id }
return activeTunnels.any { it.id == preferred?.id }
}
fun asAutoTunnelEvent(): AutoTunnelEvent {
@@ -77,7 +77,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
var selectedTunnel by remember { mutableStateOf<TunnelConf?>(null) }
val isRunningOnTv = remember { context.isRunningOnTv() }
val activeTunnels by viewModel.tunnelManager.activeTunnels.collectAsStateWithLifecycle(emptyMap())
val activeTunnels by viewModel.activeTunnels.collectAsStateWithLifecycle(emptyList())
val collator = Collator.getInstance(Locale.getDefault())
@@ -89,7 +89,6 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
val startTunnel = withVpnPermission<TunnelConf> {
viewModel.onTunnelStart(it)
}
val autoTunnelToggleBattery = withIgnoreBatteryOpt(uiState.generalState.isBatteryOptimizationDisableShown) {
if (!uiState.generalState.isBatteryOptimizationDisableShown) viewModel.setBatteryOptimizeDisableShown()
if (uiState.appSettings.isKernelEnabled) {
@@ -133,8 +132,15 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
}
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConf) {
if (!checked) return viewModel.onTunnelStop(tunnel).let { }
if (uiState.appSettings.isKernelEnabled) viewModel.onTunnelStart(tunnel) else startTunnel(tunnel)
if (!checked) {
viewModel.onTunnelStop(tunnel)
return
}
if (uiState.appSettings.isKernelEnabled) {
viewModel.onTunnelStart(tunnel)
} else {
startTunnel.invoke(tunnel)
}
}
Scaffold(
@@ -227,13 +233,13 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
key = { tunnel -> tunnel.id },
) { tunnel ->
val expanded = uiState.generalState.isTunnelStatsExpanded
val tunnelState = activeTunnels.getOrDefault(tunnel.id, TunnelState())
val tunnelState = activeTunnels.firstOrNull { it.id == tunnel.id }?.state?.collectAsStateWithLifecycle()
TunnelRowItem(
tunnelState.state.isUp(),
tunnel.isActive,
expanded,
selectedTunnel?.id == tunnel.id,
tunnel,
tunnelState = tunnelState,
tunnelState = tunnelState?.value ?: TunnelState(),
{ selectedTunnel = tunnel },
{ viewModel.onExpandedChanged(!expanded) },
onDelete = { showDeleteTunnelAlertDialog = true },
@@ -5,9 +5,8 @@ import androidx.compose.foundation.focusable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
@@ -16,7 +15,6 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ViewQuilt
import androidx.compose.material.icons.filled.AppShortcut
import androidx.compose.material.icons.filled.FolderZip
import androidx.compose.material.icons.outlined.Bolt
import androidx.compose.material.icons.outlined.Code
import androidx.compose.material.icons.outlined.FolderZip
@@ -25,11 +23,7 @@ import androidx.compose.material.icons.outlined.Pin
import androidx.compose.material.icons.outlined.Restore
import androidx.compose.material.icons.outlined.VpnKeyOff
import androidx.compose.material.icons.outlined.VpnLock
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -44,7 +38,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType
import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
import com.zaneschepke.wireguardautotunnel.ui.Route
@@ -63,7 +56,7 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.showToast
import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel
import xyz.teamgravity.pin_lock_compose.PinManager
@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel: AppViewModel, uiState: AppUiState) {
val context = LocalContext.current
@@ -75,13 +68,11 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
val interactionSource = remember { MutableInteractionSource() }
var showAuthPrompt by remember { mutableStateOf(false) }
var showExportSheet by remember { mutableStateOf(false) }
if (showAuthPrompt) {
AuthorizationPrompt(
onSuccess = {
showAuthPrompt = false
showExportSheet = true
viewModel.exportAllConfigs(context)
},
onError = { _ ->
showAuthPrompt = false
@@ -98,52 +89,6 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
)
}
if (showExportSheet) {
ModalBottomSheet(onDismissRequest = { showExportSheet = false }) {
Row(
modifier =
Modifier
.fillMaxWidth()
.clickable {
showExportSheet = false
viewModel.exportAllConfigs(context, ConfigType.AMNEZIA)
}
.padding(10.dp),
) {
Icon(
Icons.Filled.FolderZip,
contentDescription = stringResource(id = R.string.export_amnezia),
modifier = Modifier.padding(10.dp),
)
Text(
stringResource(id = R.string.export_amnezia),
modifier = Modifier.padding(10.dp),
)
}
HorizontalDivider()
Row(
modifier =
Modifier
.fillMaxWidth()
.clickable {
showExportSheet = false
viewModel.exportAllConfigs(context, ConfigType.WG)
}
.padding(10.dp),
) {
Icon(
Icons.Filled.FolderZip,
contentDescription = stringResource(id = R.string.export_wireguard),
modifier = Modifier.padding(10.dp),
)
Text(
stringResource(id = R.string.export_wireguard),
modifier = Modifier.padding(10.dp),
)
}
}
}
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.Top),
@@ -46,9 +46,9 @@ fun TunnelStatistics.PeerStats.handshakeStatus(): HandshakeStatus {
fun Peer.isReachable(preferIpv4: Boolean): Boolean {
val host =
if (this.endpoint.isPresent &&
this.endpoint.get().resolved.isPresent
this.endpoint.get().getResolved(preferIpv4).isPresent
) {
this.endpoint.get().resolved.get().host
this.endpoint.get().getResolved(preferIpv4).get().host
} else {
Constants.DEFAULT_PING_IP
}
@@ -109,8 +109,8 @@ constructor(
}
private suspend fun initTunnels() {
tunnels.withData { tunnels ->
tunnels.filter { it.isActive }.forEach {
tunnels.withData {
it.filter { it.isActive }.forEach {
tunnelManager.startTunnel(it)
}
}
@@ -24,6 +24,8 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
import com.zaneschepke.wireguardautotunnel.util.extensions.withData
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.amnezia.awg.config.Config
@@ -42,6 +44,15 @@ constructor(
appDataRepository: AppDataRepository,
) : BaseViewModel(appDataRepository) {
private val _activeTunnels = MutableStateFlow<List<TunnelConf>>(emptyList())
val activeTunnels = _activeTunnels.asStateFlow()
init {
viewModelScope.launch {
tunnelManager.activeTunnels().collect(_activeTunnels::emit)
}
}
fun onDelete(tunnel: TunnelConf) = viewModelScope.launch {
appSettings.withData { settings ->
tunnels.withData {
@@ -5,13 +5,11 @@ import androidx.core.content.FileProvider
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.util.FileUtils
import com.zaneschepke.wireguardautotunnel.util.extensions.launchShareFile
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
import java.time.Instant
import javax.inject.Inject
@@ -23,23 +21,16 @@ constructor(
private val fileUtils: FileUtils,
) : ViewModel() {
fun exportAllConfigs(context: Context, configType: ConfigType) = viewModelScope.launch {
fun exportAllConfigs(context: Context) = viewModelScope.launch {
runCatching {
val shareFile = fileUtils.createNewShareFile("wg-export_${Instant.now().epochSecond}.zip")
val tunnels = appDataRepository.tunnels.getAll()
val (files, shareFileName) = when (configType) {
ConfigType.AMNEZIA -> {
Pair(fileUtils.createAmFiles(tunnels), "am-export_${Instant.now().epochSecond}.zip")
}
ConfigType.WG -> {
Pair(fileUtils.createWgFiles(tunnels), "wg-export_${Instant.now().epochSecond}.zip")
}
}
val shareFile = fileUtils.createNewShareFile(shareFileName)
fileUtils.zipAll(shareFile, files)
val wgFiles = fileUtils.createWgFiles(tunnels)
val amFiles = fileUtils.createAmFiles(tunnels)
val allFiles = wgFiles + amFiles
fileUtils.zipAll(shareFile, allFiles)
val uri = FileProvider.getUriForFile(context, context.getString(R.string.provider), shareFile)
context.launchShareFile(uri)
}.onFailure {
Timber.e(it)
}
}
}
-2
View File
@@ -213,6 +213,4 @@
<string name="unauthorized">Failed to start tunnel, unauthorized.</string>
<string name="tunne_start_failed_title">Tunnel failure</string>
<string name="multiple">Multiple</string>
<string name="export_amnezia">Export as Amnezia</string>
<string name="export_wireguard">Export as WireGuard</string>
</resources>
+5 -9
View File
@@ -1,7 +1,7 @@
[versions]
accompanist = "0.37.0"
accompanist = "0.37.2"
activityCompose = "1.10.0"
amneziawgAndroid = "1.2.8"
amneziawgAndroid = "1.2.6"
androidx-junit = "1.2.1"
appcompat = "1.7.0"
biometricKtx = "1.2.0-alpha05"
@@ -10,7 +10,7 @@ datastorePreferences = "1.1.2"
desugar_jdk_libs = "2.1.4"
espressoCore = "3.6.1"
hiltAndroid = "2.55"
hiltCompiler = "1.2.0"
hiltNavigationCompose = "1.2.0"
junit = "4.13.2"
kotlinx-serialization-json = "1.8.0"
lifecycle-runtime-compose = "2.8.7"
@@ -19,13 +19,12 @@ navigationCompose = "2.8.7"
pinLockCompose = "1.0.4"
roomVersion = "2.6.1"
timber = "5.0.1"
tunnel = "1.2.4"
tunnel = "1.2.2"
androidGradlePlugin = "8.8.0-alpha05"
kotlin = "2.1.10"
ksp = "2.1.10-1.0.30"
composeBom = "2025.02.00"
compose = "1.7.8"
workRuntimeKtxVersion = "2.10.0"
zxingAndroidEmbedded = "4.3.0"
coreSplashscreen = "1.0.1"
gradlePlugins-grgit = "5.3.0"
@@ -46,7 +45,6 @@ amneziawg-android = { module = "com.zaneschepke:amneziawg-android", version.ref
androidx-biometric-ktx = { module = "androidx.biometric:biometric-ktx", version.ref = "biometricKtx" }
androidx-core = { module = "androidx.core:core", version.ref = "coreKtx" }
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
androidx-hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "hiltCompiler" }
androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle-runtime-compose" }
androidx-lifecycle-service = { module = "androidx.lifecycle:lifecycle-service", version.ref = "lifecycle-runtime-compose" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomVersion" }
@@ -64,17 +62,15 @@ androidx-compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compos
#hilt
androidx-room-testing = { module = "androidx.room:room-testing", version.ref = "roomVersion" }
androidx-work-runtime = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtxVersion" }
desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hiltAndroid" }
androidx-hilt-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "hiltCompiler" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" }
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltCompiler" }
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
androidx-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-junit" }
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-compose" }
+1 -1
View File
@@ -1 +1 @@
2
1