mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ef2411fc52 |
@@ -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
@@ -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 they’re 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.**
|
||||
|
||||
}
|
||||
Vendored
+1
-34
@@ -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 they’re 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.**
|
||||
}
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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))
|
||||
}
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+4
-9
@@ -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)
|
||||
|
||||
|
||||
+2
-5
@@ -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,
|
||||
}
|
||||
+5
-5
@@ -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 {
|
||||
|
||||
+13
-7
@@ -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 },
|
||||
|
||||
+3
-58
@@ -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),
|
||||
|
||||
+2
-2
@@ -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 {
|
||||
|
||||
+6
-15
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -1 +1 @@
|
||||
2
|
||||
1
|
||||
|
||||
Reference in New Issue
Block a user