Compare commits

..

15 Commits

Author SHA1 Message Date
dependabot[bot] df716d4035 build(deps): bump compose from 1.7.6 to 1.7.7
Bumps `compose` from 1.7.6 to 1.7.7.

Updates `androidx.compose.ui:ui-test-junit4` from 1.7.6 to 1.7.7

Updates `androidx.compose.ui:ui-tooling` from 1.7.6 to 1.7.7

Updates `androidx.compose.ui:ui-test-manifest` from 1.7.6 to 1.7.7

Updates `androidx.compose.ui:ui-graphics` from 1.7.6 to 1.7.7

Updates `androidx.compose.ui:ui-tooling-preview` from 1.7.6 to 1.7.7

Updates `androidx.compose.ui:ui` from 1.7.6 to 1.7.7

Updates `androidx.compose.material:material-icons-extended` from 1.7.6 to 1.7.7

---
updated-dependencies:
- dependency-name: androidx.compose.ui:ui-test-junit4
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: androidx.compose.ui:ui-tooling
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: androidx.compose.ui:ui-test-manifest
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: androidx.compose.ui:ui-graphics
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: androidx.compose.ui:ui-tooling-preview
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: androidx.compose.ui:ui
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: androidx.compose.material:material-icons-extended
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-30 13:26:48 +00:00
Zane Schepke 1857977d9b ci: add verification step 2025-01-19 13:39:56 -05:00
Zane Schepke 4784b1f65c Merge branch 'main' of https://github.com/zaneschepke/wgtunnel 2025-01-19 12:38:32 -05:00
Zane Schepke f3debcfe45 feat: disable kill switch on trusted
fix: debounce ui bug closes #532
2025-01-19 12:37:43 -05:00
Weblate (bot) a5229c62b1 feat: Translations update from Hosted Weblate (#542)
Co-authored-by: solokot <solokot@gmail.com>
Co-authored-by: Matthaiks <kitynska@gmail.com>
Co-authored-by: CyanWolf <hydemr@pm.me>
2025-01-19 11:59:42 -05:00
Zane Schepke 3e6a2981cb chore: bump deps 2025-01-18 13:21:13 -05:00
Zane Schepke 00254874f0 refactor: connectivity monitoring (#553) 2025-01-18 11:56:04 -05:00
Zane Schepke 1b9560b601 fix: light mode text bugs 2025-01-18 07:16:12 -05:00
Zane Schepke ff97c65d4f fix: preshared key should be hidden
closes #547
2025-01-18 05:47:23 -05:00
Zane Schepke 02dea1e6b0 fix: include all ipv6 with exclusion of private ipv4 addresses
closes #550
2025-01-18 05:31:01 -05:00
Zane Schepke 548362cdde refactor: minor refactor of tunnel options 2025-01-18 04:42:20 -05:00
Zane Schepke 932d27edd7 feat: localizations (#540)
Co-authored-by: teemue <eemil.koivula@gmail.com>
Co-authored-by: solokot <solokot@gmail.com>
Co-authored-by: MouaisTe44 <r.craft.212121@gmail.com>
Co-authored-by: Saturno <rodrigogigante4016@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Thomas Chuang <9euagazrg@mozmail.com>
2025-01-11 16:13:18 -05:00
Zane Schepke d89870e6de fix: tile titles not localized
closes #537
2025-01-11 15:37:39 -05:00
Zane Schepke 0dcee673e1 fix: back gesture 2025-01-11 14:52:29 -05:00
Zane Schepke 7b7c8f6e8c ci: fix fdroid dispatch 2025-01-02 22:58:04 -05:00
51 changed files with 951 additions and 537 deletions
+10 -3
View File
@@ -173,8 +173,15 @@ jobs:
body: |
${{ env.RELEASE_NOTES }}
SHA256 fingerprint:
```${{ steps.checksum.outputs.checksum }}```
SHA-256 fingerprint for the 4096-bit signing certificate:
```sh
${{ steps.checksum.outputs.checksum }}
```
To verify fingerprint:
```sh
apksigner verify --print-certs [path to APK file] | grep SHA-256
```
### Changelog
${{ steps.changelog.outputs.changes }}
@@ -195,7 +202,7 @@ jobs:
- name: Dispatch update for fdroid repo
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.ANDROID_PAT }}
token: ${{ secrets.PAT }}
repository: zaneschepke/fdroid
event-type: fdroid-update
-1
View File
@@ -191,7 +191,6 @@ dependencies {
// accompanist
implementation(libs.accompanist.permissions)
implementation(libs.accompanist.flowlayout)
implementation(libs.accompanist.drawablepainter)
// storage
@@ -0,0 +1,281 @@
{
"formatVersion": 1,
"database": {
"version": 15,
"identityHash": "4827f3b1ab5a4e5aa35937a0925d50e4",
"entities": [
{
"tableName": "Settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_enabled` INTEGER NOT NULL DEFAULT false, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT false, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `is_amnezia_enabled` INTEGER NOT NULL DEFAULT false, `is_wildcards_enabled` INTEGER NOT NULL DEFAULT false, `is_wifi_by_shell_enabled` INTEGER NOT NULL DEFAULT false, `is_stop_on_no_internet_enabled` INTEGER NOT NULL DEFAULT false, `is_vpn_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `is_lan_on_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `debounce_delay_seconds` INTEGER NOT NULL DEFAULT 3, `is_disable_kill_switch_on_trusted_enabled` INTEGER NOT NULL DEFAULT false)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isAutoTunnelEnabled",
"columnName": "is_tunnel_enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isTunnelOnMobileDataEnabled",
"columnName": "is_tunnel_on_mobile_data_enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "trustedNetworkSSIDs",
"columnName": "trusted_network_ssids",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isAlwaysOnVpnEnabled",
"columnName": "is_always_on_vpn_enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isTunnelOnEthernetEnabled",
"columnName": "is_tunnel_on_ethernet_enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isShortcutsEnabled",
"columnName": "is_shortcuts_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isTunnelOnWifiEnabled",
"columnName": "is_tunnel_on_wifi_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isKernelEnabled",
"columnName": "is_kernel_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isRestoreOnBootEnabled",
"columnName": "is_restore_on_boot_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isMultiTunnelEnabled",
"columnName": "is_multi_tunnel_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isPingEnabled",
"columnName": "is_ping_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isAmneziaEnabled",
"columnName": "is_amnezia_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isWildcardsEnabled",
"columnName": "is_wildcards_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isWifiNameByShellEnabled",
"columnName": "is_wifi_by_shell_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isStopOnNoInternetEnabled",
"columnName": "is_stop_on_no_internet_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isVpnKillSwitchEnabled",
"columnName": "is_vpn_kill_switch_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isKernelKillSwitchEnabled",
"columnName": "is_kernel_kill_switch_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isLanOnKillSwitchEnabled",
"columnName": "is_lan_on_kill_switch_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "debounceDelaySeconds",
"columnName": "debounce_delay_seconds",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "3"
},
{
"fieldPath": "isDisableKillSwitchOnTrustedEnabled",
"columnName": "is_disable_kill_switch_on_trusted_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "TunnelConfig",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` TEXT NOT NULL, `tunnel_networks` TEXT NOT NULL DEFAULT '', `is_mobile_data_tunnel` INTEGER NOT NULL DEFAULT false, `is_primary_tunnel` INTEGER NOT NULL DEFAULT false, `am_quick` TEXT NOT NULL DEFAULT '', `is_Active` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `ping_interval` INTEGER DEFAULT null, `ping_cooldown` INTEGER DEFAULT null, `ping_ip` TEXT DEFAULT null, `is_ethernet_tunnel` INTEGER NOT NULL DEFAULT false)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "wgQuick",
"columnName": "wg_quick",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tunnelNetworks",
"columnName": "tunnel_networks",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "isMobileDataTunnel",
"columnName": "is_mobile_data_tunnel",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isPrimaryTunnel",
"columnName": "is_primary_tunnel",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "amQuick",
"columnName": "am_quick",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "isActive",
"columnName": "is_Active",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isPingEnabled",
"columnName": "is_ping_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "pingInterval",
"columnName": "ping_interval",
"affinity": "INTEGER",
"notNull": false,
"defaultValue": "null"
},
{
"fieldPath": "pingCooldown",
"columnName": "ping_cooldown",
"affinity": "INTEGER",
"notNull": false,
"defaultValue": "null"
},
{
"fieldPath": "pingIp",
"columnName": "ping_ip",
"affinity": "TEXT",
"notNull": false,
"defaultValue": "null"
},
{
"fieldPath": "isEthernetTunnel",
"columnName": "is_ethernet_tunnel",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_TunnelConfig_name",
"unique": true,
"columnNames": [
"name"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4827f3b1ab5a4e5aa35937a0925d50e4')"
]
}
}
+4 -3
View File
@@ -74,7 +74,8 @@
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.SHOW_APP_INFO" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
@@ -109,7 +110,7 @@
android:name=".service.tile.TunnelControlTile"
android:exported="true"
android:icon="@drawable/ic_launcher"
android:label="Tunnel control"
android:label="@string/tunnel_control"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<meta-data
android:name="android.service.quicksettings.ACTIVE_TILE"
@@ -126,7 +127,7 @@
android:name=".service.tile.AutoTunnelControlTile"
android:exported="true"
android:icon="@drawable/ic_launcher"
android:label="Auto-tunnel"
android:label="@string/auto_tunnel"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<meta-data
android:name="android.service.quicksettings.ACTIVE_TILE"
@@ -11,7 +11,7 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
@Database(
entities = [Settings::class, TunnelConfig::class],
version = 14,
version = 15,
autoMigrations =
[
AutoMigration(from = 1, to = 2),
@@ -53,6 +53,10 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
from = 13,
to = 14,
),
AutoMigration(
from = 14,
to = 15,
),
],
exportSchema = true,
)
@@ -85,4 +85,13 @@ data class Settings(
defaultValue = "3",
)
val debounceDelaySeconds: Int = 3,
)
@ColumnInfo(
name = "is_disable_kill_switch_on_trusted_enabled",
defaultValue = "false",
)
val isDisableKillSwitchOnTrustedEnabled: Boolean = false,
) {
fun debounceDelayMillis(): Long {
return debounceDelaySeconds * 1000L
}
}
@@ -97,6 +97,9 @@ data class TunnelConfig(
const val AM_QUICK_DEFAULT = ""
const val IPV6_ALL_NETWORKS = "::/0"
const val IPV4_ALL_NETWORKS = "0.0.0.0/0"
val ALL_IPS = setOf(IPV4_ALL_NETWORKS, IPV6_ALL_NETWORKS)
val IPV4_PUBLIC_NETWORKS = setOf(
"0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3",
"64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12",
@@ -105,5 +108,6 @@ data class TunnelConfig(
"192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10",
"193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4",
)
val LAN_BYPASS_ALLOWED_IPS = setOf(IPV6_ALL_NETWORKS) + IPV4_PUBLIC_NETWORKS
}
}
@@ -1,15 +0,0 @@
package com.zaneschepke.wireguardautotunnel.module
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class Wifi
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class MobileData
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class Ethernet
@@ -1,9 +1,7 @@
package com.zaneschepke.wireguardautotunnel.module
import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
import com.zaneschepke.wireguardautotunnel.service.network.InternetConnectivityService
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@@ -14,14 +12,5 @@ import dagger.hilt.components.SingletonComponent
abstract class ServiceModule {
@Binds
@Wifi
abstract fun provideWifiService(wifiService: WifiService): NetworkService
@Binds
@MobileData
abstract fun provideMobileDataService(mobileDataService: MobileDataService): NetworkService
@Binds
@Ethernet
abstract fun provideEthernetService(ethernetService: EthernetService): NetworkService
abstract fun provideInternetConnectivityService(wifiService: InternetConnectivityService): NetworkService
}
@@ -79,9 +79,7 @@ class TunnelModule {
@IoDispatcher ioDispatcher: CoroutineDispatcher,
serviceManager: ServiceManager,
notificationService: NotificationService,
@Wifi wifiService: NetworkService,
@MobileData mobileDataService: NetworkService,
@Ethernet ethernetService: NetworkService,
internetConnectivityService: NetworkService,
): TunnelService {
return WireGuardTunnel(
amneziaBackend,
@@ -92,9 +90,7 @@ class TunnelModule {
ioDispatcher,
serviceManager,
notificationService,
wifiService,
mobileDataService,
ethernetService,
internetConnectivityService,
)
}
@@ -1,6 +1,7 @@
package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel
import android.content.Intent
import android.net.NetworkCapabilities
import android.os.IBinder
import android.os.PowerManager
import androidx.core.app.ServiceCompat
@@ -11,20 +12,20 @@ import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.module.AppShell
import com.zaneschepke.wireguardautotunnel.module.Ethernet
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
import com.zaneschepke.wireguardautotunnel.module.MainImmediateDispatcher
import com.zaneschepke.wireguardautotunnel.module.MobileData
import com.zaneschepke.wireguardautotunnel.module.Wifi
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.AutoTunnelEvent
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.AutoTunnelState
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.KillSwitchEvent
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.NetworkState
import com.zaneschepke.wireguardautotunnel.service.network.InternetConnectivityService
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
import com.zaneschepke.wireguardautotunnel.service.network.NetworkStatus
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationAction
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
@@ -53,16 +54,7 @@ class AutoTunnelService : LifecycleService() {
lateinit var rootShell: Provider<RootShell>
@Inject
@Wifi
lateinit var wifiService: NetworkService
@Inject
@MobileData
lateinit var mobileDataService: NetworkService
@Inject
@Ethernet
lateinit var ethernetService: NetworkService
lateinit var networkService: NetworkService
@Inject
lateinit var appDataRepository: Provider<AppDataRepository>
@@ -121,6 +113,7 @@ class AutoTunnelService : LifecycleService() {
}
startAutoTunnelJob()
startAutoTunnelStateJob()
startKillSwitchJob()
}.onFailure {
Timber.e(it)
}
@@ -168,51 +161,44 @@ class AutoTunnelService : LifecycleService() {
}
}
suspend fun buildNetworkState(networkStatus: NetworkStatus): NetworkState {
return with(autoTunnelStateFlow.value.networkState) {
val wifiName = when {
networkStatus.wifiAvailable &&
(wifiName == null || wifiName == Constants.UNREADABLE_SSID || networkService.didWifiChangeSinceLastCapabilitiesQuery) -> {
networkService.getWifiCapabilities()?.let { getWifiName(it) } ?: wifiName
}
!networkStatus.wifiAvailable -> null
else -> wifiName
}
copy(networkStatus.wifiAvailable, networkStatus.cellularAvailable, networkStatus.ethernetAvailable, wifiName)
}
}
private fun startAutoTunnelStateJob() = lifecycleScope.launch(ioDispatcher) {
combine(
combineSettings(),
combineNetworkEventsJob(),
networkService.status.map {
buildNetworkState(it)
}.distinctUntilChanged(),
) { double, networkState ->
var wifiName: String? = null
if (networkState.wifiName == Constants.UNREADABLE_SSID && double.first.isTunnelOnWifiEnabled) {
wifiName = getWifiName(double.first)
}
val netState = wifiName?.let { networkState.copy(wifiName = it) } ?: networkState
AutoTunnelState(tunnelService.get().vpnState.value, netState, double.first, double.second)
AutoTunnelState(tunnelService.get().vpnState.value, networkState, double.first, double.second)
}.collect { state ->
Timber.d("Network state: ${state.networkState}")
autoTunnelStateFlow.update {
it.copy(vpnState = state.vpnState, networkState = state.networkState, settings = state.settings, tunnels = state.tunnels)
}
}
}
private fun getWifiName(setting: Settings): String? {
private suspend fun getWifiName(wifiCapabilities: NetworkCapabilities): String? {
val setting = appDataRepository.get().settings.getSettings()
return if (setting.isWifiNameByShellEnabled) {
rootShell.get().getCurrentWifiName()
} else if (wifiService.capabilities != null) {
WifiService.getNetworkName(wifiService.capabilities!!, this@AutoTunnelService)
} else {
null
InternetConnectivityService.getNetworkName(wifiCapabilities, this@AutoTunnelService)
}
}
@OptIn(FlowPreview::class)
private fun combineNetworkEventsJob(): Flow<NetworkState> {
return combine(
wifiService.status,
mobileDataService.status,
ethernetService.status,
) { wifi, mobileData, ethernet ->
NetworkState(
wifi.available,
mobileData.available,
ethernet.available,
wifi.name,
)
}.distinctUntilChanged()
}
private fun combineSettings(): Flow<Pair<Settings, TunnelConfigs>> {
return combine(
appDataRepository.get().settings.getSettingsFlow(),
@@ -225,13 +211,29 @@ class AutoTunnelService : LifecycleService() {
}.distinctUntilChanged()
}
private fun startKillSwitchJob() = lifecycleScope.launch(ioDispatcher) {
autoTunnelStateFlow.collect {
if (it == defaultState) return@collect
when (val event = it.asKillSwitchEvent()) {
KillSwitchEvent.DoNothing -> Unit
is KillSwitchEvent.Start -> {
Timber.d("Starting kill switch")
tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, event.allowedIps)
}
KillSwitchEvent.Stop -> {
Timber.d("Stopping kill switch")
tunnelService.get().setBackendState(BackendState.SERVICE_ACTIVE, emptySet())
}
}
}
}
@OptIn(FlowPreview::class)
private fun startAutoTunnelJob() = lifecycleScope.launch(ioDispatcher) {
Timber.i("Starting auto-tunnel network event watcher")
val settings = appDataRepository.get().settings.getSettings()
val debounce = settings.debounceDelaySeconds * 1000L
Timber.d("Starting with debounce delay of: $debounce")
autoTunnelStateFlow.debounce(debounce).collect { watcherState ->
Timber.d("Starting with debounce delay of: ${settings.debounceDelaySeconds} seconds")
autoTunnelStateFlow.debounce(settings.debounceDelayMillis()).collect { watcherState ->
if (watcherState == defaultState) return@collect
Timber.d("New auto tunnel state emitted")
when (val event = watcherState.asAutoTunnelEvent()) {
@@ -2,6 +2,7 @@ package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
@@ -52,6 +53,14 @@ data class AutoTunnelState(
return networkState.isEthernetConnected && !settings.isTunnelOnEthernetEnabled && vpnState.status.isUp()
}
private fun stopKillSwitchOnTrusted(): Boolean {
return networkState.isWifiConnected && settings.isVpnKillSwitchEnabled && settings.isDisableKillSwitchOnTrustedEnabled && isCurrentSSIDTrusted() && vpnState.backendState == BackendState.KILL_SWITCH_ACTIVE
}
private fun startKillSwitch(): Boolean {
return settings.isVpnKillSwitchEnabled && vpnState.backendState != BackendState.KILL_SWITCH_ACTIVE && (!settings.isDisableKillSwitchOnTrustedEnabled || !isCurrentSSIDTrusted())
}
fun isNoConnectivity(): Boolean {
return !networkState.isEthernetConnected && !networkState.isWifiConnected && !networkState.isMobileDataConnected
}
@@ -116,6 +125,17 @@ data class AutoTunnelState(
}
}
fun asKillSwitchEvent(): KillSwitchEvent {
return when {
stopKillSwitchOnTrusted() -> KillSwitchEvent.Stop
startKillSwitch() -> {
val allowedIps = if (settings.isLanOnKillSwitchEnabled) TunnelConfig.LAN_BYPASS_ALLOWED_IPS else emptySet()
KillSwitchEvent.Start(allowedIps)
}
else -> KillSwitchEvent.DoNothing
}
}
private fun isCurrentSSIDTrusted(): Boolean {
return networkState.wifiName?.let {
hasTrustedWifiName(it)
@@ -0,0 +1,7 @@
package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model
sealed class KillSwitchEvent {
data class Start(val allowedIps: Set<String>) : KillSwitchEvent()
data object Stop : KillSwitchEvent()
data object DoNothing : KillSwitchEvent()
}
@@ -1,67 +0,0 @@
package com.zaneschepke.wireguardautotunnel.service.network
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import timber.log.Timber
import javax.inject.Inject
class EthernetService
@Inject
constructor(
@ApplicationContext context: Context,
) : NetworkService {
override var capabilities: NetworkCapabilities? = null
private val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
override val status = callbackFlow {
val networkStatusCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
trySend(NetworkStatus.Available(network))
}
override fun onLost(network: Network) {
trySend(NetworkStatus.Unavailable())
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
capabilities = networkCapabilities
trySend(
NetworkStatus.CapabilitiesChanged(
network,
networkCapabilities,
),
)
}
}
val request =
NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
.build()
connectivityManager.registerNetworkCallback(request, networkStatusCallback)
awaitClose { connectivityManager.unregisterNetworkCallback(networkStatusCallback) }
}.onStart {
// needed for services that are not yet available as it will impact later combine flows if we don't emit
emit(NetworkStatus.Unavailable())
}.catch {
Timber.e(it)
emit(NetworkStatus.Unavailable())
}.map {
when (it) {
is NetworkStatus.Available, is NetworkStatus.CapabilitiesChanged -> Status(true, null)
is NetworkStatus.Unavailable -> Status(false, null)
}
}
}
@@ -9,42 +9,82 @@ import android.net.NetworkRequest
import android.net.wifi.SupplicantState
import android.net.wifi.WifiManager
import android.os.Build
import com.wireguard.android.util.RootShell
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
import com.zaneschepke.wireguardautotunnel.module.AppShell
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import timber.log.Timber
import kotlinx.coroutines.flow.flowOn
import javax.inject.Inject
import javax.inject.Provider
class WifiService
class InternetConnectivityService
@Inject
constructor(
@ApplicationContext private val context: Context,
private val settingsRepository: SettingsRepository,
@AppShell private val rootShell: Provider<RootShell>,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : NetworkService {
override var capabilities: NetworkCapabilities? = null
val mutex = Mutex()
private var ssid: String? = null
private var available: Boolean = false
private val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
@get:Synchronized @set:Synchronized
private var wifiCapabilities: NetworkCapabilities? = null
@get:Synchronized @set:Synchronized
private var wifiNetworkChanged: Boolean = false
override val didWifiChangeSinceLastCapabilitiesQuery: Boolean
get() = wifiNetworkChanged
override val status = callbackFlow {
var wifiState: Boolean = false
var ethernetState: Boolean = false
var cellularState: Boolean = false
fun emitState() {
trySend(NetworkStatus(wifiState, ethernetState, cellularState))
}
val currentNetwork = connectivityManager.activeNetwork
if (currentNetwork == null) {
emitState()
}
fun updateCapabilityState(up: Boolean, network: Network) {
with(connectivityManager.getNetworkCapabilities(network)) {
when {
this == null -> return
hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> wifiState = up
hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ->
cellularState = up
hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) ->
ethernetState = up
}
}
}
fun onWifiChange(network: Network, callback: () -> Unit) {
if (connectivityManager.getNetworkCapabilities(network)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true) {
callback()
}
}
fun onAvailable(network: Network) {
onWifiChange(network) {
wifiNetworkChanged = true
}
}
fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
onWifiChange(network) {
wifiCapabilities = networkCapabilities
}
updateCapabilityState(true, network)
emitState()
}
val networkStatusCallback =
when (Build.VERSION.SDK_INT) {
in Build.VERSION_CODES.S..Int.MAX_VALUE -> {
@@ -53,20 +93,16 @@ constructor(
FLAG_INCLUDE_LOCATION_INFO,
) {
override fun onAvailable(network: Network) {
trySend(NetworkStatus.Available(network))
onAvailable(network)
}
override fun onLost(network: Network) {
trySend(NetworkStatus.Unavailable())
updateCapabilityState(false, network)
emitState()
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
trySend(
NetworkStatus.CapabilitiesChanged(
network,
networkCapabilities,
),
)
onCapabilitiesChanged(network, networkCapabilities)
}
}
}
@@ -74,21 +110,16 @@ constructor(
else -> {
object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
trySend(NetworkStatus.Available(network))
onAvailable(network)
}
override fun onLost(network: Network) {
trySend(NetworkStatus.Unavailable())
updateCapabilityState(false, network)
emitState()
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
capabilities = networkCapabilities
trySend(
NetworkStatus.CapabilitiesChanged(
network,
networkCapabilities,
),
)
onCapabilitiesChanged(network, networkCapabilities)
}
}
}
@@ -96,37 +127,19 @@ constructor(
val request =
NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
.build()
connectivityManager.registerNetworkCallback(request, networkStatusCallback)
awaitClose { connectivityManager.unregisterNetworkCallback(networkStatusCallback) }
}.onStart {
// needed for services that are not yet available as it will impact later combine flows if we don't emit
emit(NetworkStatus.Unavailable())
}.catch {
Timber.e(it)
emit(NetworkStatus.Unavailable())
}.transform {
when (it) {
is NetworkStatus.Available -> mutex.withLock {
available = true
}
is NetworkStatus.CapabilitiesChanged -> mutex.withLock {
if (available || ssid == null || ssid == Constants.UNREADABLE_SSID) {
available = false
Timber.d("Getting SSID from capabilities")
ssid = if (settingsRepository.getSettings().isWifiNameByShellEnabled) {
rootShell.get().getCurrentWifiName()
} else {
getNetworkName(it.networkCapabilities, context)
}
}
emit(Status(true, ssid))
}
is NetworkStatus.Unavailable -> emit(Status(false, null))
}
}.flowOn(ioDispatcher)
override fun getWifiCapabilities(): NetworkCapabilities? {
wifiNetworkChanged = false
return wifiCapabilities
}
companion object {
@@ -1,67 +0,0 @@
package com.zaneschepke.wireguardautotunnel.service.network
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import timber.log.Timber
import javax.inject.Inject
class MobileDataService
@Inject
constructor(
@ApplicationContext context: Context,
) : NetworkService {
override var capabilities: NetworkCapabilities? = null
private val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
override val status = callbackFlow {
val networkStatusCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
trySend(NetworkStatus.Available(network))
}
override fun onLost(network: Network) {
trySend(NetworkStatus.Unavailable())
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
capabilities = networkCapabilities
trySend(
NetworkStatus.CapabilitiesChanged(
network,
networkCapabilities,
),
)
}
}
val request =
NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
.build()
connectivityManager.registerNetworkCallback(request, networkStatusCallback)
awaitClose { connectivityManager.unregisterNetworkCallback(networkStatusCallback) }
}.onStart {
// needed for services that are not yet available as it will impact later combine flows if we don't emit
emit(NetworkStatus.Unavailable())
}.catch {
Timber.e(it)
emit(NetworkStatus.Unavailable())
}.map {
when (it) {
is NetworkStatus.Available, is NetworkStatus.CapabilitiesChanged -> Status(true, null)
is NetworkStatus.Unavailable -> Status(false, null)
}
}
}
@@ -1,28 +1,12 @@
package com.zaneschepke.wireguardautotunnel.service.network
import android.net.Network
import android.net.NetworkCapabilities
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
interface NetworkService {
val status: Flow<Status>
var capabilities: NetworkCapabilities?
}
val status: Flow<NetworkStatus>
inline fun <Result> Flow<NetworkStatus>.map(
crossinline onUnavailable: suspend () -> Result,
crossinline onAvailable: suspend (network: Network) -> Result,
crossinline onCapabilitiesChanged:
suspend (network: Network, networkCapabilities: NetworkCapabilities) -> Result,
): Flow<Result> = map { status ->
when (status) {
is NetworkStatus.Unavailable -> onUnavailable()
is NetworkStatus.Available -> onAvailable(status.network)
is NetworkStatus.CapabilitiesChanged ->
onCapabilitiesChanged(
status.network,
status.networkCapabilities,
)
}
// util to help limit location queries
val didWifiChangeSinceLastCapabilitiesQuery: Boolean
fun getWifiCapabilities(): NetworkCapabilities?
}
@@ -1,14 +1,9 @@
package com.zaneschepke.wireguardautotunnel.service.network
import android.net.Network
import android.net.NetworkCapabilities
sealed class NetworkStatus {
abstract val isConnected: Boolean
class Available(val network: Network, override val isConnected: Boolean = true) : NetworkStatus()
class Unavailable(override val isConnected: Boolean = false) : NetworkStatus()
class CapabilitiesChanged(val network: Network, val networkCapabilities: NetworkCapabilities, override val isConnected: Boolean = true) :
NetworkStatus()
data class NetworkStatus(
val wifiAvailable: Boolean,
val ethernetAvailable: Boolean,
val cellularAvailable: Boolean,
) {
val allOffline = !wifiAvailable && !ethernetAvailable && !cellularAvailable
}
@@ -1,6 +0,0 @@
package com.zaneschepke.wireguardautotunnel.service.network
data class Status(
val available: Boolean,
val name: String?,
)
@@ -5,6 +5,7 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStati
data class VpnState(
val status: TunnelState = TunnelState.DOWN,
val backendState: BackendState = BackendState.INACTIVE,
val tunnelConfig: TunnelConfig? = null,
val statistics: TunnelStatistics? = null,
)
@@ -2,28 +2,24 @@ package com.zaneschepke.wireguardautotunnel.service.tunnel
import com.wireguard.android.backend.Backend
import com.wireguard.android.backend.Tunnel.State
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
import com.zaneschepke.wireguardautotunnel.module.Ethernet
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
import com.zaneschepke.wireguardautotunnel.module.Kernel
import com.zaneschepke.wireguardautotunnel.module.MobileData
import com.zaneschepke.wireguardautotunnel.module.Wifi
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model.NetworkState
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService.Companion.VPN_NOTIFICATION_ID
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification
import com.zaneschepke.wireguardautotunnel.util.StringValue
import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState
import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendState
@@ -31,14 +27,11 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@@ -63,9 +56,7 @@ constructor(
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
private val serviceManager: ServiceManager,
private val notificationService: NotificationService,
@Wifi private val wifiService: NetworkService,
@MobileData private val mobileDataService: NetworkService,
@Ethernet private val ethernetService: NetworkService,
private val internetConnectivityService: NetworkService,
) : TunnelService {
private val _vpnState = MutableStateFlow(VpnState())
@@ -105,23 +96,6 @@ constructor(
}
}
// TODO refactor duplicate
@OptIn(FlowPreview::class)
private fun combineNetworkEventsJob(): Flow<NetworkState> {
return combine(
wifiService.status,
mobileDataService.status,
ethernetService.status,
) { wifi, mobileData, ethernet ->
NetworkState(
wifi.available,
mobileData.available,
ethernet.available,
wifi.name,
)
}.distinctUntilChanged()
}
private suspend fun setState(tunnelConfig: TunnelConfig, tunnelState: TunnelState): Result<TunnelState> {
return runCatching {
when (val backend = backend()) {
@@ -248,6 +222,9 @@ constructor(
when (val backend = backend()) {
is org.amnezia.awg.backend.Backend -> {
backend.setBackendState(backendState.asAmBackendState(), allowedIps)
_vpnState.update {
it.copy(backendState = backendState)
}
}
is Backend -> {
// TODO not yet implemented
@@ -447,13 +424,8 @@ constructor(
}
private fun startNetworkJob() = applicationScope.launch(ioDispatcher) {
combineNetworkEventsJob().collect {
Timber.d("New network state: $it")
if (!it.isWifiConnected && !it.isEthernetConnected && !it.isMobileDataConnected) {
isNetworkAvailable.set(false)
} else {
isNetworkAvailable.set(true)
}
internetConnectivityService.status.distinctUntilChanged().collect {
isNetworkAvailable.set(!it.allOffline)
}
}
@@ -213,7 +213,7 @@ constructor(
if (!enabled) return@withContext tunnelService.get().setBackendState(BackendState.SERVICE_ACTIVE, emptySet())
Timber.d("Starting kill switch")
val allowedIps = if (appDataRepository.settings.getSettings().isLanOnKillSwitchEnabled) {
TunnelConfig.IPV4_PUBLIC_NETWORKS
TunnelConfig.LAN_BYPASS_ALLOWED_IPS
} else {
emptySet()
}
@@ -227,7 +227,7 @@ constructor(
isLanOnKillSwitchEnabled = enabled,
),
)
val allowedIps = if (enabled) TunnelConfig.IPV4_PUBLIC_NETWORKS else emptySet()
val allowedIps = if (enabled) TunnelConfig.LAN_BYPASS_ALLOWED_IPS else emptySet()
Timber.d("Setting allowedIps $allowedIps")
tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps)
}
@@ -35,7 +35,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
@@ -70,7 +69,6 @@ import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.system.exitProcess
@@ -200,7 +198,7 @@ class MainActivity : AppCompatActivity() {
}
composable<Route.AutoTunnel> {
AutoTunnelScreen(
appUiState,
appUiState.settings,
)
}
composable<Route.Appearance> {
@@ -216,7 +214,7 @@ class MainActivity : AppCompatActivity() {
SupportScreen(appUiState, viewModel)
}
composable<Route.AutoTunnelAdvanced> {
AdvancedScreen(appUiState, viewModel)
AdvancedScreen(appUiState.settings, viewModel)
}
composable<Route.Logs> {
LogsScreen()
@@ -251,11 +249,9 @@ class MainActivity : AppCompatActivity() {
TunnelAutoTunnelScreen(config, appUiState.settings)
}
}
BackHandler(enabled = true) {
lifecycleScope.launch {
if (!navController.popBackStack()) {
this@MainActivity.finish()
}
BackHandler {
if (navController.previousBackStackEntry == null || !navController.popBackStack()) {
this@MainActivity.finish()
}
}
}
@@ -47,13 +47,13 @@ fun SubmitConfigurationTextBox(
CustomTextField(
isError = isErrorValue(stateValue),
textStyle = MaterialTheme.typography.bodySmall,
textStyle = MaterialTheme.typography.bodySmall.copy(color = MaterialTheme.colorScheme.onSurface),
value = stateValue,
onValueChange = { stateValue = it },
interactionSource = interactionSource,
label = { Text(label) },
label = { Text(label, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.labelMedium) },
containerColor = MaterialTheme.colorScheme.surface,
placeholder = { Text(hint, style = MaterialTheme.typography.bodySmall) },
placeholder = { Text(hint, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.outline) },
modifier =
Modifier
.padding(
@@ -21,6 +21,7 @@ import androidx.compose.material.icons.outlined.Security
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.SettingsEthernet
import androidx.compose.material.icons.outlined.SignalCellular4Bar
import androidx.compose.material.icons.outlined.VpnKeyOff
import androidx.compose.material.icons.outlined.Wifi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -42,7 +43,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.ui.AppUiState
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import com.zaneschepke.wireguardautotunnel.ui.Route
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
@@ -64,7 +65,7 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
@OptIn(ExperimentalPermissionsApi::class, ExperimentalLayoutApi::class)
@Composable
fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltViewModel()) {
fun AutoTunnelScreen(settings: Settings, viewModel: AutoTunnelViewModel = hiltViewModel()) {
val context = LocalContext.current
val navController = LocalNavController.current
@@ -103,7 +104,7 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
}
}
LaunchedEffect(uiState.settings.trustedNetworkSSIDs) {
LaunchedEffect(settings.trustedNetworkSSIDs) {
currentText = ""
}
@@ -154,15 +155,15 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
},
trailing = {
ScaledSwitch(
enabled = !uiState.settings.isAlwaysOnVpnEnabled,
checked = uiState.settings.isTunnelOnWifiEnabled,
enabled = !settings.isAlwaysOnVpnEnabled,
checked = settings.isTunnelOnWifiEnabled,
onClick = {
viewModel.onToggleTunnelOnWifi()
viewModel.onToggleTunnelOnWifi(settings)
},
)
},
onClick = {
viewModel.onToggleTunnelOnWifi()
viewModel.onToggleTunnelOnWifi(settings)
},
),
SelectionItem(
@@ -181,19 +182,19 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
},
trailing = {
ScaledSwitch(
checked = uiState.settings.isWifiNameByShellEnabled,
checked = settings.isWifiNameByShellEnabled,
onClick = {
viewModel.onRootShellWifiToggle()
viewModel.onRootShellWifiToggle(settings)
},
)
},
onClick = {
viewModel.onRootShellWifiToggle()
viewModel.onRootShellWifiToggle(settings)
},
),
),
)
if (uiState.settings.isTunnelOnWifiEnabled) {
if (settings.isTunnelOnWifiEnabled) {
addAll(
listOf(
SelectionItem(
@@ -209,14 +210,14 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
},
trailing = {
ScaledSwitch(
checked = uiState.settings.isWildcardsEnabled,
checked = settings.isWildcardsEnabled,
onClick = {
viewModel.onToggleWildcards()
viewModel.onToggleWildcards(settings)
},
)
},
onClick = {
viewModel.onToggleWildcards()
viewModel.onToggleWildcards(settings)
},
),
SelectionItem(
@@ -255,21 +256,44 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
},
description = {
TrustedNetworkTextBox(
uiState.settings.trustedNetworkSSIDs,
onDelete = viewModel::onDeleteTrustedSSID,
settings.trustedNetworkSSIDs,
onDelete = { viewModel.onDeleteTrustedSSID(it, settings) },
currentText = currentText,
onSave = { ssid ->
if (uiState.settings.isWifiNameByShellEnabled || isWifiNameReadable()) viewModel.onSaveTrustedSSID(ssid)
if (settings.isWifiNameByShellEnabled || isWifiNameReadable()) viewModel.onSaveTrustedSSID(ssid, settings)
},
onValueChange = { currentText = it },
supporting = {
if (uiState.settings.isWildcardsEnabled) {
if (settings.isWildcardsEnabled) {
WildcardsLabel()
}
},
)
},
),
SelectionItem(
Icons.Outlined.VpnKeyOff,
title = {
Text(
stringResource(R.string.kill_switch_off),
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
)
},
description = {
},
trailing = {
ScaledSwitch(
enabled = settings.isVpnKillSwitchEnabled,
checked = settings.isDisableKillSwitchOnTrustedEnabled,
onClick = {
viewModel.onToggleStopKillSwitchOnTrusted(settings)
},
)
},
onClick = {
viewModel.onToggleStopKillSwitchOnTrusted(settings)
},
),
),
)
}
@@ -287,13 +311,13 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
},
trailing = {
ScaledSwitch(
enabled = !uiState.settings.isAlwaysOnVpnEnabled,
checked = uiState.settings.isTunnelOnMobileDataEnabled,
onClick = { viewModel.onToggleTunnelOnMobileData() },
enabled = !settings.isAlwaysOnVpnEnabled,
checked = settings.isTunnelOnMobileDataEnabled,
onClick = { viewModel.onToggleTunnelOnMobileData(settings) },
)
},
onClick = {
viewModel.onToggleTunnelOnMobileData()
viewModel.onToggleTunnelOnMobileData(settings)
},
),
SelectionItem(
@@ -306,13 +330,13 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
},
trailing = {
ScaledSwitch(
enabled = !uiState.settings.isAlwaysOnVpnEnabled,
checked = uiState.settings.isTunnelOnEthernetEnabled,
onClick = { viewModel.onToggleTunnelOnEthernet() },
enabled = !settings.isAlwaysOnVpnEnabled,
checked = settings.isTunnelOnEthernetEnabled,
onClick = { viewModel.onToggleTunnelOnEthernet(settings) },
)
},
onClick = {
viewModel.onToggleTunnelOnEthernet()
viewModel.onToggleTunnelOnEthernet(settings)
},
),
SelectionItem(
@@ -331,12 +355,12 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
},
trailing = {
ScaledSwitch(
checked = uiState.settings.isStopOnNoInternetEnabled,
onClick = { viewModel.onToggleStopOnNoInternet() },
checked = settings.isStopOnNoInternetEnabled,
onClick = { viewModel.onToggleStopOnNoInternet(settings) },
)
},
onClick = {
viewModel.onToggleStopOnNoInternet()
viewModel.onToggleStopOnNoInternet(settings)
},
),
),
@@ -12,8 +12,6 @@ import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
import com.zaneschepke.wireguardautotunnel.util.StringValue
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@@ -28,11 +26,8 @@ constructor(
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() {
private val settings = appDataRepository.settings.getSettingsFlow()
.stateIn(viewModelScope, SharingStarted.Eagerly, Settings())
fun onToggleTunnelOnWifi() = viewModelScope.launch {
with(settings.value) {
fun onToggleTunnelOnWifi(settings: Settings) = viewModelScope.launch {
with(settings) {
appDataRepository.settings.save(
copy(
isTunnelOnWifiEnabled = !isTunnelOnWifiEnabled,
@@ -41,8 +36,8 @@ constructor(
}
}
fun onToggleTunnelOnMobileData() = viewModelScope.launch {
with(settings.value) {
fun onToggleTunnelOnMobileData(settings: Settings) = viewModelScope.launch {
with(settings) {
appDataRepository.settings.save(
copy(
isTunnelOnMobileDataEnabled = !isTunnelOnMobileDataEnabled,
@@ -51,8 +46,8 @@ constructor(
}
}
fun onToggleWildcards() = viewModelScope.launch {
with(settings.value) {
fun onToggleWildcards(settings: Settings) = viewModelScope.launch {
with(settings) {
appDataRepository.settings.save(
copy(
isWildcardsEnabled = !isWildcardsEnabled,
@@ -61,8 +56,8 @@ constructor(
}
}
fun onDeleteTrustedSSID(ssid: String) = viewModelScope.launch {
with(settings.value) {
fun onDeleteTrustedSSID(ssid: String, settings: Settings) = viewModelScope.launch {
with(settings) {
appDataRepository.settings.save(
copy(
trustedNetworkSSIDs = (trustedNetworkSSIDs - ssid).toMutableList(),
@@ -71,9 +66,9 @@ constructor(
}
}
fun onRootShellWifiToggle() = viewModelScope.launch {
fun onRootShellWifiToggle(settings: Settings) = viewModelScope.launch {
requestRoot().onSuccess {
with(settings.value) {
with(settings) {
appDataRepository.settings.save(
copy(isWifiNameByShellEnabled = !isWifiNameByShellEnabled),
)
@@ -92,8 +87,8 @@ constructor(
}
}
fun onToggleTunnelOnEthernet() = viewModelScope.launch {
with(settings.value) {
fun onToggleTunnelOnEthernet(settings: Settings) = viewModelScope.launch {
with(settings) {
appDataRepository.settings.save(
copy(
isTunnelOnEthernetEnabled = !isTunnelOnEthernetEnabled,
@@ -102,13 +97,16 @@ constructor(
}
}
fun onSaveTrustedSSID(ssid: String) = viewModelScope.launch {
fun onSaveTrustedSSID(ssid: String, settings: Settings) = viewModelScope.launch {
if (ssid.isEmpty()) return@launch
val trimmed = ssid.trim()
with(settings.value) {
with(settings) {
if (!trustedNetworkSSIDs.contains(trimmed)) {
this.trustedNetworkSSIDs.add(ssid)
appDataRepository.settings.save(this)
appDataRepository.settings.save(
settings.copy(
trustedNetworkSSIDs = (trustedNetworkSSIDs + ssid).toMutableList(),
),
)
} else {
SnackbarController.showMessage(
StringValue.StringResource(
@@ -119,11 +117,19 @@ constructor(
}
}
fun onToggleStopOnNoInternet() = viewModelScope.launch {
with(settings.value) {
fun onToggleStopOnNoInternet(settings: Settings) = viewModelScope.launch {
with(settings) {
appDataRepository.settings.save(
copy(isStopOnNoInternetEnabled = !isStopOnNoInternetEnabled),
)
}
}
fun onToggleStopKillSwitchOnTrusted(settings: Settings) = viewModelScope.launch {
with(settings) {
appDataRepository.settings.save(
copy(isDisableKillSwitchOnTrustedEnabled = !isDisableKillSwitchOnTrustedEnabled),
)
}
}
}
@@ -18,9 +18,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@@ -29,7 +27,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
@@ -38,21 +36,11 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
@Composable
fun AdvancedScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
fun AdvancedScreen(settings: Settings, appViewModel: AppViewModel) {
var isDropDownExpanded by remember {
mutableStateOf(false)
}
var selected by remember { mutableIntStateOf(appUiState.settings.debounceDelaySeconds) }
LaunchedEffect(selected) {
if (selected == appUiState.settings.debounceDelaySeconds) return@LaunchedEffect
appViewModel.saveSettings(appUiState.settings.copy(debounceDelaySeconds = selected))
if (appUiState.settings.isAutoTunnelEnabled) {
appViewModel.bounceAutoTunnel()
}
}
Scaffold(
topBar = {
TopNavBar(stringResource(R.string.advanced_settings))
@@ -87,7 +75,7 @@ fun AdvancedScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
horizontalArrangement = Arrangement.spacedBy(5.dp, Alignment.CenterHorizontally),
verticalAlignment = Alignment.CenterVertically,
) {
Text(text = selected.toString(), style = MaterialTheme.typography.bodyMedium)
Text(text = settings.debounceDelaySeconds.toString(), style = MaterialTheme.typography.bodyMedium)
val icon = Icons.Default.ArrowDropDown
Icon(icon, icon.name)
}
@@ -107,7 +95,9 @@ fun AdvancedScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
},
onClick = {
isDropDownExpanded = false
selected = num
appViewModel.saveSettings(
settings.copy(debounceDelaySeconds = num),
)
},
)
}
@@ -65,11 +65,17 @@ fun TrustedNetworkTextBox(
}
}
CustomTextField(
textStyle = MaterialTheme.typography.bodySmall,
textStyle = MaterialTheme.typography.bodySmall.copy(color = MaterialTheme.colorScheme.onSurface),
value = currentText,
onValueChange = onValueChange,
interactionSource = remember { MutableInteractionSource() },
label = { Text(stringResource(R.string.add_wifi_name)) },
label = {
Text(
stringResource(R.string.add_wifi_name),
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.labelMedium,
)
},
containerColor = MaterialTheme.colorScheme.surface,
supportingText = supporting,
modifier =
@@ -57,6 +57,11 @@ fun OptionsScreen(tunnelConfig: TunnelConfig, viewModel: TunnelOptionsViewModel
LaunchedEffect(tunnelConfig.tunnelNetworks) {
currentText = ""
}
val onPingToggle = {
viewModel.saveTunnelChanges(tunnelConfig.copy(isPingEnabled = !tunnelConfig.isPingEnabled))
}
Scaffold(
topBar = {
TopNavBar(tunnelConfig.name)
@@ -165,10 +170,10 @@ fun OptionsScreen(tunnelConfig: TunnelConfig, viewModel: TunnelOptionsViewModel
trailing = {
ScaledSwitch(
checked = tunnelConfig.isPingEnabled,
onClick = { viewModel.onToggleRestartOnPing(tunnelConfig) },
onClick = { onPingToggle() },
)
},
onClick = { viewModel.onToggleRestartOnPing(tunnelConfig) },
onClick = { onPingToggle() },
),
)
if (tunnelConfig.isPingEnabled) {
@@ -188,8 +193,7 @@ fun OptionsScreen(tunnelConfig: TunnelConfig, viewModel: TunnelOptionsViewModel
},
)
fun isSecondsError(seconds: String?): Boolean {
return seconds?.let { value -> if (value.isBlank()) false else value.toLong() >= Long.MAX_VALUE / 1000 }
?: false
return seconds?.let { value -> if (value.isBlank()) false else value.toLong() >= Long.MAX_VALUE / 1000 } == true
}
SubmitConfigurationTextBox(
tunnelConfig.pingInterval?.let { (it / 1000).toString() },
@@ -201,9 +205,7 @@ fun OptionsScreen(tunnelConfig: TunnelConfig, viewModel: TunnelOptionsViewModel
),
isErrorValue = ::isSecondsError,
onSubmit = {
viewModel.saveTunnelChanges(
tunnelConfig.copy(pingInterval = if (it.isBlank()) null else it.toLong() * 1000),
)
viewModel.onPingIntervalChange(tunnelConfig, it)
},
)
SubmitConfigurationTextBox(
@@ -214,11 +216,7 @@ fun OptionsScreen(tunnelConfig: TunnelConfig, viewModel: TunnelOptionsViewModel
keyboardType = KeyboardType.Number,
),
isErrorValue = ::isSecondsError,
onSubmit = {
viewModel.saveTunnelChanges(
tunnelConfig.copy(pingCooldown = if (it.isBlank()) null else it.toLong() * 1000),
)
},
onSubmit = { viewModel.onPingCoolDownChange(tunnelConfig, it) },
)
},
),
@@ -14,13 +14,6 @@ class TunnelOptionsViewModel
constructor(
private val appDataRepository: AppDataRepository,
) : ViewModel() {
fun onToggleRestartOnPing(tunnelConfig: TunnelConfig) = viewModelScope.launch {
appDataRepository.tunnels.save(
tunnelConfig.copy(
isPingEnabled = !tunnelConfig.isPingEnabled,
),
)
}
fun onTogglePrimaryTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch {
appDataRepository.tunnels.updatePrimaryTunnel(
@@ -34,4 +27,16 @@ constructor(
fun saveTunnelChanges(tunnelConfig: TunnelConfig) = viewModelScope.launch {
appDataRepository.tunnels.save(tunnelConfig)
}
fun onPingIntervalChange(tunnelConfig: TunnelConfig, interval: String) = viewModelScope.launch {
val interval = if (interval.isBlank()) null else interval.toLong() * 1000
saveTunnelChanges(
tunnelConfig.copy(pingInterval = interval),
)
}
fun onPingCoolDownChange(tunnelConfig: TunnelConfig, cooldown: String) = viewModelScope.launch {
val cooldown = if (cooldown.isBlank()) null else cooldown.toLong() * 1000
saveTunnelChanges(tunnelConfig.copy(pingCooldown = cooldown))
}
}
@@ -303,7 +303,13 @@ fun ConfigScreen(tunnelConfig: TunnelConfig?, appViewModel: AppViewModel) {
)
}
},
label = { Text(stringResource(R.string.private_key)) },
label = {
Text(
stringResource(R.string.private_key),
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.labelMedium,
)
},
singleLine = true,
placeholder = {
Text(
@@ -342,7 +348,13 @@ fun ConfigScreen(tunnelConfig: TunnelConfig?, appViewModel: AppViewModel) {
)
}
},
label = { Text(stringResource(R.string.public_key)) },
label = {
Text(
stringResource(R.string.public_key),
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.labelMedium,
)
},
singleLine = true,
placeholder = {
Text(
@@ -677,15 +689,40 @@ fun ConfigScreen(tunnelConfig: TunnelConfig?, appViewModel: AppViewModel) {
hint = stringResource(R.string.base64_key),
modifier = Modifier.fillMaxWidth(),
)
ConfigurationTextBox(
val presharedKeyEnabled = (tunnelConfig == null) || isAuthenticated ||
with(configPair.second?.peers[index]?.preSharedKey) { this?.isEmpty == true || this?.isPresent == false }
OutlinedTextField(
textStyle = MaterialTheme.typography.labelLarge,
modifier =
Modifier
.fillMaxWidth()
.clickable { showAuthPrompt = true },
value = peer.preSharedKey,
onValueChange = { value ->
peersState[index] = peersState[index].copy(preSharedKey = value)
visualTransformation =
if (presharedKeyEnabled) {
VisualTransformation.None
} else {
PasswordVisualTransformation()
},
enabled = presharedKeyEnabled,
onValueChange = { value -> peersState[index] = peersState[index].copy(preSharedKey = value) },
label = {
Text(
stringResource(R.string.preshared_key),
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.labelMedium,
)
},
singleLine = true,
placeholder = {
Text(
stringResource(R.string.optional),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.outline,
)
},
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
label = stringResource(R.string.preshared_key),
hint = stringResource(R.string.optional),
modifier = Modifier.fillMaxWidth(),
)
OutlinedTextField(
textStyle = MaterialTheme.typography.labelLarge,
@@ -702,7 +739,9 @@ fun ConfigScreen(tunnelConfig: TunnelConfig?, appViewModel: AppViewModel) {
style = MaterialTheme.typography.labelMedium,
)
},
label = { Text(stringResource(R.string.persistent_keepalive), style = MaterialTheme.typography.labelMedium) },
label = {
Text(stringResource(R.string.persistent_keepalive), color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.labelMedium)
},
singleLine = true,
placeholder = {
Text(stringResource(R.string.optional_no_recommend), style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.outline)
@@ -728,7 +767,13 @@ fun ConfigScreen(tunnelConfig: TunnelConfig?, appViewModel: AppViewModel) {
onValueChange = { value ->
peersState[index] = peersState[index].copy(allowedIps = value)
},
label = { Text(stringResource(R.string.allowed_ips)) },
label = {
Text(
stringResource(R.string.allowed_ips),
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.labelMedium,
)
},
singleLine = true,
placeholder = {
Text(stringResource(R.string.comma_separated_list), style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.outline)
@@ -1,6 +1,7 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.tunneloptions.config.model
import com.wireguard.config.Peer
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.util.extensions.joinAndTrim
data class PeerProxy(
@@ -8,7 +9,7 @@ data class PeerProxy(
val preSharedKey: String = "",
val persistentKeepalive: String = "",
val endpoint: String = "",
val allowedIps: String = ALL_IPS.joinAndTrim(),
val allowedIps: String = TunnelConfig.ALL_IPS.joinAndTrim(),
) {
fun toWgPeer(): Peer {
return Peer.Builder().apply {
@@ -30,18 +31,18 @@ data class PeerProxy(
}
fun isLanExcluded(): Boolean {
return this.allowedIps.contains(IPV4_PUBLIC_NETWORKS.joinAndTrim())
return this.allowedIps.contains(TunnelConfig.LAN_BYPASS_ALLOWED_IPS.joinAndTrim())
}
fun includeLan(): PeerProxy {
return this.copy(
allowedIps = ALL_IPS.joinAndTrim(),
allowedIps = TunnelConfig.ALL_IPS.joinAndTrim(),
)
}
fun excludeLan(): PeerProxy {
return this.copy(
allowedIps = IPV4_PUBLIC_NETWORKS.joinAndTrim(),
allowedIps = TunnelConfig.LAN_BYPASS_ALLOWED_IPS.joinAndTrim(),
)
}
@@ -95,40 +96,5 @@ data class PeerProxy(
allowedIps = peer.allowedIps.joinToString(", ").trim(),
)
}
val IPV4_PUBLIC_NETWORKS =
setOf(
"0.0.0.0/5",
"8.0.0.0/7",
"11.0.0.0/8",
"12.0.0.0/6",
"16.0.0.0/4",
"32.0.0.0/3",
"64.0.0.0/2",
"128.0.0.0/3",
"160.0.0.0/5",
"168.0.0.0/6",
"172.0.0.0/12",
"172.32.0.0/11",
"172.64.0.0/10",
"172.128.0.0/9",
"173.0.0.0/8",
"174.0.0.0/7",
"176.0.0.0/4",
"192.0.0.0/9",
"192.128.0.0/11",
"192.160.0.0/13",
"192.169.0.0/16",
"192.170.0.0/15",
"192.172.0.0/14",
"192.176.0.0/12",
"192.192.0.0/10",
"193.0.0.0/8",
"194.0.0.0/7",
"196.0.0.0/6",
"200.0.0.0/5",
"208.0.0.0/4",
)
val ALL_IPS = setOf("0.0.0.0/0", "::/0")
}
}
@@ -87,8 +87,8 @@ fun Context.launchNotificationSettings() {
fun Context.launchShareFile(file: Uri) {
val shareIntent = Intent().apply {
setAction(Intent.ACTION_SEND)
setType("*/*")
action = Intent.ACTION_SEND
type = "*/*"
putExtra(Intent.EXTRA_STREAM, file)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
@@ -146,7 +146,7 @@ fun Context.launchVpnSettings(): Result<Unit> {
fun Context.launchLocationServicesSettings(): Result<Unit> {
return kotlin.runCatching {
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS).apply {
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
startActivity(intent)
}
@@ -155,7 +155,7 @@ fun Context.launchLocationServicesSettings(): Result<Unit> {
fun Context.launchSettings(): Result<Unit> {
return kotlin.runCatching {
val intent = Intent(Settings.ACTION_SETTINGS).apply {
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
startActivity(intent)
}
@@ -165,7 +165,7 @@ fun Context.launchAppSettings() {
kotlin.runCatching {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", packageName, null)
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
startActivity(intent)
}
+125 -1
View File
@@ -1,2 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
<resources>
<string name="optional">(valinnainen)</string>
<string name="exclude">Poissulje</string>
<string name="open_issue">Raportoi ongelmasta</string>
<string name="stop">pysäytä</string>
<string name="no_email_detected">Sähköpostisovellusta ei tunnistettu</string>
<string name="allowed_ips">Sallitut IP-osoitteet</string>
<string name="create_import">Luo tyhjästä</string>
<string name="appearance">Ulkoasu</string>
<string name="tunnel_specific_settings">Tunnelikohtaiset asetukset</string>
<string name="dns_servers">DNS-palvelimet</string>
<string name="background_location_message2">varmistaaksesi, että käyttöoikeudet ovat myönnetty</string>
<string name="app_name">WG Tunnel</string>
<string name="vpn_channel_name">VPN-ilmoituskanava</string>
<string name="delete_tunnel_message">Haluatko varmasti poistaa tämän tunnelin?</string>
<string name="no_tunnels">Ei tunneleita määriteltynä!</string>
<string name="tunnels">Tunnelit</string>
<string name="privacy_policy">Näytä tietosuojakäytäntö</string>
<string name="okay">Okei</string>
<string name="tunnel_on_ethernet">Tunneloi ethernetissä</string>
<string name="thank_you">Kiitos WG Tunnelin käyttämisestä!</string>
<string name="trusted_ssid_value_description">Tallenna SSID</string>
<string name="add_tunnels_text">Lisää tiedostosta tai zip-arkistosta</string>
<string name="open_file">Avaa tiedosto</string>
<string name="add_from_qr">Lisää QR-koodilla</string>
<string name="config_changes_saved">Asetusten muutokset tallennettu.</string>
<string name="public_key">Julkinen avain</string>
<string name="addresses">Osoitteet</string>
<string name="name">Nimi</string>
<string name="always_on_vpn_support">Sall aina käytössä oleva VPN</string>
<string name="location_services_not_detected">Sijaintipalveluita ei havaittu</string>
<string name="auto_tunneling">Automaattinen tunnelointi</string>
<string name="vpn_on">VPN päällä</string>
<string name="vpn_off">VPN pois päältä</string>
<string name="turn_on_tunnel">Toiminto vaatii aktiivisen tunnelin</string>
<string name="private_key">Yksityinen avain</string>
<string name="copy_public_key">Kopioi julkinen avain</string>
<string name="base64_key">base64-avain</string>
<string name="comma_separated_list">pilkulla eroteltu lista</string>
<string name="random">(satunnainen)</string>
<string name="unknown_error">Tapahtui tuntematon virhe</string>
<string name="tunnel_on_wifi">Tunneloi epäluotettavilla wifi-yhteyksillä</string>
<string name="email_subject">WG Tunnel-tuki</string>
<string name="email_chooser">Lähetä sähköposti…</string>
<string name="docs_description">Lue dokumentaatio</string>
<string name="email_description">Lähetä minulle sähköpostia</string>
<string name="error_ssid_exists">SSID on jo luettelossa</string>
<string name="error_no_file_explorer">Tiedostonhallintasovellusta ei ole asennettuna</string>
<string name="error_invalid_code">Virheellinen QR-koodi</string>
<string name="location_services_missing_message">Sovellus ei tunnista laitteessasi käytössä olevia sijaintipalveluja. Laitteesta riippuen tämä voi aiheuttaa sen, että epäluotettava wlan-ominaisuus ei pysty lukemaan wlan-nimeä. Haluatko kuitenkin jatkaa?</string>
<string name="auto_tunnel_title">Automaattinen tunnelointipalvelu</string>
<string name="delete_tunnel">Poista tunneli</string>
<string name="yes">Kyllä</string>
<string name="auto">(auto)</string>
<string name="incorrect_pin">Pin on virheellinen</string>
<string name="pin_created">Pin luotu</string>
<string name="enter_pin">Syötä pin-koodi</string>
<string name="create_pin">Luo pin-koodi</string>
<string name="enable_app_lock">Ota käyttöön sovelluksen lukitus</string>
<string name="set_primary_tunnel">Aseta ensisijaiseksi tunneliksi</string>
<string name="edit_tunnel">Muokkaa tunnelia</string>
<string name="version">Versio</string>
<string name="settings">Asetukset</string>
<string name="support">Tuki</string>
<string name="unsure_how">jos et tiedä, miten jatkaa</string>
<string name="see_the">katso</string>
<string name="getting_started_guide">aloitusopas</string>
<string name="error_file_format">Virheellinen tunneliasetusten tiedostomuoto</string>
<string name="vpn_denied_dialog_title">Ei käyttöoikeutta</string>
<string name="vpn_settings">VPN järjestelmäasetukset</string>
<string name="always_on_message">VPN-yhteyden käyttöoikeus on evätty. Tarkista</string>
<string name="chat_description">Liity yhteisöön</string>
<string name="tunnel_required">Ominaisuus vaatii vähintään yhden tunnelin</string>
<string name="prominent_background_location_message">Tämä toiminto vaatii taustapaikannusoikeuden, jotta Wi-Fi SSID:n seuranta on mahdollista myös sovelluksen ollessa suljettuna. Lisätietoja löydät Tuki-näkymään linkatusta tietosuojakäytännöstä.</string>
<string name="optional_default">"valinnainen, oletus: "</string>
<string name="donate">Lahjoita projektille</string>
<string name="requires_app_relaunch">Muutos edellyttää sovelluksen uudelleenkäynnistämistä. Haluatko jatkaa?</string>
<string name="add_from_clipboard">Lisää leikepöydältä</string>
<string name="ethernet_tunnel">Ethernet-tunneli</string>
<string name="enable_amnezia">Ota Amnezia käyttöön</string>
<string name="wg_compat_mode">WG-yhteensopivuustila</string>
<string name="quick_actions">Pikatoiminnot</string>
<string name="advanced_settings">Lisäasetukset</string>
<string name="hide_scripts">Piilota skriptit</string>
<string name="qr_scan">QR-skannaus</string>
<string name="notifications">Ilmoitukset</string>
<string name="error_file_extension">Tiedosto ei ole .conf tai .zip-tiedosto</string>
<string name="tunnel_mobile_data">Tunneloi mobiilidatalla</string>
<string name="mtu">MTU</string>
<string name="optional_no_recommend">(valinnainen, ei suositeltu)</string>
<string name="preshared_key">Esijaettu avain</string>
<string name="all">kaikki</string>
<string name="never">ei koskaan</string>
<string name="logs">Lokitiedot</string>
<string name="set_ethernet_tunnel">Aseta ethernet-tunneliksi</string>
<string name="seconds">sekuntia</string>
<string name="cancel">Peruuta</string>
<string name="export_configs">Vie asetukset</string>
<string name="no_browser_detected">Selainta ei tunnistettu</string>
<string name="handshake">kädenpuristus</string>
<string name="light">Vaalea</string>
<string name="dark">Tumma</string>
<string name="trusted_wifi_names">Luotetut WIFI:t</string>
<string name="vpn_channel_description">Kanava VPN-tilan ilmoituksille</string>
<string name="dynamic">Dynaaminen</string>
<string name="primary_tunnel">Ensisijainen tunneli</string>
<string name="learn_more">Lue lisää</string>
<string name="show_scripts">Näytä skriptit</string>
<string name="start_auto">Käynnistä automaattinen tunnelointi</string>
<string name="stop_auto">Kytke automaattinen tunnelointi pois päältä</string>
<string name="allow_lan_traffic">Salli LAN-tietoliikenne</string>
<string name="turn_off_tunnel">Toiminto vaatii, että tunneli on pois päältä</string>
<string name="error_authentication_failed">Tunnistautuminen epäonnistui</string>
<string name="display_theme">Teema</string>
<string name="skip">Ohita</string>
<string name="add_wifi_name">Lisää WIFI:n nimi</string>
<string name="language">Kieli</string>
<string name="include">Sisällytä</string>
<string name="enabled_app_shortcuts">Salli sovelluksen pikakuvakkeet</string>
<string name="automatic">Automaattinen</string>
<string name="tunnel_name">Tunnelin nimi</string>
<string name="sec">sek</string>
<string name="read_logs">Lue lokitiedot</string>
<string name="mobile_tunnel">Mobiilidatatunneli</string>
</resources>
+28 -4
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="turn_off_tunnel">Cette action nécessite la désactivation du tunnel</string>
<string name="turn_off_tunnel">Cette action nécessite la désactivation du tunnel</string>
<string name="no_tunnels">Aucun tunnel n\'a été ajouté pour le moment!</string>
<string name="tunnels">Tunnels</string>
<string name="tunnel_mobile_data">Tunnel sur données mobiles</string>
@@ -38,7 +38,7 @@
<string name="config_changes_saved">Changements de la configuration sauvegardés.</string>
<string name="public_key">Clé publique</string>
<string name="addresses">Adresses</string>
<string name="vpn_on">VPN allumé</string>
<string name="vpn_on">VPN allumé</string>
<string name="create_import">Créer à partir de zéro</string>
<string name="private_key">Clé privée</string>
<string name="interface_">Interface</string>
@@ -90,7 +90,7 @@
<string name="vpn_settings">paramètres système des VPN</string>
<string name="tunnel_required">Cette fonctionnalité nécessite au moins un tunnel</string>
<string name="app_settings">les réglages de l\'application</string>
<string name="background_location_message2">afin de s\'assurer que ces permissions soient actives.</string>
<string name="background_location_message2">afin de s\'assurer que ces permissions soient actives</string>
<string name="root_accepted">Accès au shell root autorisé</string>
<string name="set_custom_ping_ip">Personnaliser l\'ip à pinguer</string>
<string name="optional_default">"optionnel, par défaut : "</string>
@@ -160,4 +160,28 @@
<string name="kernel_not_supported">Noyau non supporté</string>
<string name="start_auto">Démarrer l\'auto-tunnel</string>
<string name="requires_app_relaunch">Cette modification nécessite un redémarrage de l\'application. Voulez-vous continuer?</string>
</resources>
<string name="exclude_lan">Exclure le LAN</string>
<string name="tunnel_specific_settings">Réglages spécifiques du tunnel</string>
<string name="show_scripts">Voir les scripts</string>
<string name="hide_scripts">Cacher les scripts</string>
<string name="remove_amnezia_compatibility">Retirer la prise en charge d\'Amnezia</string>
<string name="hide_amnezia_properties">Cacher les propriétés d\'Amnezia</string>
<string name="advanced_settings">Réglages avancés</string>
<string name="stop_on_no_internet">Arrêt en l\'absence d\'internet</string>
<string name="stop_on_internet_loss">Couper les tunnels en l\'absence d\'internet</string>
<string name="ethernet_tunnel">Tunnel ethernet</string>
<string name="set_ethernet_tunnel">Définir comme tunnel ethernet</string>
<string name="native_kill_switch">Arrêt d\'urgence natif</string>
<string name="vpn_kill_switch">Arrêt d\'urgence VPN</string>
<string name="kill_switch_options">Options d\'arrêt d\'urgence</string>
<string name="allow_lan_traffic">Autoriser le trafic LAN</string>
<string name="bypass_lan_for_kill_switch">Contourner le LAN en cas d\'arêt d\'urgence</string>
<string name="stop">arrêter</string>
<string name="splt_tunneling">Tunnel partagé</string>
<string name="amnezia_kernel_message">Amnezia n\'est pas disponible en mode noyau</string>
<string name="enable_amnezia">Activer Amnezia</string>
<string name="wg_compat_mode">Mode de compatibilité WG</string>
<string name="quick_actions">Actions rapides</string>
<string name="enable_amnezia_compatibility">Activer la prise en charge d\'Amnezia</string>
<string name="include_lan">Inclure le LAN</string>
</resources>
+4 -1
View File
@@ -193,4 +193,7 @@
<string name="include_lan">Uwzględnij LAN</string>
<string name="advanced_settings">Ustawienia zaawansowane</string>
<string name="hide_amnezia_properties">Ukryj właściwości protokołu Amnezia</string>
</resources>
<string name="error_tunnel_start">Nie udało się uruchomić tunelu</string>
<string name="tunnel_control">Kontrola tunelu</string>
<string name="auto_tunnel">Autotunel</string>
</resources>
+56 -2
View File
@@ -102,7 +102,7 @@
<string name="see_the">Veja o</string>
<string name="getting_started_guide">guia de início rápido</string>
<string name="error_file_format">Formato de configuração inválido</string>
<string name="vpn_channel_name">Canal de notificações VPN</string>
<string name="vpn_channel_name">Canal de notificações VPN</string>
<string name="set_custom_ping_ip">Definir ip ping personalizado</string>
<string name="vpn_denied_dialog_title">Permissão negada</string>
<string name="vpn_settings">Configurações do sistema VPN</string>
@@ -120,4 +120,58 @@
<string name="handshake">handshake</string>
<string name="background_location_message">Permitir que toda a permissão de localização do tempo e/ou localização precisa é necessária para este recurso. Por favor, veja</string>
<string name="sec">seg</string>
</resources>
<string name="notifications">Notificações</string>
<string name="exclude_lan">Excluir LAN</string>
<string name="hide_scripts">Ocultar scripts</string>
<string name="trusted_wifi_names">Nomes de Wi-Fi confiáveis</string>
<string name="hide_amnezia_properties">Ocultar propriedades Amnezia</string>
<string name="remove_amnezia_compatibility">Remover compatibilidade Amnezia</string>
<string name="include_lan">Incluir LAN</string>
<string name="language">Idioma</string>
<string name="add_wifi_name">Adicionar nome Wi-Fi</string>
<string name="display_theme">Tema</string>
<string name="on_demand_rules">Regras de tunelamento sob demanda</string>
<string name="dark">Escuro</string>
<string name="dynamic">Dinâmico</string>
<string name="skip">Pular</string>
<string name="mobile_tunnel">Túnel com dados móveis</string>
<string name="requires_app_relaunch">Para aplicar as mudanças é necessário reiniciar o aplicativo. Deseja prosseguir ?</string>
<string name="add_from_clipboard">Adicionar da área de transferência</string>
<string name="restart_at_boot">Ativar na inicialização</string>
<string name="appearance">Aparência</string>
<string name="automatic">Automático</string>
<string name="light">Claro</string>
<string name="wildcards_active">Wildcards ativos</string>
<string name="learn_more">Saber mais</string>
<string name="use_wildcards">Usar nomes coringas</string>
<string name="wifi_name_via_shell">Nome do Wi-Fi por shell</string>
<string name="use_root_shell_for_wifi">Obter o nome do Wi-Fi através do shell root</string>
<string name="start_auto">Iniciar túnel automático</string>
<string name="stop_auto">Pausar túnel automático</string>
<string name="monitoring_state_changes">Monitorar status de alterações</string>
<string name="tunnel_running">Túnel em execução</string>
<string name="donate">Contribua com esse projeto</string>
<string name="local_logging">Registro local</string>
<string name="enable_local_logging">Ativar registro local</string>
<string name="configuration_change">Configuração alterada</string>
<string name="stop_on_no_internet">Interromper quando não há internet</string>
<string name="stop_on_internet_loss">Interrompa o túnel quando a internet não estiver disponível</string>
<string name="ethernet_tunnel">Túnel ethernet</string>
<string name="set_ethernet_tunnel">Definir como túnel ethernet</string>
<string name="native_kill_switch">Interruptor de desligamento padrão</string>
<string name="vpn_kill_switch">Interruptor de desligamento VPN</string>
<string name="kill_switch_options">Opções do interruptor de desligamento</string>
<string name="allow_lan_traffic">Permitir tráfego LAN</string>
<string name="bypass_lan_for_kill_switch">Ignorar LAN no interruptor de desligamento</string>
<string name="splt_tunneling">Tunelamento dividido</string>
<string name="stop">pausar</string>
<string name="tunnel_specific_settings">Configurações específicas no túnel</string>
<string name="show_scripts">Mostrar scripts</string>
<string name="amnezia_kernel_message">Amnezia indisponível no kernel</string>
<string name="enable_amnezia">Ativar Amnezia</string>
<string name="wg_compat_mode">Modo de compatibilidade WG</string>
<string name="quick_actions">Ações rápidas</string>
<string name="kernel_not_supported">Kernel não suportado</string>
<string name="advanced_settings">Configurações avançadas</string>
<string name="enable_amnezia_compatibility">Ativar compatibilidade Amnezia</string>
</resources>
+4 -4
View File
@@ -173,8 +173,8 @@
<string name="native_kill_switch">Штатное экстренное отключение</string>
<string name="vpn_kill_switch">Экстренное отключение VPN</string>
<string name="kill_switch_options">Настройка экстренного отключения</string>
<string name="allow_lan_traffic">Разрешать трафик LAN</string>
<string name="bypass_lan_for_kill_switch">Обход LAN при экстренном отключении</string>
<string name="allow_lan_traffic">Обход LAN</string>
<string name="bypass_lan_for_kill_switch">Разрешать трафик LAN при экстренном отключении</string>
<string name="vpn_channel_description">Канал уведомлений о состоянии VPN</string>
<string name="auto_tunnel_channel_description">Канал уведомлений о состоянии автотуннеля</string>
<string name="stop">стоп</string>
@@ -186,10 +186,10 @@
<string name="post_down">После деактивации</string>
<string name="debounce_delay">Задержка отбоя</string>
<string name="remove_amnezia_compatibility">Отключить совместимость с Amnezia</string>
<string name="exclude_lan">Исключить локальную сеть</string>
<string name="exclude_lan">Исключить LAN</string>
<string name="hide_scripts">Скрыть сценарии</string>
<string name="hide_amnezia_properties">Скрыть настройки Amnezia</string>
<string name="advanced_settings">Дополнительные настройки</string>
<string name="enable_amnezia_compatibility">Включить совместимость с Amnezia</string>
<string name="include_lan">Включить локальную сеть</string>
<string name="include_lan">Включить LAN</string>
</resources>
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
+3
View File
@@ -203,4 +203,7 @@
<string name="exclude_lan">Exclude LAN</string>
<string name="include_lan">Include LAN</string>
<string name="error_tunnel_start">Failed to starting tunnel</string>
<string name="tunnel_control">Tunnel control</string>
<string name="auto_tunnel">Auto-tunnel</string>
<string name="kill_switch_off">Stop kill switch on trusted</string>
</resources>
@@ -0,0 +1,6 @@
Novedades:
- La función Ping ahora funciona independientemente del túnel automático
- Añadida acción para compatibilidad con Amnezia
- Añadida acción para excluir LAN del túnel
- Añadida la opción de ajuste del retardo de rebote para el túnel automático.
- Numerosas correcciones de errores y mejoras
@@ -0,0 +1 @@
Vaihtoehtoinen WireGuard-asiakassovellus lisäominaisuuksilla
@@ -0,0 +1 @@
WG Tunnel
@@ -0,0 +1,6 @@
Co nowego:
- Funkcja ping działa teraz niezależnie od autotunelowania
- Dodano wygodną czynność dla zgodności z protokołem Amnezia
- Dodano wygodną czynność dla wykluczenia sieci LAN z tunelowania
- Dodano opcję dostrajania opóźnienia odbicia dla autotunelowania
- Wiele poprawek błędów i ulepszeń
@@ -0,0 +1 @@
Uma alternativa de cliente WireGuard VPN com recursos adicionais
@@ -0,0 +1,6 @@
Что нового:
- Функция пинга теперь работает независимо от автоматического туннелирования
- Функционал совместимости с Amnezia стал удобнее
- Добавлен функционал исключения локальной сети из туннеля
- Добавлен параметр задержки отбоя для автоматического туннелирования
- Множество исправлений ошибок и улучшений
@@ -0,0 +1,3 @@
強化項目:
- 修正 Android 9 以下版本的權限問題
- 其他優化
@@ -0,0 +1,14 @@
特色功能
- 可透過 .conf 檔、zip 壓縮檔、手動輸入及掃瞄 QRCode 新增通道
- 根據 Wi-Fi SSID、乙太網路或行動網路自動連線至 VPN
- 為各別應用程式設定是否使用分割通道(支援搜尋功能)
- 支援 WireGuard 內核模式與使用者空間模式
- 支援 Amnezia 使用者空間模式,以應對 DPI 及審查保護
- 支援隨時保持 VPN 連線
- 支援將 Amnezia 及 WireGuard 通道匯出為 zip 檔
- 支援以快速設定方塊切換 VPN
- 支援主要通道靜態捷徑,便於自動化整合
- 所有通道皆支援 Intent 自動化
- 裝置重新啟動後自動重新啟動服務
- 耗電節約措施
@@ -0,0 +1 @@
一款具備額外功能的 WireGuard VPN 客戶端應用程式
@@ -0,0 +1 @@
WG Tunnel
+8 -9
View File
@@ -1,18 +1,18 @@
[versions]
accompanist = "0.36.0"
activityCompose = "1.9.3"
accompanist = "0.37.0"
activityCompose = "1.10.0"
amneziawgAndroid = "1.2.4"
androidx-junit = "1.2.1"
appcompat = "1.7.0"
biometricKtx = "1.2.0-alpha05"
coreKtx = "1.15.0"
datastorePreferences = "1.1.1"
datastorePreferences = "1.1.2"
desugar_jdk_libs = "2.1.4"
espressoCore = "3.6.1"
hiltAndroid = "2.54"
hiltAndroid = "2.55"
hiltNavigationCompose = "1.2.0"
junit = "4.13.2"
kotlinx-serialization-json = "1.7.3"
kotlinx-serialization-json = "1.8.0"
lifecycle-runtime-compose = "2.8.7"
material3 = "1.3.1"
navigationCompose = "2.8.5"
@@ -20,11 +20,11 @@ pinLockCompose = "1.0.4"
roomVersion = "2.6.1"
timber = "5.0.1"
tunnel = "1.2.1"
androidGradlePlugin = "8.8.0-rc02"
androidGradlePlugin = "8.8.0"
kotlin = "2.1.0"
ksp = "2.1.0-1.0.29"
composeBom = "2024.12.01"
compose = "1.7.6"
composeBom = "2025.01.00"
compose = "1.7.7"
zxingAndroidEmbedded = "4.3.0"
coreSplashscreen = "1.0.1"
gradlePlugins-grgit = "5.3.0"
@@ -38,7 +38,6 @@ gradlePlugins-ktlint="12.1.2"
# accompanist
accompanist-drawablepainter = { module = "com.google.accompanist:accompanist-drawablepainter", version.ref = "accompanist" }
accompanist-flowlayout = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanist" }
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" }
#room