parent
0784c96011
commit
d3ea75869a
|
@ -0,0 +1,232 @@
|
||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 11,
|
||||||
|
"identityHash": "4c9418386f72dfac5d28ab96c1e5ea0b",
|
||||||
|
"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)",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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)",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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, '4c9418386f72dfac5d28ab96c1e5ea0b')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -186,10 +186,6 @@
|
||||||
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver
|
|
||||||
android:name=".receiver.BackgroundActionReceiver"
|
|
||||||
android:enabled="true"
|
|
||||||
android:exported="false"/>
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".receiver.AppUpdateReceiver"
|
android:name=".receiver.AppUpdateReceiver"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
|
|
|
@ -11,7 +11,7 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [Settings::class, TunnelConfig::class],
|
entities = [Settings::class, TunnelConfig::class],
|
||||||
version = 10,
|
version = 11,
|
||||||
autoMigrations =
|
autoMigrations =
|
||||||
[
|
[
|
||||||
AutoMigration(from = 1, to = 2),
|
AutoMigration(from = 1, to = 2),
|
||||||
|
@ -36,6 +36,11 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
AutoMigration(7, 8),
|
AutoMigration(7, 8),
|
||||||
AutoMigration(8, 9),
|
AutoMigration(8, 9),
|
||||||
AutoMigration(9, 10),
|
AutoMigration(9, 10),
|
||||||
|
AutoMigration(
|
||||||
|
from = 10,
|
||||||
|
to = 11,
|
||||||
|
spec = RemoveTunnelPauseMigration::class,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
exportSchema = true,
|
exportSchema = true,
|
||||||
)
|
)
|
||||||
|
@ -55,3 +60,9 @@ abstract class AppDatabase : RoomDatabase() {
|
||||||
columnName = "is_battery_saver_enabled",
|
columnName = "is_battery_saver_enabled",
|
||||||
)
|
)
|
||||||
class RemoveLegacySettingColumnsMigration : AutoMigrationSpec
|
class RemoveLegacySettingColumnsMigration : AutoMigrationSpec
|
||||||
|
|
||||||
|
@DeleteColumn(
|
||||||
|
tableName = "Settings",
|
||||||
|
columnName = "is_auto_tunnel_paused",
|
||||||
|
)
|
||||||
|
class RemoveTunnelPauseMigration : AutoMigrationSpec
|
||||||
|
|
|
@ -26,7 +26,6 @@ class DataStoreManager(
|
||||||
val currentSSID = stringPreferencesKey("CURRENT_SSID")
|
val currentSSID = stringPreferencesKey("CURRENT_SSID")
|
||||||
val pinLockEnabled = booleanPreferencesKey("PIN_LOCK_ENABLED")
|
val pinLockEnabled = booleanPreferencesKey("PIN_LOCK_ENABLED")
|
||||||
val tunnelStatsExpanded = booleanPreferencesKey("TUNNEL_STATS_EXPANDED")
|
val tunnelStatsExpanded = booleanPreferencesKey("TUNNEL_STATS_EXPANDED")
|
||||||
val wildcardsEnabled = booleanPreferencesKey("WILDCARDS_ENABLED")
|
|
||||||
val theme = stringPreferencesKey("THEME")
|
val theme = stringPreferencesKey("THEME")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,14 +7,12 @@ data class GeneralState(
|
||||||
val isBatteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
|
val isBatteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
|
||||||
val isPinLockEnabled: Boolean = PIN_LOCK_ENABLED_DEFAULT,
|
val isPinLockEnabled: Boolean = PIN_LOCK_ENABLED_DEFAULT,
|
||||||
val isTunnelStatsExpanded: Boolean = IS_TUNNEL_STATS_EXPANDED,
|
val isTunnelStatsExpanded: Boolean = IS_TUNNEL_STATS_EXPANDED,
|
||||||
val isWildcardsEnabled: Boolean = IS_WILDCARDS_ENABLED,
|
val theme: Theme = Theme.AUTOMATIC,
|
||||||
val theme: Theme = Theme.AUTOMATIC
|
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val LOCATION_DISCLOSURE_SHOWN_DEFAULT = false
|
const val LOCATION_DISCLOSURE_SHOWN_DEFAULT = false
|
||||||
const val BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT = false
|
const val BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT = false
|
||||||
const val PIN_LOCK_ENABLED_DEFAULT = false
|
const val PIN_LOCK_ENABLED_DEFAULT = false
|
||||||
const val IS_TUNNEL_STATS_EXPANDED = false
|
const val IS_TUNNEL_STATS_EXPANDED = false
|
||||||
const val IS_WILDCARDS_ENABLED = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,11 +40,6 @@ data class Settings(
|
||||||
defaultValue = "false",
|
defaultValue = "false",
|
||||||
)
|
)
|
||||||
val isMultiTunnelEnabled: Boolean = false,
|
val isMultiTunnelEnabled: Boolean = false,
|
||||||
@ColumnInfo(
|
|
||||||
name = "is_auto_tunnel_paused",
|
|
||||||
defaultValue = "false",
|
|
||||||
)
|
|
||||||
val isAutoTunnelPaused: Boolean = false,
|
|
||||||
@ColumnInfo(
|
@ColumnInfo(
|
||||||
name = "is_ping_enabled",
|
name = "is_ping_enabled",
|
||||||
defaultValue = "false",
|
defaultValue = "false",
|
||||||
|
@ -55,4 +50,14 @@ data class Settings(
|
||||||
defaultValue = "false",
|
defaultValue = "false",
|
||||||
)
|
)
|
||||||
val isAmneziaEnabled: Boolean = false,
|
val isAmneziaEnabled: Boolean = false,
|
||||||
|
@ColumnInfo(
|
||||||
|
name = "is_wildcards_enabled",
|
||||||
|
defaultValue = "false",
|
||||||
|
)
|
||||||
|
val isWildcardsEnabled: Boolean = false,
|
||||||
|
@ColumnInfo(
|
||||||
|
name = "is_wifi_by_shell_enabled",
|
||||||
|
defaultValue = "false",
|
||||||
|
)
|
||||||
|
val isWifiNameByShellEnabled: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
|
@ -13,10 +13,6 @@ interface AppStateRepository {
|
||||||
|
|
||||||
suspend fun setPinLockEnabled(enabled: Boolean)
|
suspend fun setPinLockEnabled(enabled: Boolean)
|
||||||
|
|
||||||
suspend fun isWildcardsEnabled(): Boolean
|
|
||||||
|
|
||||||
suspend fun setWildcardsEnabled(enabled: Boolean)
|
|
||||||
|
|
||||||
suspend fun isBatteryOptimizationDisableShown(): Boolean
|
suspend fun isBatteryOptimizationDisableShown(): Boolean
|
||||||
|
|
||||||
suspend fun setBatteryOptimizationDisableShown(shown: Boolean)
|
suspend fun setBatteryOptimizationDisableShown(shown: Boolean)
|
||||||
|
|
|
@ -29,14 +29,6 @@ class DataStoreAppStateRepository(
|
||||||
dataStoreManager.saveToDataStore(DataStoreManager.pinLockEnabled, enabled)
|
dataStoreManager.saveToDataStore(DataStoreManager.pinLockEnabled, enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun isWildcardsEnabled(): Boolean {
|
|
||||||
return dataStoreManager.getFromStore(DataStoreManager.wildcardsEnabled) ?: GeneralState.IS_WILDCARDS_ENABLED
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun setWildcardsEnabled(enabled: Boolean) {
|
|
||||||
dataStoreManager.saveToDataStore(DataStoreManager.wildcardsEnabled, enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun isBatteryOptimizationDisableShown(): Boolean {
|
override suspend fun isBatteryOptimizationDisableShown(): Boolean {
|
||||||
return dataStoreManager.getFromStore(DataStoreManager.batteryDisableShown)
|
return dataStoreManager.getFromStore(DataStoreManager.batteryDisableShown)
|
||||||
?: GeneralState.BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT
|
?: GeneralState.BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT
|
||||||
|
@ -92,8 +84,7 @@ class DataStoreAppStateRepository(
|
||||||
pref[DataStoreManager.pinLockEnabled]
|
pref[DataStoreManager.pinLockEnabled]
|
||||||
?: GeneralState.PIN_LOCK_ENABLED_DEFAULT,
|
?: GeneralState.PIN_LOCK_ENABLED_DEFAULT,
|
||||||
isTunnelStatsExpanded = pref[DataStoreManager.tunnelStatsExpanded] ?: GeneralState.IS_TUNNEL_STATS_EXPANDED,
|
isTunnelStatsExpanded = pref[DataStoreManager.tunnelStatsExpanded] ?: GeneralState.IS_TUNNEL_STATS_EXPANDED,
|
||||||
isWildcardsEnabled = pref[DataStoreManager.wildcardsEnabled] ?: GeneralState.IS_WILDCARDS_ENABLED,
|
theme = getTheme(),
|
||||||
theme = getTheme()
|
|
||||||
)
|
)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.data.repository
|
package com.zaneschepke.wireguardautotunnel.data.repository
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
|
import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
@ -26,8 +24,6 @@ class RoomTunnelConfigRepository(
|
||||||
override suspend fun save(tunnelConfig: TunnelConfig) {
|
override suspend fun save(tunnelConfig: TunnelConfig) {
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
tunnelConfigDao.save(tunnelConfig)
|
tunnelConfigDao.save(tunnelConfig)
|
||||||
}.also {
|
|
||||||
WireGuardAutoTunnel.instance.requestTunnelTileServiceStateUpdate()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,8 +56,6 @@ class RoomTunnelConfigRepository(
|
||||||
override suspend fun delete(tunnelConfig: TunnelConfig) {
|
override suspend fun delete(tunnelConfig: TunnelConfig) {
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
tunnelConfigDao.delete(tunnelConfig)
|
tunnelConfigDao.delete(tunnelConfig)
|
||||||
}.also {
|
|
||||||
WireGuardAutoTunnel.instance.requestTunnelTileServiceStateUpdate()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.wireguard.android.util.RootShell
|
||||||
import com.wireguard.android.util.ToolsInstaller
|
import com.wireguard.android.util.ToolsInstaller
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.WireGuardTunnel
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.WireGuardTunnel
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
@ -65,6 +66,7 @@ class TunnelModule {
|
||||||
tunnelConfigRepository: TunnelConfigRepository,
|
tunnelConfigRepository: TunnelConfigRepository,
|
||||||
@ApplicationScope applicationScope: CoroutineScope,
|
@ApplicationScope applicationScope: CoroutineScope,
|
||||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||||
|
serviceManager: ServiceManager,
|
||||||
): TunnelService {
|
): TunnelService {
|
||||||
return WireGuardTunnel(
|
return WireGuardTunnel(
|
||||||
amneziaBackend,
|
amneziaBackend,
|
||||||
|
@ -73,6 +75,13 @@ class TunnelModule {
|
||||||
appDataRepository,
|
appDataRepository,
|
||||||
applicationScope,
|
applicationScope,
|
||||||
ioDispatcher,
|
ioDispatcher,
|
||||||
|
serviceManager,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideServiceManager(@ApplicationContext context: Context): ServiceManager {
|
||||||
|
return ServiceManager(context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,12 @@ import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.startTunnelBackground
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Provider
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class AppUpdateReceiver : BroadcastReceiver() {
|
class AppUpdateReceiver : BroadcastReceiver() {
|
||||||
|
@ -25,7 +25,10 @@ class AppUpdateReceiver : BroadcastReceiver() {
|
||||||
lateinit var appDataRepository: AppDataRepository
|
lateinit var appDataRepository: AppDataRepository
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var tunnelService: TunnelService
|
lateinit var tunnelService: Provider<TunnelService>
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
if (intent.action != Intent.ACTION_MY_PACKAGE_REPLACED) return
|
if (intent.action != Intent.ACTION_MY_PACKAGE_REPLACED) return
|
||||||
|
@ -33,11 +36,11 @@ class AppUpdateReceiver : BroadcastReceiver() {
|
||||||
val settings = appDataRepository.settings.getSettings()
|
val settings = appDataRepository.settings.getSettings()
|
||||||
if (settings.isAutoTunnelEnabled) {
|
if (settings.isAutoTunnelEnabled) {
|
||||||
Timber.i("Restarting services after upgrade")
|
Timber.i("Restarting services after upgrade")
|
||||||
ServiceManager.startWatcherServiceForeground(context)
|
serviceManager.startAutoTunnel(true)
|
||||||
}
|
}
|
||||||
if (!settings.isAutoTunnelEnabled || settings.isAutoTunnelPaused) {
|
if (!settings.isAutoTunnelEnabled) {
|
||||||
val tunnels = appDataRepository.tunnels.getAll().filter { it.isActive }
|
val tunnels = appDataRepository.tunnels.getAll().filter { it.isActive }
|
||||||
if (tunnels.isNotEmpty()) context.startTunnelBackground(tunnels.first().id)
|
if (tunnels.isNotEmpty()) tunnelService.get().startTunnel(tunnels.first(), true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.receiver
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
|
||||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Provider
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class BackgroundActionReceiver : BroadcastReceiver() {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
@ApplicationScope
|
|
||||||
lateinit var applicationScope: CoroutineScope
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var tunnelService: Provider<TunnelService>
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var tunnelConfigRepository: TunnelConfigRepository
|
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
|
||||||
val id = intent.getIntExtra(TUNNEL_ID_EXTRA_KEY, 0)
|
|
||||||
if (id == 0) return
|
|
||||||
when (intent.action) {
|
|
||||||
ACTION_CONNECT -> {
|
|
||||||
Timber.d("Connect actions")
|
|
||||||
applicationScope.launch {
|
|
||||||
val tunnel = tunnelConfigRepository.getById(id)
|
|
||||||
tunnel?.let {
|
|
||||||
ServiceManager.startTunnelBackgroundService(context)
|
|
||||||
tunnelService.get().startTunnel(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ACTION_DISCONNECT -> {
|
|
||||||
applicationScope.launch {
|
|
||||||
val tunnel = tunnelConfigRepository.getById(id)
|
|
||||||
tunnel?.let {
|
|
||||||
ServiceManager.stopTunnelBackgroundService(context)
|
|
||||||
tunnelService.get().stopTunnel(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val ACTION_CONNECT = "ACTION_CONNECT"
|
|
||||||
const val ACTION_DISCONNECT = "ACTION_DISCONNECT"
|
|
||||||
const val TUNNEL_ID_EXTRA_KEY = "tunnelId"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,7 +8,6 @@ import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.startTunnelBackground
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -28,6 +27,9 @@ class BootReceiver : BroadcastReceiver() {
|
||||||
@ApplicationScope
|
@ApplicationScope
|
||||||
lateinit var applicationScope: CoroutineScope
|
lateinit var applicationScope: CoroutineScope
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
if (Intent.ACTION_BOOT_COMPLETED != intent.action) return
|
if (Intent.ACTION_BOOT_COMPLETED != intent.action) return
|
||||||
applicationScope.launch {
|
applicationScope.launch {
|
||||||
|
@ -37,11 +39,11 @@ class BootReceiver : BroadcastReceiver() {
|
||||||
val tunState = tunnelService.get().vpnState.value.status
|
val tunState = tunnelService.get().vpnState.value.status
|
||||||
if (activeTunnels.isNotEmpty() && tunState != TunnelState.UP) {
|
if (activeTunnels.isNotEmpty() && tunState != TunnelState.UP) {
|
||||||
Timber.i("Starting previously active tunnel")
|
Timber.i("Starting previously active tunnel")
|
||||||
context.startTunnelBackground(activeTunnels.first().id)
|
tunnelService.get().startTunnel(activeTunnels.first(), true)
|
||||||
}
|
}
|
||||||
if (isAutoTunnelEnabled) {
|
if (isAutoTunnelEnabled) {
|
||||||
Timber.i("Starting watcher service from boot")
|
Timber.i("Starting watcher service from boot")
|
||||||
ServiceManager.startWatcherServiceForeground(context)
|
serviceManager.startAutoTunnel(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,10 +26,11 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
|
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
|
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isDown
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.onNotRunning
|
import com.zaneschepke.wireguardautotunnel.util.extensions.onNotRunning
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
@ -74,6 +75,9 @@ class AutoTunnelService : LifecycleService() {
|
||||||
@IoDispatcher
|
@IoDispatcher
|
||||||
lateinit var ioDispatcher: CoroutineDispatcher
|
lateinit var ioDispatcher: CoroutineDispatcher
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@MainImmediateDispatcher
|
@MainImmediateDispatcher
|
||||||
lateinit var mainImmediateDispatcher: CoroutineDispatcher
|
lateinit var mainImmediateDispatcher: CoroutineDispatcher
|
||||||
|
@ -88,14 +92,11 @@ class AutoTunnelService : LifecycleService() {
|
||||||
private var pingJob: Job? = null
|
private var pingJob: Job? = null
|
||||||
private var networkEventJob: Job? = null
|
private var networkEventJob: Job? = null
|
||||||
|
|
||||||
@get:Synchronized @set:Synchronized
|
|
||||||
private var running: Boolean = false
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
lifecycleScope.launch(mainImmediateDispatcher) {
|
lifecycleScope.launch(mainImmediateDispatcher) {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
launchNotification()
|
launchWatcherNotification()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Timber.e(it)
|
Timber.e(it)
|
||||||
}
|
}
|
||||||
|
@ -110,32 +111,14 @@ class AutoTunnelService : LifecycleService() {
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
Timber.d("onStartCommand executed with startId: $startId")
|
Timber.d("onStartCommand executed with startId: $startId")
|
||||||
if (intent != null) {
|
serviceManager.autoTunnelService.complete(this)
|
||||||
val action = intent.action
|
|
||||||
when (action) {
|
|
||||||
Action.START.name,
|
|
||||||
Action.START_FOREGROUND.name,
|
|
||||||
-> startService()
|
|
||||||
Action.STOP.name, Action.STOP_FOREGROUND.name -> stopService()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.onStartCommand(intent, flags, startId)
|
return super.onStartCommand(intent, flags, startId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun launchNotification() {
|
fun start() {
|
||||||
if (appDataRepository.settings.getSettings().isAutoTunnelPaused) {
|
|
||||||
launchWatcherPausedNotification()
|
|
||||||
} else {
|
|
||||||
launchWatcherNotification()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startService() {
|
|
||||||
if (running) return
|
|
||||||
running = true
|
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
lifecycleScope.launch(mainImmediateDispatcher) {
|
lifecycleScope.launch(mainImmediateDispatcher) {
|
||||||
launchNotification()
|
launchWatcherNotification()
|
||||||
initWakeLock()
|
initWakeLock()
|
||||||
}
|
}
|
||||||
startSettingsJob()
|
startSettingsJob()
|
||||||
|
@ -145,7 +128,7 @@ class AutoTunnelService : LifecycleService() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopService() {
|
fun stop() {
|
||||||
wakeLock?.let {
|
wakeLock?.let {
|
||||||
if (it.isHeld) {
|
if (it.isHeld) {
|
||||||
it.release()
|
it.release()
|
||||||
|
@ -157,6 +140,7 @@ class AutoTunnelService : LifecycleService() {
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
cancelAndResetNetworkJobs()
|
cancelAndResetNetworkJobs()
|
||||||
cancelAndResetPingJob()
|
cancelAndResetPingJob()
|
||||||
|
serviceManager.autoTunnelService = CompletableDeferred()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,10 +160,6 @@ class AutoTunnelService : LifecycleService() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchWatcherPausedNotification() {
|
|
||||||
launchWatcherNotification(getString(R.string.watcher_notification_text_paused))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initWakeLock() {
|
private fun initWakeLock() {
|
||||||
wakeLock =
|
wakeLock =
|
||||||
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
|
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
|
||||||
|
@ -265,8 +245,7 @@ class AutoTunnelService : LifecycleService() {
|
||||||
runCatching {
|
runCatching {
|
||||||
do {
|
do {
|
||||||
val vpnState = tunnelService.get().vpnState.value
|
val vpnState = tunnelService.get().vpnState.value
|
||||||
val settings = appDataRepository.settings.getSettings()
|
if (vpnState.status == TunnelState.UP) {
|
||||||
if (vpnState.status == TunnelState.UP && !settings.isAutoTunnelPaused) {
|
|
||||||
if (vpnState.tunnelConfig != null) {
|
if (vpnState.tunnelConfig != null) {
|
||||||
val config = TunnelConfig.configFromWgQuick(vpnState.tunnelConfig.wgQuick)
|
val config = TunnelConfig.configFromWgQuick(vpnState.tunnelConfig.wgQuick)
|
||||||
val results = if (vpnState.tunnelConfig.pingIp != null) {
|
val results = if (vpnState.tunnelConfig.pingIp != null) {
|
||||||
|
@ -296,17 +275,6 @@ class AutoTunnelService : LifecycleService() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onAutoTunnelPause(paused: Boolean) {
|
|
||||||
if (autoTunnelStateFlow.value.settings.isAutoTunnelPaused
|
|
||||||
!= paused
|
|
||||||
) {
|
|
||||||
when (paused) {
|
|
||||||
true -> launchWatcherPausedNotification()
|
|
||||||
false -> launchWatcherNotification()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun watchForSettingsChanges() {
|
private suspend fun watchForSettingsChanges() {
|
||||||
Timber.i("Starting settings watcher")
|
Timber.i("Starting settings watcher")
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
|
@ -321,7 +289,7 @@ class AutoTunnelService : LifecycleService() {
|
||||||
tunnels = tunnels,
|
tunnels = tunnels,
|
||||||
)
|
)
|
||||||
}.collect {
|
}.collect {
|
||||||
onAutoTunnelPause(it.settings.isAutoTunnelPaused)
|
Timber.d("got new settings: ${it.settings}")
|
||||||
manageJobsBySettings(it.settings)
|
manageJobsBySettings(it.settings)
|
||||||
autoTunnelStateFlow.emit(it)
|
autoTunnelStateFlow.emit(it)
|
||||||
}
|
}
|
||||||
|
@ -331,7 +299,12 @@ class AutoTunnelService : LifecycleService() {
|
||||||
private suspend fun watchForVpnStateChanges() {
|
private suspend fun watchForVpnStateChanges() {
|
||||||
Timber.i("Starting vpn state watcher")
|
Timber.i("Starting vpn state watcher")
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
tunnelService.get().vpnState.collect { state ->
|
tunnelService.get().vpnState.distinctUntilChanged { old, new ->
|
||||||
|
old.tunnelConfig == new.tunnelConfig && old.status == new.status
|
||||||
|
}.collect { state ->
|
||||||
|
autoTunnelStateFlow.update {
|
||||||
|
it.copy(vpnState = state)
|
||||||
|
}
|
||||||
state.tunnelConfig?.let {
|
state.tunnelConfig?.let {
|
||||||
val settings = appDataRepository.settings.getSettings()
|
val settings = appDataRepository.settings.getSettings()
|
||||||
if (it.isPingEnabled && !settings.isPingEnabled) {
|
if (it.isPingEnabled && !settings.isPingEnabled) {
|
||||||
|
@ -475,9 +448,8 @@ class AutoTunnelService : LifecycleService() {
|
||||||
|
|
||||||
private suspend fun getWifiSSID(networkCapabilities: NetworkCapabilities): String? {
|
private suspend fun getWifiSSID(networkCapabilities: NetworkCapabilities): String? {
|
||||||
return withContext(ioDispatcher) {
|
return withContext(ioDispatcher) {
|
||||||
try {
|
with(autoTunnelStateFlow.value.settings) {
|
||||||
rootShell.get().getCurrentWifiName()
|
if (isWifiNameByShellEnabled) return@withContext rootShell.get().getCurrentWifiName()
|
||||||
} catch (_: Exception) {
|
|
||||||
wifiService.getNetworkName(networkCapabilities)
|
wifiService.getNetworkName(networkCapabilities)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -487,24 +459,20 @@ class AutoTunnelService : LifecycleService() {
|
||||||
return appDataRepository.tunnels.findByMobileDataTunnel().firstOrNull()
|
return appDataRepository.tunnels.findByMobileDataTunnel().firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isTunnelDown(): Boolean {
|
|
||||||
return tunnelService.get().vpnState.value.status == TunnelState.DOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun handleNetworkEventChanges() {
|
private suspend fun handleNetworkEventChanges() {
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
Timber.i("Starting network event watcher")
|
Timber.i("Starting network event watcher")
|
||||||
autoTunnelStateFlow.collectLatest { watcherState ->
|
autoTunnelStateFlow.collectLatest { watcherState ->
|
||||||
val autoTunnel = "Auto-tunnel watcher"
|
val autoTunnel = "Auto-tunnel watcher"
|
||||||
if (!watcherState.settings.isAutoTunnelPaused) {
|
Timber.d("New watcher state!")
|
||||||
// delay for rapid network state changes and then collect latest
|
// delay for rapid network state changes and then collect latest
|
||||||
delay(Constants.WATCHER_COLLECTION_DELAY)
|
delay(Constants.WATCHER_COLLECTION_DELAY)
|
||||||
val activeTunnel = tunnelService.get().vpnState.value.tunnelConfig
|
val activeTunnel = watcherState.vpnState.tunnelConfig
|
||||||
val defaultTunnel = appDataRepository.getPrimaryOrFirstTunnel()
|
val defaultTunnel = appDataRepository.getPrimaryOrFirstTunnel()
|
||||||
when {
|
when {
|
||||||
watcherState.isEthernetConditionMet() -> {
|
watcherState.isEthernetConditionMet() -> {
|
||||||
Timber.i("$autoTunnel - tunnel on on ethernet condition met")
|
Timber.i("$autoTunnel - tunnel on on ethernet condition met")
|
||||||
if (isTunnelDown()) {
|
if (watcherState.vpnState.isDown()) {
|
||||||
defaultTunnel?.let {
|
defaultTunnel?.let {
|
||||||
tunnelService.get().startTunnel(it)
|
tunnelService.get().startTunnel(it)
|
||||||
}
|
}
|
||||||
|
@ -516,7 +484,7 @@ class AutoTunnelService : LifecycleService() {
|
||||||
val mobileDataTunnel = getMobileDataTunnel()
|
val mobileDataTunnel = getMobileDataTunnel()
|
||||||
val tunnel =
|
val tunnel =
|
||||||
mobileDataTunnel ?: defaultTunnel
|
mobileDataTunnel ?: defaultTunnel
|
||||||
if (isTunnelDown() || activeTunnel?.isMobileDataTunnel == false) {
|
if (watcherState.vpnState.isDown() || activeTunnel?.isMobileDataTunnel == false) {
|
||||||
tunnel?.let {
|
tunnel?.let {
|
||||||
tunnelService.get().startTunnel(it)
|
tunnelService.get().startTunnel(it)
|
||||||
}
|
}
|
||||||
|
@ -525,7 +493,7 @@ class AutoTunnelService : LifecycleService() {
|
||||||
|
|
||||||
watcherState.isTunnelOffOnMobileDataConditionMet() -> {
|
watcherState.isTunnelOffOnMobileDataConditionMet() -> {
|
||||||
Timber.i("$autoTunnel - tunnel off on mobile data met, turning vpn off")
|
Timber.i("$autoTunnel - tunnel off on mobile data met, turning vpn off")
|
||||||
if (!isTunnelDown()) {
|
if (!watcherState.vpnState.isDown()) {
|
||||||
activeTunnel?.let {
|
activeTunnel?.let {
|
||||||
tunnelService.get().stopTunnel(it)
|
tunnelService.get().stopTunnel(it)
|
||||||
}
|
}
|
||||||
|
@ -534,21 +502,21 @@ class AutoTunnelService : LifecycleService() {
|
||||||
|
|
||||||
watcherState.isUntrustedWifiConditionMet() -> {
|
watcherState.isUntrustedWifiConditionMet() -> {
|
||||||
Timber.i("Untrusted wifi condition met")
|
Timber.i("Untrusted wifi condition met")
|
||||||
if (activeTunnel?.tunnelNetworks?.isMatchingToWildcardList(watcherState.currentNetworkSSID) == false ||
|
if (activeTunnel == null || watcherState.isCurrentSSIDActiveTunnelNetwork() == false ||
|
||||||
activeTunnel == null || isTunnelDown()
|
watcherState.vpnState.isDown()
|
||||||
) {
|
) {
|
||||||
Timber.i(
|
Timber.i(
|
||||||
"$autoTunnel - tunnel on ssid not associated with current tunnel condition met",
|
"$autoTunnel - tunnel on ssid not associated with current tunnel condition met",
|
||||||
)
|
)
|
||||||
watcherState.tunnels.firstOrNull { it.tunnelNetworks.isMatchingToWildcardList(watcherState.currentNetworkSSID) }?.let {
|
watcherState.getTunnelWithMatchingTunnelNetwork()?.let {
|
||||||
Timber.i("Found tunnel associated with this SSID, bringing tunnel up: ${it.name}")
|
Timber.i("Found tunnel associated with this SSID, bringing tunnel up: ${it.name}")
|
||||||
if (isTunnelDown() || activeTunnel?.id != it.id) {
|
if (watcherState.vpnState.isDown() || activeTunnel?.id != it.id) {
|
||||||
tunnelService.get().startTunnel(it)
|
tunnelService.get().startTunnel(it)
|
||||||
}
|
}
|
||||||
} ?: suspend {
|
} ?: suspend {
|
||||||
Timber.i("No tunnel associated with this SSID, using defaults")
|
Timber.i("No tunnel associated with this SSID, using defaults")
|
||||||
val default = appDataRepository.getPrimaryOrFirstTunnel()
|
val default = appDataRepository.getPrimaryOrFirstTunnel()
|
||||||
if (default?.name != tunnelService.get().name || isTunnelDown()) {
|
if (default?.name != tunnelService.get().name || watcherState.vpnState.isDown()) {
|
||||||
default?.let {
|
default?.let {
|
||||||
tunnelService.get().startTunnel(it)
|
tunnelService.get().startTunnel(it)
|
||||||
}
|
}
|
||||||
|
@ -561,21 +529,21 @@ class AutoTunnelService : LifecycleService() {
|
||||||
Timber.i(
|
Timber.i(
|
||||||
"$autoTunnel - tunnel off on trusted wifi condition met, turning vpn off",
|
"$autoTunnel - tunnel off on trusted wifi condition met, turning vpn off",
|
||||||
)
|
)
|
||||||
if (!isTunnelDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
|
if (!watcherState.vpnState.isDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
watcherState.isTunnelOffOnWifiConditionMet() -> {
|
watcherState.isTunnelOffOnWifiConditionMet() -> {
|
||||||
Timber.i(
|
Timber.i(
|
||||||
"$autoTunnel - tunnel off on wifi condition met, turning vpn off",
|
"$autoTunnel - tunnel off on wifi condition met, turning vpn off",
|
||||||
)
|
)
|
||||||
if (!isTunnelDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
|
if (!watcherState.vpnState.isDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
watcherState.isTunnelOffOnNoConnectivityMet() -> {
|
watcherState.isTunnelOffOnNoConnectivityMet() -> {
|
||||||
Timber.i(
|
Timber.i(
|
||||||
"$autoTunnel - tunnel off on no connectivity met, turning vpn off",
|
"$autoTunnel - tunnel off on no connectivity met, turning vpn off",
|
||||||
)
|
)
|
||||||
if (!isTunnelDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
|
if (!watcherState.vpnState.isDown()) activeTunnel?.let { tunnelService.get().stopTunnel(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
@ -586,4 +554,3 @@ class AutoTunnelService : LifecycleService() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.service.foreground
|
package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
|
||||||
|
|
||||||
data class AutoTunnelState(
|
data class AutoTunnelState(
|
||||||
|
val vpnState: VpnState = VpnState(),
|
||||||
val isWifiConnected: Boolean = false,
|
val isWifiConnected: Boolean = false,
|
||||||
val isEthernetConnected: Boolean = false,
|
val isEthernetConnected: Boolean = false,
|
||||||
val isMobileDataConnected: Boolean = false,
|
val isMobileDataConnected: Boolean = false,
|
||||||
|
@ -41,7 +44,7 @@ data class AutoTunnelState(
|
||||||
return (
|
return (
|
||||||
!isEthernetConnected &&
|
!isEthernetConnected &&
|
||||||
isWifiConnected &&
|
isWifiConnected &&
|
||||||
!settings.trustedNetworkSSIDs.isMatchingToWildcardList(currentNetworkSSID) &&
|
!isCurrentSSIDTrusted() &&
|
||||||
settings.isTunnelOnWifiEnabled
|
settings.isTunnelOnWifiEnabled
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -51,7 +54,7 @@ data class AutoTunnelState(
|
||||||
!isEthernetConnected &&
|
!isEthernetConnected &&
|
||||||
(
|
(
|
||||||
isWifiConnected &&
|
isWifiConnected &&
|
||||||
settings.trustedNetworkSSIDs.isMatchingToWildcardList(currentNetworkSSID)
|
isCurrentSSIDTrusted()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -73,4 +76,32 @@ data class AutoTunnelState(
|
||||||
!isMobileDataConnected
|
!isMobileDataConnected
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isCurrentSSIDTrusted(): Boolean {
|
||||||
|
return if (settings.isWildcardsEnabled) {
|
||||||
|
settings.trustedNetworkSSIDs.isMatchingToWildcardList(currentNetworkSSID)
|
||||||
|
} else {
|
||||||
|
settings.trustedNetworkSSIDs.contains(currentNetworkSSID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun isCurrentSSIDActiveTunnelNetwork(): Boolean {
|
||||||
|
val currentTunnelNetworks = vpnState.tunnelConfig?.tunnelNetworks
|
||||||
|
return (
|
||||||
|
if (settings.isWildcardsEnabled) {
|
||||||
|
currentTunnelNetworks?.isMatchingToWildcardList(currentNetworkSSID)
|
||||||
|
} else {
|
||||||
|
currentTunnelNetworks?.contains(currentNetworkSSID)
|
||||||
|
}
|
||||||
|
) == true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTunnelWithMatchingTunnelNetwork(): TunnelConfig? {
|
||||||
|
return tunnels.firstOrNull {
|
||||||
|
if (settings.isWildcardsEnabled) {
|
||||||
|
it.tunnelNetworks.isMatchingToWildcardList(currentNetworkSSID)
|
||||||
|
} else {
|
||||||
|
it.tunnelNetworks.contains(currentNetworkSSID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,69 +3,81 @@ package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.VpnService
|
import com.zaneschepke.wireguardautotunnel.util.SingletonHolder
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
||||||
|
import jakarta.inject.Inject
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
object ServiceManager {
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private fun <T : Service> actionOnService(action: Action, context: Context, cls: Class<T>, extras: Map<String, Int>? = null) {
|
class ServiceManager
|
||||||
if (VpnService.prepare(context) != null) return
|
@Inject constructor(private val context: Context) {
|
||||||
val intent =
|
|
||||||
Intent(context, cls).also {
|
|
||||||
it.action = action.name
|
|
||||||
extras?.forEach { (k, v) -> it.putExtra(k, v) }
|
|
||||||
}
|
|
||||||
intent.component?.javaClass
|
|
||||||
try {
|
|
||||||
when (action) {
|
|
||||||
Action.START_FOREGROUND, Action.STOP_FOREGROUND ->
|
|
||||||
context.startForegroundService(
|
|
||||||
intent,
|
|
||||||
)
|
|
||||||
|
|
||||||
Action.START, Action.STOP -> context.startService(intent)
|
private val _autoTunnelActive = MutableStateFlow(false)
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
val autoTunnelActive = _autoTunnelActive.asStateFlow()
|
||||||
Timber.e(e.message)
|
|
||||||
|
var autoTunnelService = CompletableDeferred<AutoTunnelService>()
|
||||||
|
var backgroundService = CompletableDeferred<TunnelBackgroundService>()
|
||||||
|
|
||||||
|
companion object : SingletonHolder<ServiceManager, Context>(::ServiceManager)
|
||||||
|
|
||||||
|
private fun <T : Service> startService(cls: Class<T>, background: Boolean) {
|
||||||
|
val intent = Intent(context, cls)
|
||||||
|
if (background) {
|
||||||
|
context.startForegroundService(intent)
|
||||||
|
} else {
|
||||||
|
context.startService(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startWatcherServiceForeground(context: Context) {
|
suspend fun startAutoTunnel(background: Boolean) {
|
||||||
actionOnService(
|
if (autoTunnelService.isCompleted) return _autoTunnelActive.update { true }
|
||||||
Action.START_FOREGROUND,
|
kotlin.runCatching {
|
||||||
context,
|
startService(AutoTunnelService::class.java, background)
|
||||||
AutoTunnelService::class.java,
|
autoTunnelService.await()
|
||||||
)
|
autoTunnelService.getCompleted().start()
|
||||||
|
_autoTunnelActive.update { true }
|
||||||
|
}.onFailure {
|
||||||
|
Timber.e(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startWatcherService(context: Context) {
|
suspend fun startBackgroundService() {
|
||||||
actionOnService(
|
if (backgroundService.isCompleted) return
|
||||||
Action.START,
|
kotlin.runCatching {
|
||||||
context,
|
startService(TunnelBackgroundService::class.java, true)
|
||||||
AutoTunnelService::class.java,
|
backgroundService.await()
|
||||||
)
|
backgroundService.getCompleted().start()
|
||||||
|
}.onFailure {
|
||||||
|
Timber.e(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopWatcherService(context: Context) {
|
fun stopBackgroundService() {
|
||||||
actionOnService(
|
if (!backgroundService.isCompleted) return
|
||||||
Action.STOP,
|
runCatching {
|
||||||
context,
|
backgroundService.getCompleted().stop()
|
||||||
AutoTunnelService::class.java,
|
}.onFailure {
|
||||||
)
|
Timber.e(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startTunnelBackgroundService(context: Context) {
|
fun stopAutoTunnel() {
|
||||||
actionOnService(
|
if (!autoTunnelService.isCompleted) return
|
||||||
Action.START_FOREGROUND,
|
runCatching {
|
||||||
context,
|
autoTunnelService.getCompleted().stop()
|
||||||
TunnelBackgroundService::class.java,
|
_autoTunnelActive.update { false }
|
||||||
)
|
}.onFailure {
|
||||||
|
Timber.e(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopTunnelBackgroundService(context: Context) {
|
fun requestTunnelTileUpdate() {
|
||||||
actionOnService(
|
context.requestTunnelTileServiceStateUpdate()
|
||||||
Action.STOP,
|
|
||||||
context,
|
|
||||||
TunnelBackgroundService::class.java,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import androidx.lifecycle.LifecycleService
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
|
@ -15,6 +16,9 @@ class TunnelBackgroundService : LifecycleService() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var notificationService: NotificationService
|
lateinit var notificationService: NotificationService
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
private val foregroundId = 123
|
private val foregroundId = 123
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
|
@ -29,27 +33,24 @@ class TunnelBackgroundService : LifecycleService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
if (intent != null) {
|
serviceManager.backgroundService.complete(this)
|
||||||
val action = intent.action
|
|
||||||
when (action) {
|
|
||||||
Action.START.name,
|
|
||||||
Action.START_FOREGROUND.name,
|
|
||||||
-> startService()
|
|
||||||
Action.STOP.name, Action.STOP_FOREGROUND.name -> stopService()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.onStartCommand(intent, flags, startId)
|
return super.onStartCommand(intent, flags, startId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startService() {
|
fun start() {
|
||||||
startForeground(foregroundId, createNotification())
|
startForeground(foregroundId, createNotification())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopService() {
|
fun stop() {
|
||||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
serviceManager.backgroundService = CompletableDeferred()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
private fun createNotification(): Notification {
|
private fun createNotification(): Notification {
|
||||||
return notificationService.createNotification(
|
return notificationService.createNotification(
|
||||||
getString(R.string.vpn_channel_id),
|
getString(R.string.vpn_channel_id),
|
||||||
|
|
|
@ -6,9 +6,8 @@ import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
|
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.AutoTunnelService
|
import com.zaneschepke.wireguardautotunnel.service.foreground.AutoTunnelService
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.startTunnelBackground
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.stopTunnelBackground
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -24,6 +23,9 @@ class ShortcutsActivity : ComponentActivity() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var tunnelService: Provider<TunnelService>
|
lateinit var tunnelService: Provider<TunnelService>
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@ApplicationScope
|
@ApplicationScope
|
||||||
lateinit var applicationScope: CoroutineScope
|
lateinit var applicationScope: CoroutineScope
|
||||||
|
@ -44,26 +46,16 @@ class ShortcutsActivity : ComponentActivity() {
|
||||||
Timber.d("Shortcut action on name: ${tunnelConfig?.name}")
|
Timber.d("Shortcut action on name: ${tunnelConfig?.name}")
|
||||||
tunnelConfig?.let {
|
tunnelConfig?.let {
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
Action.START.name -> this@ShortcutsActivity.startTunnelBackground(it.id)
|
Action.START.name -> tunnelService.get().startTunnel(it, true)
|
||||||
Action.STOP.name -> this@ShortcutsActivity.stopTunnelBackground(it.id)
|
Action.STOP.name -> tunnelService.get().stopTunnel(it)
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AutoTunnelService::class.java.simpleName, LEGACY_AUTO_TUNNEL_SERVICE_NAME -> {
|
AutoTunnelService::class.java.simpleName, LEGACY_AUTO_TUNNEL_SERVICE_NAME -> {
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
Action.START.name ->
|
Action.START.name -> serviceManager.startAutoTunnel(true)
|
||||||
appDataRepository.settings.save(
|
Action.STOP.name -> serviceManager.stopAutoTunnel()
|
||||||
settings.copy(
|
|
||||||
isAutoTunnelPaused = false,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
Action.STOP.name ->
|
|
||||||
appDataRepository.settings.save(
|
|
||||||
settings.copy(
|
|
||||||
isAutoTunnelPaused = true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.service.tile
|
package com.zaneschepke.wireguardautotunnel.service.tile
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.service.quicksettings.Tile
|
import android.service.quicksettings.Tile
|
||||||
import android.service.quicksettings.TileService
|
import android.service.quicksettings.TileService
|
||||||
|
@ -9,9 +8,9 @@ import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.LifecycleRegistry
|
import androidx.lifecycle.LifecycleRegistry
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -23,51 +22,18 @@ class AutoTunnelControlTile : TileService(), LifecycleOwner {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appDataRepository: AppDataRepository
|
lateinit var appDataRepository: AppDataRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@ApplicationScope
|
@ApplicationScope
|
||||||
lateinit var applicationScope: CoroutineScope
|
lateinit var applicationScope: CoroutineScope
|
||||||
|
|
||||||
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
|
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
|
||||||
|
|
||||||
/* This works around an annoying unsolved frameworks bug some people are hitting. */
|
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
|
||||||
var ret: IBinder? = null
|
|
||||||
try {
|
|
||||||
ret = super.onBind(intent)
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
Timber.e("Failed to bind to AutoTunnelTile")
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
||||||
|
|
||||||
applicationScope.launch {
|
|
||||||
appDataRepository.settings.getSettingsFlow().collect {
|
|
||||||
kotlin.runCatching {
|
|
||||||
when (it.isAutoTunnelEnabled) {
|
|
||||||
true -> {
|
|
||||||
if (it.isAutoTunnelPaused) {
|
|
||||||
setInactive()
|
|
||||||
setTileDescription(this@AutoTunnelControlTile.getString(R.string.paused))
|
|
||||||
} else {
|
|
||||||
setActive()
|
|
||||||
setTileDescription(this@AutoTunnelControlTile.getString(R.string.active))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
false -> {
|
|
||||||
setTileDescription(this@AutoTunnelControlTile.getString(R.string.disabled))
|
|
||||||
setUnavailable()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.onFailure {
|
|
||||||
Timber.e(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStopListening() {
|
override fun onStopListening() {
|
||||||
|
@ -82,26 +48,28 @@ class AutoTunnelControlTile : TileService(), LifecycleOwner {
|
||||||
override fun onStartListening() {
|
override fun onStartListening() {
|
||||||
super.onStartListening()
|
super.onStartListening()
|
||||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
|
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
|
||||||
|
lifecycleScope.launch {
|
||||||
|
if (appDataRepository.tunnels.getAll().isEmpty()) return@launch setUnavailable()
|
||||||
|
updateTileState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateTileState() {
|
||||||
|
serviceManager.autoTunnelActive.value.let {
|
||||||
|
if (it) setActive() else setInactive()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick() {
|
override fun onClick() {
|
||||||
super.onClick()
|
super.onClick()
|
||||||
unlockAndRun {
|
unlockAndRun {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
kotlin.runCatching {
|
if (serviceManager.autoTunnelActive.value) {
|
||||||
val settings = appDataRepository.settings.getSettings()
|
serviceManager.stopAutoTunnel()
|
||||||
if (settings.isAutoTunnelPaused) {
|
setInactive()
|
||||||
return@launch appDataRepository.settings.save(
|
} else {
|
||||||
settings.copy(
|
serviceManager.startAutoTunnel(true)
|
||||||
isAutoTunnelPaused = false,
|
setActive()
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
appDataRepository.settings.save(
|
|
||||||
settings.copy(
|
|
||||||
isAutoTunnelPaused = true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,16 +96,15 @@ class AutoTunnelControlTile : TileService(), LifecycleOwner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setTileDescription(description: String) {
|
/* This works around an annoying unsolved frameworks bug some people are hitting. */
|
||||||
kotlin.runCatching {
|
override fun onBind(intent: Intent): IBinder? {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
var ret: IBinder? = null
|
||||||
qsTile.subtitle = description
|
try {
|
||||||
}
|
ret = super.onBind(intent)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
} catch (_: Throwable) {
|
||||||
qsTile.stateDescription = description
|
Timber.e("Failed to bind to TunnelControlTile")
|
||||||
}
|
|
||||||
qsTile.updateTile()
|
|
||||||
}
|
}
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
override val lifecycle: Lifecycle
|
override val lifecycle: Lifecycle
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.service.tile
|
package com.zaneschepke.wireguardautotunnel.service.tile
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.IBinder
|
||||||
import android.service.quicksettings.Tile
|
import android.service.quicksettings.Tile
|
||||||
import android.service.quicksettings.TileService
|
import android.service.quicksettings.TileService
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
|
@ -11,8 +13,6 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.startTunnelBackground
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.stopTunnelBackground
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -36,7 +36,6 @@ class TunnelControlTile : TileService(), LifecycleOwner {
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
Timber.d("onCreate for tile service")
|
|
||||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +51,7 @@ class TunnelControlTile : TileService(), LifecycleOwner {
|
||||||
override fun onStartListening() {
|
override fun onStartListening() {
|
||||||
super.onStartListening()
|
super.onStartListening()
|
||||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
|
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
|
||||||
|
Timber.d("Updating tile!")
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
if (appDataRepository.tunnels.getAll().isEmpty()) return@launch setUnavailable()
|
if (appDataRepository.tunnels.getAll().isEmpty()) return@launch setUnavailable()
|
||||||
updateTileState()
|
updateTileState()
|
||||||
|
@ -60,6 +60,7 @@ class TunnelControlTile : TileService(), LifecycleOwner {
|
||||||
|
|
||||||
private suspend fun updateTileState() {
|
private suspend fun updateTileState() {
|
||||||
val lastActive = appDataRepository.getStartTunnelConfig()
|
val lastActive = appDataRepository.getStartTunnelConfig()
|
||||||
|
Timber.d("Got config $lastActive")
|
||||||
lastActive?.let {
|
lastActive?.let {
|
||||||
updateTile(it)
|
updateTile(it)
|
||||||
}
|
}
|
||||||
|
@ -68,13 +69,15 @@ class TunnelControlTile : TileService(), LifecycleOwner {
|
||||||
override fun onClick() {
|
override fun onClick() {
|
||||||
super.onClick()
|
super.onClick()
|
||||||
unlockAndRun {
|
unlockAndRun {
|
||||||
Timber.d("Click")
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val context = this@TunnelControlTile
|
|
||||||
val lastActive = appDataRepository.getStartTunnelConfig()
|
val lastActive = appDataRepository.getStartTunnelConfig()
|
||||||
lastActive?.let { tunnel ->
|
lastActive?.let { tunnel ->
|
||||||
if (tunnel.isActive) return@launch context.stopTunnelBackground(tunnel.id)
|
if (tunnel.isActive) {
|
||||||
context.startTunnelBackground(tunnel.id)
|
tunnelService.get().stopTunnel(tunnel)
|
||||||
|
} else {
|
||||||
|
tunnelService.get().startTunnel(tunnel, true)
|
||||||
|
}
|
||||||
|
updateTileState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,6 +127,17 @@ class TunnelControlTile : TileService(), LifecycleOwner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* This works around an annoying unsolved frameworks bug some people are hitting. */
|
||||||
|
override fun onBind(intent: Intent): IBinder? {
|
||||||
|
var ret: IBinder? = null
|
||||||
|
try {
|
||||||
|
ret = super.onBind(intent)
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
Timber.e("Failed to bind to TunnelControlTile")
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
override val lifecycle: Lifecycle
|
override val lifecycle: Lifecycle
|
||||||
get() = lifecycleRegistry
|
get() = lifecycleRegistry
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
interface TunnelService : Tunnel, org.amnezia.awg.backend.Tunnel {
|
interface TunnelService : Tunnel, org.amnezia.awg.backend.Tunnel {
|
||||||
suspend fun startTunnel(tunnelConfig: TunnelConfig): Result<TunnelState>
|
suspend fun startTunnel(tunnelConfig: TunnelConfig, background: Boolean = false): Result<TunnelState>
|
||||||
|
|
||||||
suspend fun stopTunnel(tunnelConfig: TunnelConfig): Result<TunnelState>
|
suspend fun stopTunnel(tunnelConfig: TunnelConfig): Result<TunnelState>
|
||||||
|
|
||||||
|
@ -18,5 +18,6 @@ interface TunnelService : Tunnel, org.amnezia.awg.backend.Tunnel {
|
||||||
suspend fun getState(): TunnelState
|
suspend fun getState(): TunnelState
|
||||||
|
|
||||||
fun cancelStatsJob()
|
fun cancelStatsJob()
|
||||||
|
|
||||||
fun startStatsJob()
|
fun startStatsJob()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepositor
|
||||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.module.Kernel
|
import com.zaneschepke.wireguardautotunnel.module.Kernel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
|
||||||
|
@ -36,6 +37,7 @@ constructor(
|
||||||
private val appDataRepository: AppDataRepository,
|
private val appDataRepository: AppDataRepository,
|
||||||
@ApplicationScope private val applicationScope: CoroutineScope,
|
@ApplicationScope private val applicationScope: CoroutineScope,
|
||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
|
private val serviceManager: ServiceManager,
|
||||||
) : TunnelService {
|
) : TunnelService {
|
||||||
|
|
||||||
private val _vpnState = MutableStateFlow(VpnState())
|
private val _vpnState = MutableStateFlow(VpnState())
|
||||||
|
@ -87,9 +89,9 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun startTunnel(tunnelConfig: TunnelConfig): Result<TunnelState> {
|
override suspend fun startTunnel(tunnelConfig: TunnelConfig, background: Boolean): Result<TunnelState> {
|
||||||
return withContext(ioDispatcher) {
|
return withContext(ioDispatcher) {
|
||||||
onBeforeStart(tunnelConfig)
|
onBeforeStart(tunnelConfig, background)
|
||||||
setState(tunnelConfig, TunnelState.UP).onSuccess {
|
setState(tunnelConfig, TunnelState.UP).onSuccess {
|
||||||
emitTunnelState(it)
|
emitTunnelState(it)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
|
@ -143,18 +145,28 @@ constructor(
|
||||||
resetBackendStatistics()
|
resetBackendStatistics()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onBeforeStart(tunnelConfig: TunnelConfig) {
|
private suspend fun onBeforeStart(tunnelConfig: TunnelConfig, background: Boolean) {
|
||||||
if (_vpnState.value.status == TunnelState.UP) vpnState.value.tunnelConfig?.let { stopTunnel(it) }
|
if (_vpnState.value.status == TunnelState.UP &&
|
||||||
|
tunnelConfig != _vpnState.value.tunnelConfig
|
||||||
|
) {
|
||||||
|
vpnState.value.tunnelConfig?.let { stopTunnel(it) }
|
||||||
|
}
|
||||||
|
if (background) serviceManager.startBackgroundService()
|
||||||
resetBackendStatistics()
|
resetBackendStatistics()
|
||||||
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = true))
|
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = true))
|
||||||
emitVpnStateConfig(tunnelConfig)
|
emitVpnStateConfig(tunnelConfig)
|
||||||
startStatsJob()
|
startStatsJob()
|
||||||
|
Timber.d("Updating start")
|
||||||
|
serviceManager.requestTunnelTileUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onBeforeStop(tunnelConfig: TunnelConfig) {
|
private suspend fun onBeforeStop(tunnelConfig: TunnelConfig) {
|
||||||
cancelStatsJob()
|
cancelStatsJob()
|
||||||
resetBackendStatistics()
|
resetBackendStatistics()
|
||||||
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = false))
|
appDataRepository.tunnels.save(tunnelConfig.copy(isActive = false))
|
||||||
|
serviceManager.stopBackgroundService()
|
||||||
|
Timber.d("UPdating stop")
|
||||||
|
serviceManager.requestTunnelTileUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun emitTunnelState(state: TunnelState) {
|
private fun emitTunnelState(state: TunnelState) {
|
||||||
|
|
|
@ -10,6 +10,5 @@ data class AppUiState(
|
||||||
val tunnels: List<TunnelConfig> = emptyList(),
|
val tunnels: List<TunnelConfig> = emptyList(),
|
||||||
val vpnState: VpnState = VpnState(),
|
val vpnState: VpnState = VpnState(),
|
||||||
val generalState: GeneralState = GeneralState(),
|
val generalState: GeneralState = GeneralState(),
|
||||||
val isKernelAvailable: Boolean = false,
|
val autoTunnelActive: Boolean = false,
|
||||||
val isRooted: Boolean = false,
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,8 +2,6 @@ package com.zaneschepke.wireguardautotunnel.ui
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.wireguard.android.backend.WgQuickBackend
|
|
||||||
import com.wireguard.android.util.RootShell
|
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
|
@ -11,7 +9,6 @@ import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
@ -20,13 +17,10 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.onCompletion
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
import kotlinx.coroutines.flow.onStart
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.takeWhile
|
import kotlinx.coroutines.flow.takeWhile
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import xyz.teamgravity.pin_lock_compose.PinManager
|
import xyz.teamgravity.pin_lock_compose.PinManager
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
|
@ -38,6 +32,7 @@ constructor(
|
||||||
private val appDataRepository: AppDataRepository,
|
private val appDataRepository: AppDataRepository,
|
||||||
private val tunnelService: Provider<TunnelService>,
|
private val tunnelService: Provider<TunnelService>,
|
||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
|
private val serviceManager: ServiceManager,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
val uiState =
|
val uiState =
|
||||||
|
@ -46,12 +41,14 @@ constructor(
|
||||||
appDataRepository.tunnels.getTunnelConfigsFlow(),
|
appDataRepository.tunnels.getTunnelConfigsFlow(),
|
||||||
tunnelService.get().vpnState,
|
tunnelService.get().vpnState,
|
||||||
appDataRepository.appState.generalStateFlow,
|
appDataRepository.appState.generalStateFlow,
|
||||||
) { settings, tunnels, tunnelState, generalState ->
|
serviceManager.autoTunnelActive,
|
||||||
|
) { settings, tunnels, tunnelState, generalState, autoTunnel ->
|
||||||
AppUiState(
|
AppUiState(
|
||||||
settings,
|
settings,
|
||||||
tunnels,
|
tunnels,
|
||||||
tunnelState,
|
tunnelState,
|
||||||
generalState,
|
generalState,
|
||||||
|
autoTunnel,
|
||||||
)
|
)
|
||||||
}.stateIn(
|
}.stateIn(
|
||||||
viewModelScope + ioDispatcher,
|
viewModelScope + ioDispatcher,
|
||||||
|
@ -95,7 +92,7 @@ constructor(
|
||||||
|
|
||||||
private suspend fun initAutoTunnel() {
|
private suspend fun initAutoTunnel() {
|
||||||
val settings = appDataRepository.settings.getSettings()
|
val settings = appDataRepository.settings.getSettings()
|
||||||
if (settings.isAutoTunnelEnabled) ServiceManager.startWatcherService(WireGuardAutoTunnel.instance)
|
if (settings.isAutoTunnelEnabled) serviceManager.startAutoTunnel(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onPinLockDisabled() = viewModelScope.launch(ioDispatcher) {
|
fun onPinLockDisabled() = viewModelScope.launch(ioDispatcher) {
|
||||||
|
|
|
@ -39,9 +39,7 @@ import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.data.datastore.LocaleStorage
|
import com.zaneschepke.wireguardautotunnel.data.datastore.LocaleStorage
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalFocusRequester
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalFocusRequester
|
||||||
|
@ -67,6 +65,7 @@ import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
|
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
|
@ -100,17 +99,17 @@ class MainActivity : AppCompatActivity() {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val rootItemFocusRequester = remember { FocusRequester() }
|
val rootItemFocusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
LaunchedEffect(appUiState.vpnState.status) {
|
LaunchedEffect(appUiState.tunnels) {
|
||||||
val context = this@MainActivity
|
Timber.d("Updating launched")
|
||||||
when (appUiState.vpnState.status) {
|
requestTunnelTileServiceStateUpdate()
|
||||||
TunnelState.DOWN -> ServiceManager.stopTunnelBackgroundService(context)
|
|
||||||
else -> Unit
|
|
||||||
}
|
}
|
||||||
context.requestTunnelTileServiceStateUpdate()
|
|
||||||
|
LaunchedEffect(appUiState.autoTunnelActive) {
|
||||||
|
requestAutoTunnelTileServiceUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
with(appUiState.settings) {
|
with(appUiState.settings) {
|
||||||
LaunchedEffect(isAutoTunnelPaused, isAutoTunnelEnabled) {
|
LaunchedEffect(isAutoTunnelEnabled) {
|
||||||
this@MainActivity.requestAutoTunnelTileServiceUpdate()
|
this@MainActivity.requestAutoTunnelTileServiceUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,4 +247,3 @@ class MainActivity : AppCompatActivity() {
|
||||||
tunnelService.cancelStatsJob()
|
tunnelService.cancelStatsJob()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,6 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
|
||||||
import androidx.compose.ui.focus.focusRequester
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package com.zaneschepke.wireguardautotunnel.ui.common.button
|
||||||
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.focusable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
@ -31,10 +30,14 @@ import kotlin.let
|
||||||
@androidx.compose.runtime.Composable
|
@androidx.compose.runtime.Composable
|
||||||
fun IconSurfaceButton(title: String, onClick: () -> Unit, selected: Boolean, leadingIcon: ImageVector? = null, description: String? = null) {
|
fun IconSurfaceButton(title: String, onClick: () -> Unit, selected: Boolean, leadingIcon: ImageVector? = null, description: String? = null) {
|
||||||
val border: BorderStroke? =
|
val border: BorderStroke? =
|
||||||
if (selected) BorderStroke(
|
if (selected) {
|
||||||
|
BorderStroke(
|
||||||
1.dp,
|
1.dp,
|
||||||
MaterialTheme.colorScheme.primary
|
MaterialTheme.colorScheme.primary,
|
||||||
) else null
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
Card(
|
Card(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
|
@ -44,8 +47,10 @@ fun IconSurfaceButton(title: String, onClick: () -> Unit, selected: Boolean, lea
|
||||||
border = border,
|
border = border,
|
||||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
|
||||||
) {
|
) {
|
||||||
Box(modifier = Modifier.clickable { onClick() }
|
Box(
|
||||||
.fillMaxWidth()) {
|
modifier = Modifier.clickable { onClick() }
|
||||||
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
|
@ -61,7 +66,7 @@ fun IconSurfaceButton(title: String, onClick: () -> Unit, selected: Boolean, lea
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(
|
horizontalArrangement = Arrangement.spacedBy(
|
||||||
16.dp.scaledWidth()
|
16.dp.scaledWidth(),
|
||||||
),
|
),
|
||||||
verticalAlignment = Alignment.Companion.CenterVertically,
|
verticalAlignment = Alignment.Companion.CenterVertically,
|
||||||
modifier = Modifier.padding(vertical = if (description == null) 10.dp.scaledHeight() else 0.dp),
|
modifier = Modifier.padding(vertical = if (description == null) 10.dp.scaledHeight() else 0.dp),
|
||||||
|
@ -77,7 +82,7 @@ fun IconSurfaceButton(title: String, onClick: () -> Unit, selected: Boolean, lea
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleMedium,
|
||||||
)
|
)
|
||||||
description?.let {
|
description?.let {
|
||||||
Text(
|
Text(
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.common.button.surface
|
package com.zaneschepke.wireguardautotunnel.ui.common.button.surface
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
|
||||||
data class SelectionItem(
|
data class SelectionItem(
|
||||||
|
|
|
@ -24,7 +24,6 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SurfaceSelectionGroupButton(items: List<SelectionItem>) {
|
fun SurfaceSelectionGroupButton(items: List<SelectionItem>) {
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(8.dp),
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
@ -35,7 +34,7 @@ fun SurfaceSelectionGroupButton(items: List<SelectionItem>) {
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.then(item.onClick?.let { Modifier.clickable { it() } } ?: Modifier)
|
.then(item.onClick?.let { Modifier.clickable { it() } } ?: Modifier)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
@ -85,4 +84,3 @@ fun SurfaceSelectionGroupButton(items: List<SelectionItem>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,6 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
|
||||||
import androidx.compose.ui.focus.focusRequester
|
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
|
@ -19,4 +19,3 @@ fun GroupLabel(title: String) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ fun VersionLabel() {
|
||||||
color = MaterialTheme.colorScheme.outline,
|
color = MaterialTheme.colorScheme.outline,
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
clipboardManager.setText(AnnotatedString(BuildConfig.VERSION_NAME))
|
clipboardManager.setText(AnnotatedString(BuildConfig.VERSION_NAME))
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,21 +8,11 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
|
||||||
import androidx.compose.ui.input.key.Key
|
|
||||||
import androidx.compose.ui.input.key.KeyEventType
|
|
||||||
import androidx.compose.ui.input.key.key
|
|
||||||
import androidx.compose.ui.input.key.onKeyEvent
|
|
||||||
import androidx.compose.ui.input.key.type
|
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BottomNavBar(navController: NavController, bottomNavItems: List<BottomNavItem>) {
|
fun BottomNavBar(navController: NavController, bottomNavItems: List<BottomNavItem>) {
|
||||||
|
@ -34,7 +24,6 @@ fun BottomNavBar(navController: NavController, bottomNavItems: List<BottomNavIte
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showBottomBar) {
|
if (showBottomBar) {
|
||||||
|
|
||||||
NavigationBar(
|
NavigationBar(
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.common.navigation
|
package com.zaneschepke.wireguardautotunnel.ui.common.navigation
|
||||||
|
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
|
||||||
|
@ -10,4 +9,3 @@ val LocalNavController = compositionLocalOf<NavHostController> {
|
||||||
}
|
}
|
||||||
|
|
||||||
val LocalFocusRequester = compositionLocalOf<FocusRequester> { error("FocusRequester is not provided") }
|
val LocalFocusRequester = compositionLocalOf<FocusRequester> { error("FocusRequester is not provided") }
|
||||||
|
|
||||||
|
|
|
@ -18,13 +18,15 @@ fun TopNavBar(title: String, trailing: @Composable () -> Unit = {}, showBack: Bo
|
||||||
Text(title)
|
Text(title)
|
||||||
},
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
if(showBack) IconButton(onClick = { navController.popBackStack() }) {
|
if (showBack) {
|
||||||
|
IconButton(onClick = { navController.popBackStack() }) {
|
||||||
val icon = Icons.AutoMirrored.Outlined.ArrowBack
|
val icon = Icons.AutoMirrored.Outlined.ArrowBack
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
contentDescription = icon.name,
|
contentDescription = icon.name,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
trailing()
|
trailing()
|
||||||
|
|
|
@ -234,7 +234,7 @@ fun ConfigScreen(tunnelId: Int) {
|
||||||
hint = stringResource(R.string.tunnel_name).lowercase(),
|
hint = stringResource(R.string.tunnel_name).lowercase(),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier =
|
modifier =
|
||||||
|
@ -347,7 +347,7 @@ fun ConfigScreen(tunnelId: Int) {
|
||||||
hint = stringResource(R.string.junk_packet_count).lowercase(),
|
hint = stringResource(R.string.junk_packet_count).lowercase(),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = uiState.interfaceProxy.junkPacketMinSize,
|
value = uiState.interfaceProxy.junkPacketMinSize,
|
||||||
|
@ -360,7 +360,7 @@ fun ConfigScreen(tunnelId: Int) {
|
||||||
).lowercase(),
|
).lowercase(),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = uiState.interfaceProxy.junkPacketMaxSize,
|
value = uiState.interfaceProxy.junkPacketMaxSize,
|
||||||
|
@ -373,7 +373,7 @@ fun ConfigScreen(tunnelId: Int) {
|
||||||
).lowercase(),
|
).lowercase(),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = uiState.interfaceProxy.initPacketJunkSize,
|
value = uiState.interfaceProxy.initPacketJunkSize,
|
||||||
|
@ -383,7 +383,7 @@ fun ConfigScreen(tunnelId: Int) {
|
||||||
hint = stringResource(R.string.init_packet_junk_size).lowercase(),
|
hint = stringResource(R.string.init_packet_junk_size).lowercase(),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = uiState.interfaceProxy.responsePacketJunkSize,
|
value = uiState.interfaceProxy.responsePacketJunkSize,
|
||||||
|
@ -396,7 +396,7 @@ fun ConfigScreen(tunnelId: Int) {
|
||||||
).lowercase(),
|
).lowercase(),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = uiState.interfaceProxy.initPacketMagicHeader,
|
value = uiState.interfaceProxy.initPacketMagicHeader,
|
||||||
|
@ -409,7 +409,7 @@ fun ConfigScreen(tunnelId: Int) {
|
||||||
).lowercase(),
|
).lowercase(),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = uiState.interfaceProxy.responsePacketMagicHeader,
|
value = uiState.interfaceProxy.responsePacketMagicHeader,
|
||||||
|
@ -422,7 +422,7 @@ fun ConfigScreen(tunnelId: Int) {
|
||||||
).lowercase(),
|
).lowercase(),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = uiState.interfaceProxy.underloadPacketMagicHeader,
|
value = uiState.interfaceProxy.underloadPacketMagicHeader,
|
||||||
|
@ -435,7 +435,7 @@ fun ConfigScreen(tunnelId: Int) {
|
||||||
).lowercase(),
|
).lowercase(),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = uiState.interfaceProxy.transportPacketMagicHeader,
|
value = uiState.interfaceProxy.transportPacketMagicHeader,
|
||||||
|
@ -448,7 +448,7 @@ fun ConfigScreen(tunnelId: Int) {
|
||||||
).lowercase(),
|
).lowercase(),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.main
|
package com.zaneschepke.wireguardautotunnel.ui.screens.main
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
|
import android.provider.Settings
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.ActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
@ -53,9 +57,9 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.TunnelImpo
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.TunnelRowItem
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.TunnelRowItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.VpnDeniedDialog
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.VpnDeniedDialog
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isBatteryOptimizationsDisabled
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.startTunnelBackground
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -75,13 +79,19 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||||
NestedScrollListener({ isFabVisible = false }, { isFabVisible = true })
|
NestedScrollListener({ isFabVisible = false }, { isFabVisible = true })
|
||||||
}
|
}
|
||||||
|
|
||||||
val vpnActivityResultState =
|
val vpnActivity =
|
||||||
rememberLauncherForActivityResult(
|
rememberLauncherForActivityResult(
|
||||||
ActivityResultContracts.StartActivityForResult(),
|
ActivityResultContracts.StartActivityForResult(),
|
||||||
onResult = {
|
onResult = {
|
||||||
if (it.resultCode != RESULT_OK) showVpnPermissionDialog = true
|
if (it.resultCode != RESULT_OK) showVpnPermissionDialog = true
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
val batteryActivity =
|
||||||
|
rememberLauncherForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult(),
|
||||||
|
) { result: ActivityResult ->
|
||||||
|
viewModel.setBatteryOptimizeDisableShown()
|
||||||
|
}
|
||||||
|
|
||||||
val tunnelFileImportResultLauncher = rememberFileImportLauncherForResult(onNoFileExplorer = {
|
val tunnelFileImportResultLauncher = rememberFileImportLauncherForResult(onNoFileExplorer = {
|
||||||
snackbar.showMessage(
|
snackbar.showMessage(
|
||||||
|
@ -104,7 +114,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||||
InfoDialog(
|
InfoDialog(
|
||||||
onDismiss = { showDeleteTunnelAlertDialog = false },
|
onDismiss = { showDeleteTunnelAlertDialog = false },
|
||||||
onAttest = {
|
onAttest = {
|
||||||
selectedTunnel?.let { viewModel.onDelete(it, context) }
|
selectedTunnel?.let { viewModel::onDelete }
|
||||||
showDeleteTunnelAlertDialog = false
|
showDeleteTunnelAlertDialog = false
|
||||||
selectedTunnel = null
|
selectedTunnel = null
|
||||||
},
|
},
|
||||||
|
@ -114,15 +124,35 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun requestBatteryOptimizationsDisabled() {
|
||||||
|
val intent =
|
||||||
|
Intent().apply {
|
||||||
|
action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
||||||
|
data = Uri.parse("package:${context.packageName}")
|
||||||
|
}
|
||||||
|
batteryActivity.launch(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onAutoTunnelToggle() {
|
||||||
|
if (!uiState.generalState.isBatteryOptimizationDisableShown &&
|
||||||
|
!context.isBatteryOptimizationsDisabled() && !isRunningOnTv
|
||||||
|
) {
|
||||||
|
return requestBatteryOptimizationsDisabled()
|
||||||
|
}
|
||||||
|
val intent = if (!uiState.settings.isKernelEnabled) {
|
||||||
|
VpnService.prepare(context)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
if (intent != null) return vpnActivity.launch(intent)
|
||||||
|
viewModel.onToggleAutoTunnel()
|
||||||
|
}
|
||||||
|
|
||||||
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConfig) {
|
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConfig) {
|
||||||
val intent = if (uiState.settings.isKernelEnabled) null else VpnService.prepare(context)
|
val intent = if (uiState.settings.isKernelEnabled) null else VpnService.prepare(context)
|
||||||
if (intent != null) return vpnActivityResultState.launch(intent)
|
if (intent != null) return vpnActivity.launch(intent)
|
||||||
if (!checked) viewModel.onTunnelStop(tunnel).also { return }
|
if (!checked) viewModel.onTunnelStop(tunnel).also { return }
|
||||||
if (uiState.settings.isKernelEnabled) {
|
viewModel.onTunnelStart(tunnel, uiState.settings.isKernelEnabled)
|
||||||
context.startTunnelBackground(tunnel.id)
|
|
||||||
} else {
|
|
||||||
viewModel.onTunnelStart(tunnel)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
@ -137,7 +167,8 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||||
},
|
},
|
||||||
floatingActionButtonPosition = FabPosition.End,
|
floatingActionButtonPosition = FabPosition.End,
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
if(!isRunningOnTv) ScrollDismissFab({
|
if (!isRunningOnTv) {
|
||||||
|
ScrollDismissFab({
|
||||||
val icon = Icons.Filled.Add
|
val icon = Icons.Filled.Add
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
|
@ -147,9 +178,11 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||||
}, isVisible = isFabVisible, onClick = {
|
}, isVisible = isFabVisible, onClick = {
|
||||||
showBottomSheet = true
|
showBottomSheet = true
|
||||||
})
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
topBar = {
|
topBar = {
|
||||||
if(isRunningOnTv) TopNavBar(
|
if (isRunningOnTv) {
|
||||||
|
TopNavBar(
|
||||||
showBack = false,
|
showBack = false,
|
||||||
title = stringResource(R.string.app_name),
|
title = stringResource(R.string.app_name),
|
||||||
trailing = {
|
trailing = {
|
||||||
|
@ -162,9 +195,10 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||||
contentDescription = icon.name,
|
contentDescription = icon.name,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
TunnelImportSheet(
|
TunnelImportSheet(
|
||||||
showBottomSheet,
|
showBottomSheet,
|
||||||
|
@ -196,7 +230,9 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
item {
|
item {
|
||||||
AutoTunnelRowItem(uiState.settings, { viewModel.onToggleAutoTunnel(context) })
|
AutoTunnelRowItem(uiState, {
|
||||||
|
onAutoTunnelToggle()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items(
|
items(
|
||||||
|
|
|
@ -30,26 +30,24 @@ import timber.log.Timber
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Provider
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class MainViewModel
|
class MainViewModel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val appDataRepository: AppDataRepository,
|
private val appDataRepository: AppDataRepository,
|
||||||
val tunnelService: TunnelService,
|
private val tunnelService: Provider<TunnelService>,
|
||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
|
private val serviceManager: ServiceManager,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private fun stopWatcherService(context: Context) {
|
fun onDelete(tunnel: TunnelConfig) {
|
||||||
ServiceManager.stopWatcherService(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onDelete(tunnel: TunnelConfig, context: Context) {
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val settings = appDataRepository.settings.getSettings()
|
val settings = appDataRepository.settings.getSettings()
|
||||||
val isPrimary = tunnel.isPrimaryTunnel
|
val isPrimary = tunnel.isPrimaryTunnel
|
||||||
if (appDataRepository.tunnels.count() == 1 || isPrimary) {
|
if (appDataRepository.tunnels.count() == 1 || isPrimary) {
|
||||||
stopWatcherService(context)
|
serviceManager.stopAutoTunnel()
|
||||||
resetTunnelSetting(settings)
|
resetTunnelSetting(settings)
|
||||||
}
|
}
|
||||||
appDataRepository.tunnels.delete(tunnel)
|
appDataRepository.tunnels.delete(tunnel)
|
||||||
|
@ -69,14 +67,14 @@ constructor(
|
||||||
appDataRepository.appState.setTunnelStatsExpanded(expanded)
|
appDataRepository.appState.setTunnelStatsExpanded(expanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onTunnelStart(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
fun onTunnelStart(tunnelConfig: TunnelConfig, background: Boolean) = viewModelScope.launch {
|
||||||
Timber.i("Starting tunnel ${tunnelConfig.name}")
|
Timber.i("Starting tunnel ${tunnelConfig.name}")
|
||||||
tunnelService.startTunnel(tunnelConfig)
|
tunnelService.get().startTunnel(tunnelConfig, background)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onTunnelStop(tunnel: TunnelConfig) = viewModelScope.launch {
|
fun onTunnelStop(tunnel: TunnelConfig) = viewModelScope.launch {
|
||||||
Timber.i("Stopping active tunnel")
|
Timber.i("Stopping active tunnel")
|
||||||
tunnelService.stopTunnel(tunnel)
|
tunnelService.get().stopTunnel(tunnel)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateQrCodeDefaultName(config: String): String {
|
private fun generateQrCodeDefaultName(config: String): String {
|
||||||
|
@ -160,16 +158,17 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onToggleAutoTunnel(context: Context) = viewModelScope.launch {
|
fun onToggleAutoTunnel() = viewModelScope.launch {
|
||||||
val settings = appDataRepository.settings.getSettings()
|
val settings = appDataRepository.settings.getSettings()
|
||||||
if (settings.isAutoTunnelEnabled) {
|
val toggled = !settings.isAutoTunnelEnabled
|
||||||
ServiceManager.stopWatcherService(context)
|
if (toggled) {
|
||||||
|
serviceManager.startAutoTunnel(false)
|
||||||
} else {
|
} else {
|
||||||
ServiceManager.startWatcherService(context)
|
serviceManager.stopAutoTunnel()
|
||||||
}
|
}
|
||||||
appDataRepository.settings.save(
|
appDataRepository.settings.save(
|
||||||
settings.copy(
|
settings.copy(
|
||||||
isAutoTunnelEnabled = !settings.isAutoTunnelEnabled,
|
isAutoTunnelEnabled = toggled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -195,6 +194,10 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setBatteryOptimizeDisableShown() = viewModelScope.launch {
|
||||||
|
appDataRepository.appState.setBatteryOptimizationDisableShown(true)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun saveTunnelFromConfUri(name: String, uri: Uri, context: Context) {
|
private suspend fun saveTunnelFromConfUri(name: String, uri: Uri, context: Context) {
|
||||||
val stream = getInputStreamFromUri(uri, context) ?: throw FileReadException
|
val stream = getInputStreamFromUri(uri, context) ?: throw FileReadException
|
||||||
saveTunnelConfigFromStream(stream, name)
|
saveTunnelConfigFromStream(stream, name)
|
||||||
|
|
|
@ -4,30 +4,25 @@ import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Bolt
|
import androidx.compose.material.icons.rounded.Bolt
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.ExpandingRowListItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.ExpandingRowListItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
|
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AutoTunnelRowItem(settings: Settings, onToggle: () -> Unit) {
|
fun AutoTunnelRowItem(appUiState: AppUiState, onToggle: () -> Unit) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val itemFocusRequester = remember { FocusRequester() }
|
val itemFocusRequester = remember { FocusRequester() }
|
||||||
ExpandingRowListItem(
|
ExpandingRowListItem(
|
||||||
|
@ -40,7 +35,7 @@ fun AutoTunnelRowItem(settings: Settings, onToggle: () -> Unit) {
|
||||||
Modifier
|
Modifier
|
||||||
.size(16.dp.scaledHeight()).scale(1.5f),
|
.size(16.dp.scaledHeight()).scale(1.5f),
|
||||||
tint =
|
tint =
|
||||||
if (!settings.isAutoTunnelEnabled) {
|
if (!appUiState.autoTunnelActive) {
|
||||||
Color.Gray
|
Color.Gray
|
||||||
} else {
|
} else {
|
||||||
SilverTree
|
SilverTree
|
||||||
|
@ -50,10 +45,10 @@ fun AutoTunnelRowItem(settings: Settings, onToggle: () -> Unit) {
|
||||||
text = stringResource(R.string.auto_tunneling),
|
text = stringResource(R.string.auto_tunneling),
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ScaledSwitch(
|
||||||
settings.isAutoTunnelEnabled,
|
appUiState.settings.isAutoTunnelEnabled,
|
||||||
onClick = {
|
onClick = {
|
||||||
onToggle()
|
onToggle()
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|
|
@ -9,8 +9,6 @@ import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
|
||||||
import androidx.compose.ui.focus.focusRequester
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
@ -182,14 +182,14 @@ fun TunnelRowItem(
|
||||||
ScaledSwitch(
|
ScaledSwitch(
|
||||||
modifier = Modifier.focusRequester(itemFocusRequester),
|
modifier = Modifier.focusRequester(itemFocusRequester),
|
||||||
checked = isActive,
|
checked = isActive,
|
||||||
onClick = onSwitchClick
|
onClick = onSwitchClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ScaledSwitch(
|
ScaledSwitch(
|
||||||
modifier = Modifier.focusRequester(itemFocusRequester),
|
modifier = Modifier.focusRequester(itemFocusRequester),
|
||||||
checked = isActive,
|
checked = isActive,
|
||||||
onClick = onSwitchClick
|
onClick = onSwitchClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,14 +46,18 @@ fun TunnelStatisticsRow(statistics: TunnelStatistics?, tunnelConfig: TunnelConfi
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.peer).lowercase() + ": $peerId", style = MaterialTheme.typography.bodySmall)
|
Text(
|
||||||
Text("tx: $peerTxMB MB", style = MaterialTheme.typography.bodySmall)
|
stringResource(R.string.peer).lowercase() + ": $peerId",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.outline,
|
||||||
|
)
|
||||||
|
Text("tx: $peerTxMB MB", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.outline)
|
||||||
}
|
}
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.handshake) + ": $handshake", style = MaterialTheme.typography.bodySmall)
|
Text(stringResource(R.string.handshake) + ": $handshake", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.outline)
|
||||||
Text("rx: $peerRxMB MB", style = MaterialTheme.typography.bodySmall)
|
Text("rx: $peerRxMB MB", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.outline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,10 @@ import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.systemBars
|
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Edit
|
import androidx.compose.material.icons.outlined.Edit
|
||||||
import androidx.compose.material.icons.outlined.NetworkPing
|
import androidx.compose.material.icons.outlined.NetworkPing
|
||||||
|
@ -27,18 +22,11 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
|
||||||
import androidx.compose.ui.input.key.Key
|
|
||||||
import androidx.compose.ui.input.key.KeyEventType
|
|
||||||
import androidx.compose.ui.input.key.key
|
|
||||||
import androidx.compose.ui.input.key.onKeyEvent
|
|
||||||
import androidx.compose.ui.input.key.type
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
@ -56,7 +44,6 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.compon
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -84,7 +71,7 @@ fun OptionsScreen(optionsViewModel: OptionsViewModel = hiltViewModel(), appUiSta
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
|
@ -101,7 +88,12 @@ fun OptionsScreen(optionsViewModel: OptionsViewModel = hiltViewModel(), appUiSta
|
||||||
listOf(
|
listOf(
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.Star,
|
Icons.Outlined.Star,
|
||||||
title = { Text(stringResource(R.string.primary_tunnel), style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)) },
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.primary_tunnel),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
|
},
|
||||||
description = {
|
description = {
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.set_primary_tunnel),
|
stringResource(R.string.set_primary_tunnel),
|
||||||
|
@ -114,7 +106,7 @@ fun OptionsScreen(optionsViewModel: OptionsViewModel = hiltViewModel(), appUiSta
|
||||||
onClick = { optionsViewModel.onTogglePrimaryTunnel(config) },
|
onClick = { optionsViewModel.onTogglePrimaryTunnel(config) },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = { optionsViewModel.onTogglePrimaryTunnel(config) }
|
onClick = { optionsViewModel.onTogglePrimaryTunnel(config) },
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.PhoneAndroid,
|
Icons.Outlined.PhoneAndroid,
|
||||||
|
@ -131,7 +123,7 @@ fun OptionsScreen(optionsViewModel: OptionsViewModel = hiltViewModel(), appUiSta
|
||||||
onClick = { optionsViewModel.onToggleIsMobileDataTunnel(config) },
|
onClick = { optionsViewModel.onToggleIsMobileDataTunnel(config) },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = { optionsViewModel.onToggleIsMobileDataTunnel(config) }
|
onClick = { optionsViewModel.onToggleIsMobileDataTunnel(config) },
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.NetworkPing,
|
Icons.Outlined.NetworkPing,
|
||||||
|
@ -147,7 +139,7 @@ fun OptionsScreen(optionsViewModel: OptionsViewModel = hiltViewModel(), appUiSta
|
||||||
onClick = { optionsViewModel.onToggleRestartOnPing(config) },
|
onClick = { optionsViewModel.onToggleRestartOnPing(config) },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = { optionsViewModel.onToggleRestartOnPing(config) }
|
onClick = { optionsViewModel.onToggleRestartOnPing(config) },
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
title = {
|
title = {
|
||||||
|
@ -180,24 +172,25 @@ fun OptionsScreen(optionsViewModel: OptionsViewModel = hiltViewModel(), appUiSta
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
description = {
|
description = {
|
||||||
TrustedNetworkTextBox(
|
TrustedNetworkTextBox(
|
||||||
config.tunnelNetworks, onDelete = { optionsViewModel.onDeleteRunSSID(it, config) },
|
config.tunnelNetworks,
|
||||||
|
onDelete = { optionsViewModel.onDeleteRunSSID(it, config) },
|
||||||
currentText = currentText,
|
currentText = currentText,
|
||||||
onSave = { optionsViewModel.onSaveRunSSID(it, config) },
|
onSave = { optionsViewModel.onSaveRunSSID(it, config) },
|
||||||
onValueChange = { currentText = it },
|
onValueChange = { currentText = it },
|
||||||
supporting = { if(appUiState.generalState.isWildcardsEnabled) {
|
supporting = {
|
||||||
|
if (appUiState.settings.isWildcardsEnabled) {
|
||||||
WildcardsLabel()
|
WildcardsLabel()
|
||||||
}}
|
}
|
||||||
)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
)
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,13 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings
|
package com.zaneschepke.wireguardautotunnel.ui.screens.settings
|
||||||
|
|
||||||
import android.content.Context.POWER_SERVICE
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.net.VpnService
|
|
||||||
import android.os.PowerManager
|
|
||||||
import android.provider.Settings
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
||||||
import androidx.activity.result.ActivityResult
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.focusable
|
import androidx.compose.foundation.focusable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.systemBars
|
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
@ -49,7 +36,6 @@ import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
|
@ -63,13 +49,9 @@ import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalFocusReques
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.VpnDeniedDialog
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.BackgroundLocationDialog
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.ForwardButton
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.ForwardButton
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.LocationServicesDialog
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.topPadding
|
import com.zaneschepke.wireguardautotunnel.ui.theme.topPadding
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchAppSettings
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchNotificationSettings
|
import com.zaneschepke.wireguardautotunnel.util.extensions.launchNotificationSettings
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchVpnSettings
|
import com.zaneschepke.wireguardautotunnel.util.extensions.launchVpnSettings
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||||
|
@ -91,114 +73,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
val isRunningOnTv = remember { context.isRunningOnTv() }
|
val isRunningOnTv = remember { context.isRunningOnTv() }
|
||||||
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
|
||||||
val settingsUiState by viewModel.uiState.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
var showVpnPermissionDialog by remember { mutableStateOf(false) }
|
|
||||||
var showLocationServicesAlertDialog by remember { mutableStateOf(false) }
|
|
||||||
var showAuthPrompt by remember { mutableStateOf(false) }
|
var showAuthPrompt by remember { mutableStateOf(false) }
|
||||||
var showLocationDialog by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
val startForResult =
|
|
||||||
rememberLauncherForActivityResult(
|
|
||||||
ActivityResultContracts.StartActivityForResult(),
|
|
||||||
) { result: ActivityResult ->
|
|
||||||
if (result.resultCode == RESULT_OK) {
|
|
||||||
result.data
|
|
||||||
// Handle the Intent
|
|
||||||
}
|
|
||||||
viewModel.setBatteryOptimizeDisableShown()
|
|
||||||
}
|
|
||||||
|
|
||||||
val vpnActivityResultState =
|
|
||||||
rememberLauncherForActivityResult(
|
|
||||||
ActivityResultContracts.StartActivityForResult(),
|
|
||||||
onResult = {
|
|
||||||
val accepted = (it.resultCode == RESULT_OK)
|
|
||||||
if (!accepted) {
|
|
||||||
showVpnPermissionDialog = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
fun isBatteryOptimizationsDisabled(): Boolean {
|
|
||||||
val pm = context.getSystemService(POWER_SERVICE) as PowerManager
|
|
||||||
return pm.isIgnoringBatteryOptimizations(context.packageName)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun requestBatteryOptimizationsDisabled() {
|
|
||||||
val intent =
|
|
||||||
Intent().apply {
|
|
||||||
action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
|
||||||
data = Uri.parse("package:${context.packageName}")
|
|
||||||
}
|
|
||||||
startForResult.launch(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fun handleAutoTunnelToggle() {
|
|
||||||
// if (!uiState.generalState.isBatteryOptimizationDisableShown &&
|
|
||||||
// !isBatteryOptimizationsDisabled() && !isRunningOnTv
|
|
||||||
// ) {
|
|
||||||
// return requestBatteryOptimizationsDisabled()
|
|
||||||
// }
|
|
||||||
// val intent = if (!uiState.settings.isKernelEnabled) {
|
|
||||||
// VpnService.prepare(context)
|
|
||||||
// } else {
|
|
||||||
// null
|
|
||||||
// }
|
|
||||||
// if (intent != null) return vpnActivityResultState.launch(intent)
|
|
||||||
// viewModel.onToggleAutoTunnel(context)
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// fun checkFineLocationGranted() {
|
|
||||||
// isBackgroundLocationGranted =
|
|
||||||
// if (!fineLocationState.status.isGranted) {
|
|
||||||
// false
|
|
||||||
// } else {
|
|
||||||
// viewModel.setLocationDisclosureShown()
|
|
||||||
// true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
// if (
|
|
||||||
// isRunningOnTv &&
|
|
||||||
// Build.VERSION.SDK_INT == Build.VERSION_CODES.Q
|
|
||||||
// ) {
|
|
||||||
// checkFineLocationGranted()
|
|
||||||
// } else {
|
|
||||||
// val backgroundLocationState =
|
|
||||||
// rememberPermissionState(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
|
|
||||||
// isBackgroundLocationGranted =
|
|
||||||
// if (!backgroundLocationState.status.isGranted) {
|
|
||||||
// false
|
|
||||||
// } else {
|
|
||||||
// SideEffect { viewModel.setLocationDisclosureShown() }
|
|
||||||
// true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
|
|
||||||
// checkFineLocationGranted()
|
|
||||||
// }
|
|
||||||
|
|
||||||
BackgroundLocationDialog(
|
|
||||||
showLocationDialog,
|
|
||||||
onDismiss = { showLocationDialog = false },
|
|
||||||
onAttest = { showLocationDialog = false },
|
|
||||||
)
|
|
||||||
|
|
||||||
// LocationServicesDialog(
|
|
||||||
// showLocationServicesAlertDialog,
|
|
||||||
// onDismiss = { showVpnPermissionDialog = false },
|
|
||||||
// onAttest = { handleAutoTunnelToggle() },
|
|
||||||
// )
|
|
||||||
|
|
||||||
VpnDeniedDialog(showVpnPermissionDialog, onDismiss = { showVpnPermissionDialog = false })
|
|
||||||
|
|
||||||
if (showAuthPrompt) {
|
if (showAuthPrompt) {
|
||||||
AuthorizationPrompt(
|
AuthorizationPrompt(
|
||||||
|
@ -231,12 +106,18 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
.padding(top = topPadding)
|
.padding(top = topPadding)
|
||||||
.padding(bottom = 40.dp.scaledHeight())
|
.padding(bottom = 40.dp.scaledHeight())
|
||||||
.padding(horizontal = 24.dp.scaledWidth())
|
.padding(horizontal = 24.dp.scaledWidth())
|
||||||
.then(if(!isRunningOnTv) Modifier.clickable(
|
.then(
|
||||||
|
if (!isRunningOnTv) {
|
||||||
|
Modifier.clickable(
|
||||||
indication = null,
|
indication = null,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
) {
|
) {
|
||||||
focusManager.clearFocus()
|
focusManager.clearFocus()
|
||||||
} else Modifier)
|
}
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
},
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
SurfaceSelectionGroupButton(
|
SurfaceSelectionGroupButton(
|
||||||
listOf(
|
listOf(
|
||||||
|
@ -256,12 +137,13 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
trailing = {
|
trailing = {
|
||||||
ForwardButton(Modifier.focusable().focusRequester(rootFocusRequester)) { navController.navigate(Route.AutoTunnel) }
|
ForwardButton(Modifier.focusable().focusRequester(rootFocusRequester)) { navController.navigate(Route.AutoTunnel) }
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
SurfaceSelectionGroupButton(
|
SurfaceSelectionGroupButton(
|
||||||
buildList {
|
buildList {
|
||||||
if (!isRunningOnTv) addAll(
|
if (!isRunningOnTv) {
|
||||||
|
addAll(
|
||||||
listOf(
|
listOf(
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Filled.AppShortcut,
|
Icons.Filled.AppShortcut,
|
||||||
|
@ -274,9 +156,10 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.enabled_app_shortcuts),
|
stringResource(R.string.enabled_app_shortcuts),
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface))
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onClick = { viewModel.onToggleShortcutsEnabled() }
|
onClick = { viewModel.onToggleShortcutsEnabled() },
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.VpnLock,
|
Icons.Outlined.VpnLock,
|
||||||
|
@ -297,16 +180,18 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.always_on_vpn_support),
|
stringResource(R.string.always_on_vpn_support),
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface))
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onClick = { viewModel.onToggleAlwaysOnVPN() }
|
onClick = { viewModel.onToggleAlwaysOnVPN() },
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.AdminPanelSettings,
|
Icons.Outlined.AdminPanelSettings,
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.kill_switch),
|
stringResource(R.string.kill_switch),
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface))
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
context.launchVpnSettings()
|
context.launchVpnSettings()
|
||||||
|
@ -314,9 +199,10 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
trailing = {
|
trailing = {
|
||||||
ForwardButton { context.launchVpnSettings() }
|
ForwardButton { context.launchVpnSettings() }
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
)
|
|
||||||
add(
|
add(
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.Restore,
|
Icons.Outlined.Restore,
|
||||||
|
@ -329,16 +215,18 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.restart_at_boot),
|
stringResource(R.string.restart_at_boot),
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface))
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onClick = { viewModel.onToggleRestartAtBoot() }
|
onClick = { viewModel.onToggleRestartAtBoot() },
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
},
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
SurfaceSelectionGroupButton(
|
SurfaceSelectionGroupButton(
|
||||||
listOf(SelectionItem(
|
listOf(
|
||||||
|
SelectionItem(
|
||||||
Icons.AutoMirrored.Outlined.ViewQuilt,
|
Icons.AutoMirrored.Outlined.ViewQuilt,
|
||||||
title = { Text(stringResource(R.string.appearance), style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)) },
|
title = { Text(stringResource(R.string.appearance), style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
|
@ -360,7 +248,12 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.Pin,
|
Icons.Outlined.Pin,
|
||||||
title = { Text(stringResource(R.string.enable_app_lock), style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)) },
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.enable_app_lock),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
|
},
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ScaledSwitch(
|
||||||
uiState.generalState.isPinLockEnabled,
|
uiState.generalState.isPinLockEnabled,
|
||||||
|
@ -374,16 +267,21 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = { if (uiState.generalState.isPinLockEnabled) {
|
onClick = {
|
||||||
|
if (uiState.generalState.isPinLockEnabled) {
|
||||||
appViewModel.onPinLockDisabled()
|
appViewModel.onPinLockDisabled()
|
||||||
} else {
|
} else {
|
||||||
PinManager.initialize(context)
|
PinManager.initialize(context)
|
||||||
navController.navigate(Route.Lock)
|
navController.navigate(Route.Lock)
|
||||||
} }
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
))
|
|
||||||
|
|
||||||
if(!isRunningOnTv) SurfaceSelectionGroupButton(listOf(
|
if (!isRunningOnTv) {
|
||||||
|
SurfaceSelectionGroupButton(
|
||||||
|
listOf(
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.Code,
|
Icons.Outlined.Code,
|
||||||
title = { Text(stringResource(R.string.kernel), style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)) },
|
title = { Text(stringResource(R.string.kernel), style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)) },
|
||||||
|
@ -400,344 +298,36 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
enabled = !(
|
enabled = !(
|
||||||
uiState.settings.isAutoTunnelEnabled ||
|
uiState.settings.isAutoTunnelEnabled ||
|
||||||
uiState.settings.isAlwaysOnVpnEnabled ||
|
uiState.settings.isAlwaysOnVpnEnabled ||
|
||||||
(uiState.vpnState.status == TunnelState.UP) ||
|
(uiState.vpnState.status == TunnelState.UP)
|
||||||
!settingsUiState.isKernelAvailable
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onToggleKernelMode()
|
viewModel.onToggleKernelMode()
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
))
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if(!isRunningOnTv) SurfaceSelectionGroupButton(
|
if (!isRunningOnTv) {
|
||||||
|
SurfaceSelectionGroupButton(
|
||||||
listOf(
|
listOf(
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.FolderZip,
|
Icons.Outlined.FolderZip,
|
||||||
title = { Text(stringResource(R.string.export_configs), style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)) },
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.export_configs),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
if (uiState.tunnels.isEmpty()) return@SelectionItem context.showToast(R.string.tunnel_required)
|
if (uiState.tunnels.isEmpty()) return@SelectionItem context.showToast(R.string.tunnel_required)
|
||||||
showAuthPrompt = true
|
showAuthPrompt = true
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Surface(
|
|
||||||
// tonalElevation = 2.dp,
|
|
||||||
// shadowElevation = 2.dp,
|
|
||||||
// shape = RoundedCornerShape(12.dp),
|
|
||||||
// color = MaterialTheme.colorScheme.surface,
|
|
||||||
// modifier =
|
|
||||||
// (
|
|
||||||
// if (isRunningOnTv) {
|
|
||||||
// Modifier
|
|
||||||
// .height(IntrinsicSize.Min)
|
|
||||||
// .fillMaxWidth(fillMaxWidth)
|
|
||||||
// .padding(top = 10.dp)
|
|
||||||
// } else {
|
|
||||||
// Modifier
|
|
||||||
// .fillMaxWidth(fillMaxWidth)
|
|
||||||
// .padding(top = 20.dp)
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// .padding(bottom = 10.dp),
|
|
||||||
// ) {
|
|
||||||
// Column(
|
|
||||||
// horizontalAlignment = Alignment.Start,
|
|
||||||
// verticalArrangement = Arrangement.Top,
|
|
||||||
// modifier = Modifier.padding(15.dp),
|
|
||||||
// ) {
|
|
||||||
// SectionTitle(
|
|
||||||
// title = stringResource(id = R.string.auto_tunneling),
|
|
||||||
// padding = screenPadding,
|
|
||||||
// )
|
|
||||||
// ConfigurationToggle(
|
|
||||||
// stringResource(id = R.string.tunnel_on_wifi),
|
|
||||||
// enabled = !uiState.settings.isAlwaysOnVpnEnabled,
|
|
||||||
// checked = uiState.settings.isTunnelOnWifiEnabled,
|
|
||||||
// onCheckChanged = { checked ->
|
|
||||||
// if (!checked || settingsUiState.isRooted) viewModel.onToggleTunnelOnWifi().also { return@ConfigurationToggle }
|
|
||||||
// onAutoTunnelWifiChecked()
|
|
||||||
// },
|
|
||||||
// modifier =
|
|
||||||
// if (uiState.settings.isAutoTunnelEnabled) {
|
|
||||||
// Modifier
|
|
||||||
// } else {
|
|
||||||
// Modifier
|
|
||||||
// .focusRequester(focusRequester)
|
|
||||||
// },
|
|
||||||
// )
|
|
||||||
// if (uiState.settings.isTunnelOnWifiEnabled) {
|
|
||||||
// Column {
|
|
||||||
// FlowRow(
|
|
||||||
// modifier =
|
|
||||||
// Modifier
|
|
||||||
// .padding(screenPadding)
|
|
||||||
// .fillMaxWidth(),
|
|
||||||
// horizontalArrangement = Arrangement.spacedBy(5.dp),
|
|
||||||
// ) {
|
|
||||||
// uiState.settings.trustedNetworkSSIDs.forEach { ssid ->
|
|
||||||
// ClickableIconButton(
|
|
||||||
// onClick = {
|
|
||||||
// if (isRunningOnTv) {
|
|
||||||
// focusRequester.requestFocus()
|
|
||||||
// viewModel.onDeleteTrustedSSID(ssid)
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// onIconClick = {
|
|
||||||
// if (isRunningOnTv) focusRequester.requestFocus()
|
|
||||||
// viewModel.onDeleteTrustedSSID(ssid)
|
|
||||||
// },
|
|
||||||
// text = ssid,
|
|
||||||
// icon = Icons.Filled.Close,
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// if (uiState.settings.trustedNetworkSSIDs.isEmpty()) {
|
|
||||||
// Text(
|
|
||||||
// stringResource(R.string.none),
|
|
||||||
// fontStyle = FontStyle.Italic,
|
|
||||||
// style = MaterialTheme.typography.bodySmall,
|
|
||||||
// color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// OutlinedTextField(
|
|
||||||
// value = currentText,
|
|
||||||
// onValueChange = { currentText = it },
|
|
||||||
// label = { Text(stringResource(R.string.add_trusted_ssid)) },
|
|
||||||
// modifier =
|
|
||||||
// Modifier
|
|
||||||
// .padding(
|
|
||||||
// start = screenPadding,
|
|
||||||
// top = 5.dp,
|
|
||||||
// bottom = 10.dp,
|
|
||||||
// ),
|
|
||||||
// supportingText = { WildcardSupportingLabel { context.openWebUrl(it) } },
|
|
||||||
// maxLines = 1,
|
|
||||||
// keyboardOptions =
|
|
||||||
// KeyboardOptions(
|
|
||||||
// capitalization = KeyboardCapitalization.None,
|
|
||||||
// imeAction = ImeAction.Done,
|
|
||||||
// ),
|
|
||||||
// keyboardActions = KeyboardActions(onDone = { saveTrustedSSID() }),
|
|
||||||
// trailingIcon = {
|
|
||||||
// if (currentText != "") {
|
|
||||||
// IconButton(onClick = { saveTrustedSSID() }) {
|
|
||||||
// Icon(
|
|
||||||
// imageVector = Icons.Outlined.Add,
|
|
||||||
// contentDescription =
|
|
||||||
// if (currentText == "") {
|
|
||||||
// stringResource(
|
|
||||||
// id =
|
|
||||||
// R.string
|
|
||||||
// .trusted_ssid_empty_description,
|
|
||||||
// )
|
|
||||||
// } else {
|
|
||||||
// stringResource(
|
|
||||||
// id =
|
|
||||||
// R.string
|
|
||||||
// .trusted_ssid_value_description,
|
|
||||||
// )
|
|
||||||
// },
|
|
||||||
// tint = MaterialTheme.colorScheme.primary,
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// ConfigurationToggle(
|
|
||||||
// stringResource(R.string.tunnel_mobile_data),
|
|
||||||
// enabled = !uiState.settings.isAlwaysOnVpnEnabled,
|
|
||||||
// checked = uiState.settings.isTunnelOnMobileDataEnabled,
|
|
||||||
// onCheckChanged = { viewModel.onToggleTunnelOnMobileData() },
|
|
||||||
// )
|
|
||||||
// ConfigurationToggle(
|
|
||||||
// stringResource(id = R.string.tunnel_on_ethernet),
|
|
||||||
// enabled = !uiState.settings.isAlwaysOnVpnEnabled,
|
|
||||||
// checked = uiState.settings.isTunnelOnEthernetEnabled,
|
|
||||||
// onCheckChanged = { viewModel.onToggleTunnelOnEthernet() },
|
|
||||||
// )
|
|
||||||
// ConfigurationToggle(
|
|
||||||
// stringResource(R.string.restart_on_ping),
|
|
||||||
// checked = uiState.settings.isPingEnabled,
|
|
||||||
// onCheckChanged = { viewModel.onToggleRestartOnPing() },
|
|
||||||
// )
|
|
||||||
// Row(
|
|
||||||
// verticalAlignment = Alignment.CenterVertically,
|
|
||||||
// modifier =
|
|
||||||
// (
|
|
||||||
// if (!uiState.settings.isAutoTunnelEnabled) {
|
|
||||||
// Modifier
|
|
||||||
// } else {
|
|
||||||
// Modifier.focusRequester(
|
|
||||||
// focusRequester,
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// .fillMaxSize()
|
|
||||||
// .padding(top = 5.dp),
|
|
||||||
// horizontalArrangement = Arrangement.Center,
|
|
||||||
// ) {
|
|
||||||
// TextButton(
|
|
||||||
// onClick = {
|
|
||||||
// if (uiState.tunnels.isEmpty()) return@TextButton context.showToast(R.string.tunnel_required)
|
|
||||||
// handleAutoTunnelToggle()
|
|
||||||
// },
|
|
||||||
// ) {
|
|
||||||
// val autoTunnelButtonText =
|
|
||||||
// if (uiState.settings.isAutoTunnelEnabled) {
|
|
||||||
// stringResource(R.string.disable_auto_tunnel)
|
|
||||||
// } else {
|
|
||||||
// stringResource(id = R.string.enable_auto_tunnel)
|
|
||||||
// }
|
|
||||||
// Text(autoTunnelButtonText)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Surface(
|
|
||||||
// tonalElevation = 2.dp,
|
|
||||||
// shadowElevation = 2.dp,
|
|
||||||
// shape = RoundedCornerShape(12.dp),
|
|
||||||
// color = MaterialTheme.colorScheme.surface,
|
|
||||||
// modifier =
|
|
||||||
// Modifier
|
|
||||||
// .fillMaxWidth(fillMaxWidth)
|
|
||||||
// .padding(vertical = 10.dp),
|
|
||||||
// ) {
|
|
||||||
// Column(
|
|
||||||
// horizontalAlignment = Alignment.Start,
|
|
||||||
// verticalArrangement = Arrangement.Top,
|
|
||||||
// modifier = Modifier.padding(15.dp),
|
|
||||||
// ) {
|
|
||||||
// SectionTitle(
|
|
||||||
// title = stringResource(id = R.string.backend),
|
|
||||||
// padding = screenPadding,
|
|
||||||
// )
|
|
||||||
// ConfigurationToggle(
|
|
||||||
// stringResource(R.string.use_kernel),
|
|
||||||
// enabled =
|
|
||||||
// !(
|
|
||||||
// uiState.settings.isAutoTunnelEnabled ||
|
|
||||||
// uiState.settings.isAlwaysOnVpnEnabled ||
|
|
||||||
// (uiState.vpnState.status == TunnelState.UP) ||
|
|
||||||
// !settingsUiState.isKernelAvailable
|
|
||||||
// ),
|
|
||||||
// checked = uiState.settings.isKernelEnabled,
|
|
||||||
// onCheckChanged = {
|
|
||||||
// viewModel.onToggleKernelMode()
|
|
||||||
// },
|
|
||||||
// )
|
|
||||||
// Row(
|
|
||||||
// verticalAlignment = Alignment.CenterVertically,
|
|
||||||
// modifier =
|
|
||||||
// Modifier
|
|
||||||
// .fillMaxSize()
|
|
||||||
// .padding(top = 5.dp),
|
|
||||||
// horizontalArrangement = Arrangement.Center,
|
|
||||||
// ) {
|
|
||||||
// TextButton(
|
|
||||||
// onClick = {
|
|
||||||
// viewModel.onRequestRoot()
|
|
||||||
// },
|
|
||||||
// ) {
|
|
||||||
// Text(stringResource(R.string.request_root))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Surface(
|
|
||||||
// tonalElevation = 2.dp,
|
|
||||||
// shadowElevation = 2.dp,
|
|
||||||
// shape = RoundedCornerShape(12.dp),
|
|
||||||
// color = MaterialTheme.colorScheme.surface,
|
|
||||||
// modifier =
|
|
||||||
// Modifier
|
|
||||||
// .fillMaxWidth(fillMaxWidth)
|
|
||||||
// .padding(vertical = 10.dp)
|
|
||||||
// .padding(bottom = 10.dp),
|
|
||||||
// ) {
|
|
||||||
// Column(
|
|
||||||
// horizontalAlignment = Alignment.Start,
|
|
||||||
// verticalArrangement = Arrangement.Top,
|
|
||||||
// modifier = Modifier.padding(15.dp),
|
|
||||||
// ) {
|
|
||||||
// SectionTitle(
|
|
||||||
// title = stringResource(id = R.string.other),
|
|
||||||
// padding = screenPadding,
|
|
||||||
// )
|
|
||||||
// if (!isRunningOnTv) {
|
|
||||||
// ConfigurationToggle(
|
|
||||||
// stringResource(R.string.always_on_vpn_support),
|
|
||||||
// enabled = !(
|
|
||||||
// (
|
|
||||||
// uiState.settings.isTunnelOnWifiEnabled ||
|
|
||||||
// uiState.settings.isTunnelOnEthernetEnabled ||
|
|
||||||
// uiState.settings.isTunnelOnMobileDataEnabled
|
|
||||||
// ) &&
|
|
||||||
// uiState.settings.isAutoTunnelEnabled
|
|
||||||
// ),
|
|
||||||
// checked = uiState.settings.isAlwaysOnVpnEnabled,
|
|
||||||
// onCheckChanged = { viewModel.onToggleAlwaysOnVPN() },
|
|
||||||
// )
|
|
||||||
// ConfigurationToggle(
|
|
||||||
// stringResource(R.string.enabled_app_shortcuts),
|
|
||||||
// enabled = true,
|
|
||||||
// checked = uiState.settings.isShortcutsEnabled,
|
|
||||||
// onCheckChanged = { viewModel.onToggleShortcutsEnabled() },
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// ConfigurationToggle(
|
|
||||||
// stringResource(R.string.restart_at_boot),
|
|
||||||
// enabled = true,
|
|
||||||
// checked = uiState.settings.isRestoreOnBootEnabled,
|
|
||||||
// onCheckChanged = {
|
|
||||||
// viewModel.onToggleRestartAtBoot()
|
|
||||||
// },
|
|
||||||
// )
|
|
||||||
// ConfigurationToggle(
|
|
||||||
// stringResource(R.string.enable_app_lock),
|
|
||||||
// enabled = true,
|
|
||||||
// checked = uiState.generalState.isPinLockEnabled,
|
|
||||||
// onCheckChanged = {
|
|
||||||
// if (uiState.generalState.isPinLockEnabled) {
|
|
||||||
// appViewModel.onPinLockDisabled()
|
|
||||||
// } else {
|
|
||||||
// // TODO may want to show a dialog before proceeding in the future
|
|
||||||
// PinManager.initialize(WireGuardAutoTunnel.instance)
|
|
||||||
// navController.navigate(Route.Lock)
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// )
|
|
||||||
// if (!isRunningOnTv) {
|
|
||||||
// Row(
|
|
||||||
// verticalAlignment = Alignment.CenterVertically,
|
|
||||||
// modifier =
|
|
||||||
// Modifier
|
|
||||||
// .fillMaxSize()
|
|
||||||
// .padding(top = 5.dp),
|
|
||||||
// horizontalArrangement = Arrangement.Center,
|
|
||||||
// ) {
|
|
||||||
// TextButton(
|
|
||||||
// enabled = !didExportFiles,
|
|
||||||
// onClick = {
|
|
||||||
// if (uiState.tunnels.isEmpty()) return@TextButton context.showToast(R.string.tunnel_required)
|
|
||||||
// showAuthPrompt = true
|
|
||||||
// },
|
|
||||||
// ) {
|
|
||||||
// Text(stringResource(R.string.export_configs))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings
|
|
||||||
|
|
||||||
data class SettingsUiState(
|
|
||||||
val isRooted: Boolean = false,
|
|
||||||
val isKernelAvailable: Boolean = false,
|
|
||||||
)
|
|
|
@ -10,19 +10,14 @@ import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
||||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchShareFile
|
import com.zaneschepke.wireguardautotunnel.util.extensions.launchShareFile
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.onStart
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
@ -39,16 +34,6 @@ constructor(
|
||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _uiState = MutableStateFlow(SettingsUiState())
|
|
||||||
val uiState = _uiState.onStart {
|
|
||||||
_uiState.update {
|
|
||||||
it.copy(isKernelAvailable = isKernelSupported(), isRooted = isRooted())
|
|
||||||
}
|
|
||||||
}.stateIn(
|
|
||||||
viewModelScope,
|
|
||||||
SharingStarted.WhileSubscribed(Constants.SUBSCRIPTION_TIMEOUT),
|
|
||||||
SettingsUiState(),
|
|
||||||
)
|
|
||||||
private val settings = appDataRepository.settings.getSettingsFlow()
|
private val settings = appDataRepository.settings.getSettingsFlow()
|
||||||
.stateIn(viewModelScope, SharingStarted.Eagerly, Settings())
|
.stateIn(viewModelScope, SharingStarted.Eagerly, Settings())
|
||||||
|
|
||||||
|
@ -56,10 +41,6 @@ constructor(
|
||||||
appDataRepository.appState.setLocationDisclosureShown(true)
|
appDataRepository.appState.setLocationDisclosureShown(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setBatteryOptimizeDisableShown() = viewModelScope.launch {
|
|
||||||
appDataRepository.appState.setBatteryOptimizationDisableShown(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onToggleAlwaysOnVPN() = viewModelScope.launch {
|
fun onToggleAlwaysOnVPN() = viewModelScope.launch {
|
||||||
with(settings.value) {
|
with(settings.value) {
|
||||||
appDataRepository.settings.save(
|
appDataRepository.settings.save(
|
||||||
|
@ -90,23 +71,11 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onToggleAmnezia() = viewModelScope.launch {
|
|
||||||
with(settings.value) {
|
|
||||||
if (isKernelEnabled) {
|
|
||||||
saveKernelMode(false)
|
|
||||||
}
|
|
||||||
appDataRepository.settings.save(
|
|
||||||
copy(
|
|
||||||
isAmneziaEnabled = !isAmneziaEnabled,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onToggleKernelMode() = viewModelScope.launch {
|
fun onToggleKernelMode() = viewModelScope.launch {
|
||||||
with(settings.value) {
|
with(settings.value) {
|
||||||
if (!isKernelEnabled) {
|
if (!isKernelEnabled) {
|
||||||
requestRoot().onSuccess {
|
requestRoot().onSuccess {
|
||||||
|
if (!isKernelSupported()) return@onSuccess SnackbarController.showMessage(StringValue.StringResource(R.string.kernel_not_supported))
|
||||||
appDataRepository.settings.save(
|
appDataRepository.settings.save(
|
||||||
copy(
|
copy(
|
||||||
isKernelEnabled = true,
|
isKernelEnabled = true,
|
||||||
|
@ -136,17 +105,6 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun isRooted(): Boolean {
|
|
||||||
return try {
|
|
||||||
withContext(ioDispatcher) {
|
|
||||||
rootShell.get().start()
|
|
||||||
}
|
|
||||||
true
|
|
||||||
} catch (_: Exception) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun requestRoot(): Result<Unit> {
|
private suspend fun requestRoot(): Result<Unit> {
|
||||||
return withContext(ioDispatcher) {
|
return withContext(ioDispatcher) {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
|
@ -158,10 +116,6 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onRequestRoot() = viewModelScope.launch {
|
|
||||||
requestRoot()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun exportAllConfigs(context: Context) = viewModelScope.launch {
|
fun exportAllConfigs(context: Context) = viewModelScope.launch {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
val shareFile = fileUtils.createNewShareFile("wg-export_${Instant.now().epochSecond}.zip")
|
val shareFile = fileUtils.createNewShareFile("wg-export_${Instant.now().epochSecond}.zip")
|
||||||
|
|
|
@ -32,7 +32,7 @@ fun AppearanceScreen() {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopNavBar(stringResource(R.string.appearance))
|
TopNavBar(stringResource(R.string.appearance))
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
|
@ -51,7 +51,7 @@ fun AppearanceScreen() {
|
||||||
onClick = { navController.navigate(Route.Language) },
|
onClick = { navController.navigate(Route.Language) },
|
||||||
trailing = {
|
trailing = {
|
||||||
ForwardButton { navController.navigate(Route.Language) }
|
ForwardButton { navController.navigate(Route.Language) }
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -63,7 +63,7 @@ fun AppearanceScreen() {
|
||||||
onClick = { navController.navigate(Route.Display) },
|
onClick = { navController.navigate(Route.Display) },
|
||||||
trailing = {
|
trailing = {
|
||||||
ForwardButton { navController.navigate(Route.Display) }
|
ForwardButton { navController.navigate(Route.Display) }
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,11 +2,8 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.settings.appearance.displ
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.systemBars
|
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
@ -24,11 +21,10 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DisplayScreen(appUiState: AppUiState, viewModel: DisplayViewModel = hiltViewModel()) {
|
fun DisplayScreen(appUiState: AppUiState, viewModel: DisplayViewModel = hiltViewModel()) {
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopNavBar(stringResource(R.string.display_theme))
|
TopNavBar(stringResource(R.string.display_theme))
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
|
|
|
@ -12,7 +12,7 @@ import javax.inject.Inject
|
||||||
class DisplayViewModel
|
class DisplayViewModel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val appStateRepository: AppStateRepository
|
private val appStateRepository: AppStateRepository,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
fun onThemeChange(theme: Theme) = viewModelScope.launch {
|
fun onThemeChange(theme: Theme) = viewModelScope.launch {
|
||||||
|
|
|
@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.systemBars
|
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
@ -69,7 +68,7 @@ fun LanguageScreen(localeStorage: LocaleStorage) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopNavBar(stringResource(R.string.language))
|
TopNavBar(stringResource(R.string.language))
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel
|
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.os.Build
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
@ -11,6 +12,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Code
|
||||||
import androidx.compose.material.icons.outlined.Filter1
|
import androidx.compose.material.icons.outlined.Filter1
|
||||||
import androidx.compose.material.icons.outlined.NetworkPing
|
import androidx.compose.material.icons.outlined.NetworkPing
|
||||||
import androidx.compose.material.icons.outlined.Security
|
import androidx.compose.material.icons.outlined.Security
|
||||||
|
@ -31,7 +33,6 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
|
@ -45,9 +46,12 @@ import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelec
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.components.TrustedNetworkTextBox
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.components.TrustedNetworkTextBox
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.components.WildcardsLabel
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.components.WildcardsLabel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.BackgroundLocationDialog
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.LearnMoreLinkLabel
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.LearnMoreLinkLabel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.LocationServicesDialog
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isLocationServicesEnabled
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isLocationServicesEnabled
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||||
|
@ -63,11 +67,12 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||||
var showLocationServicesAlertDialog by remember { mutableStateOf(false) }
|
var showLocationServicesAlertDialog by remember { mutableStateOf(false) }
|
||||||
var showLocationDialog by remember { mutableStateOf(false) }
|
var showLocationDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LaunchedEffect(uiState.settings.trustedNetworkSSIDs) {
|
fun checkFineLocationGranted() {
|
||||||
currentText = ""
|
isBackgroundLocationGranted = fineLocationState.status.isGranted
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onAutoTunnelWifiChecked() {
|
fun onAutoTunnelWifiChecked() {
|
||||||
|
if (uiState.settings.isTunnelOnWifiEnabled) viewModel.onToggleTunnelOnWifi().also { return }
|
||||||
when (false) {
|
when (false) {
|
||||||
isBackgroundLocationGranted -> showLocationDialog = true
|
isBackgroundLocationGranted -> showLocationDialog = true
|
||||||
fineLocationState.status.isGranted -> showLocationDialog = true
|
fineLocationState.status.isGranted -> showLocationDialog = true
|
||||||
|
@ -79,11 +84,39 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) checkFineLocationGranted()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
if (context.isRunningOnTv() && Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
|
||||||
|
checkFineLocationGranted()
|
||||||
|
} else {
|
||||||
|
val backgroundLocationState = rememberPermissionState(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
|
||||||
|
isBackgroundLocationGranted = backgroundLocationState.status.isGranted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(uiState.settings.trustedNetworkSSIDs) {
|
||||||
|
currentText = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationServicesDialog(
|
||||||
|
showLocationServicesAlertDialog,
|
||||||
|
onDismiss = { showLocationServicesAlertDialog = false },
|
||||||
|
onAttest = {
|
||||||
|
viewModel.onToggleTunnelOnWifi()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
BackgroundLocationDialog(
|
||||||
|
showLocationDialog,
|
||||||
|
onDismiss = { showLocationDialog = false },
|
||||||
|
onAttest = { showLocationDialog = false },
|
||||||
|
)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
contentWindowInsets = WindowInsets(0.dp),
|
contentWindowInsets = WindowInsets(0.dp),
|
||||||
topBar = {
|
topBar = {
|
||||||
TopNavBar(stringResource(R.string.auto_tunneling))
|
TopNavBar(stringResource(R.string.auto_tunneling))
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
|
@ -97,13 +130,14 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||||
) {
|
) {
|
||||||
SurfaceSelectionGroupButton(
|
SurfaceSelectionGroupButton(
|
||||||
buildList {
|
buildList {
|
||||||
add(
|
addAll(
|
||||||
|
listOf(
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.Wifi,
|
Icons.Outlined.Wifi,
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.tunnel_on_wifi),
|
stringResource(R.string.tunnel_on_wifi),
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
description = {
|
description = {
|
||||||
|
@ -113,18 +147,43 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||||
enabled = !uiState.settings.isAlwaysOnVpnEnabled,
|
enabled = !uiState.settings.isAlwaysOnVpnEnabled,
|
||||||
checked = uiState.settings.isTunnelOnWifiEnabled,
|
checked = uiState.settings.isTunnelOnWifiEnabled,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (!uiState.settings.isTunnelOnWifiEnabled || uiState.isRooted) viewModel.onToggleTunnelOnWifi()
|
if (uiState.settings.isWifiNameByShellEnabled) viewModel.onToggleTunnelOnWifi().also { return@ScaledSwitch }
|
||||||
.also { return@ScaledSwitch }
|
|
||||||
onAutoTunnelWifiChecked()
|
onAutoTunnelWifiChecked()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
if (!uiState.settings.isTunnelOnWifiEnabled || uiState.isRooted) viewModel.onToggleTunnelOnWifi()
|
if (uiState.settings.isWifiNameByShellEnabled) viewModel.onToggleTunnelOnWifi().also { return@SelectionItem }
|
||||||
.also { return@SelectionItem }
|
|
||||||
onAutoTunnelWifiChecked()
|
onAutoTunnelWifiChecked()
|
||||||
}
|
},
|
||||||
|
),
|
||||||
|
SelectionItem(
|
||||||
|
Icons.Outlined.Code,
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.wifi_name_via_shell),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
description = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.use_root_shell_for_wifi),
|
||||||
|
style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailing = {
|
||||||
|
ScaledSwitch(
|
||||||
|
checked = uiState.settings.isWifiNameByShellEnabled,
|
||||||
|
onClick = {
|
||||||
|
viewModel.onRootShellWifiToggle()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
viewModel.onRootShellWifiToggle()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
if (uiState.settings.isTunnelOnWifiEnabled) {
|
if (uiState.settings.isTunnelOnWifiEnabled) {
|
||||||
addAll(
|
addAll(
|
||||||
|
@ -134,7 +193,7 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.use_wildcards),
|
stringResource(R.string.use_wildcards),
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
description = {
|
description = {
|
||||||
|
@ -142,7 +201,7 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||||
},
|
},
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ScaledSwitch(
|
||||||
checked = uiState.generalState.isWildcardsEnabled,
|
checked = uiState.settings.isWildcardsEnabled,
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onToggleWildcards()
|
viewModel.onToggleWildcards()
|
||||||
},
|
},
|
||||||
|
@ -150,7 +209,7 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onToggleWildcards()
|
viewModel.onToggleWildcards()
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
title = {
|
title = {
|
||||||
|
@ -183,26 +242,28 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
description = {
|
description = {
|
||||||
TrustedNetworkTextBox(
|
TrustedNetworkTextBox(
|
||||||
uiState.settings.trustedNetworkSSIDs, onDelete = viewModel::onDeleteTrustedSSID,
|
uiState.settings.trustedNetworkSSIDs,
|
||||||
|
onDelete = viewModel::onDeleteTrustedSSID,
|
||||||
currentText = currentText,
|
currentText = currentText,
|
||||||
onSave = viewModel::onSaveTrustedSSID,
|
onSave = viewModel::onSaveTrustedSSID,
|
||||||
onValueChange = { currentText = it },
|
onValueChange = { currentText = it },
|
||||||
supporting = { if(uiState.generalState.isWildcardsEnabled) {
|
supporting = {
|
||||||
|
if (uiState.settings.isWildcardsEnabled) {
|
||||||
WildcardsLabel()
|
WildcardsLabel()
|
||||||
}}
|
}
|
||||||
)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
))
|
},
|
||||||
}
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
SurfaceSelectionGroupButton(
|
SurfaceSelectionGroupButton(
|
||||||
listOf(
|
listOf(
|
||||||
|
@ -223,7 +284,7 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onToggleTunnelOnMobileData()
|
viewModel.onToggleTunnelOnMobileData()
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.SettingsEthernet,
|
Icons.Outlined.SettingsEthernet,
|
||||||
|
@ -242,7 +303,7 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onToggleTunnelOnEthernet()
|
viewModel.onToggleTunnelOnEthernet()
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.NetworkPing,
|
Icons.Outlined.NetworkPing,
|
||||||
|
@ -260,9 +321,9 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onToggleRestartOnPing()
|
viewModel.onToggleRestartOnPing()
|
||||||
}
|
},
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,22 +2,29 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.wireguard.android.util.RootShell
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Provider
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class AutoTunnelViewModel
|
class AutoTunnelViewModel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val appDataRepository: AppDataRepository,
|
private val appDataRepository: AppDataRepository,
|
||||||
|
private val rootShell: Provider<RootShell>,
|
||||||
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val settings = appDataRepository.settings.getSettingsFlow()
|
private val settings = appDataRepository.settings.getSettingsFlow()
|
||||||
|
@ -37,29 +44,53 @@ constructor(
|
||||||
with(settings.value) {
|
with(settings.value) {
|
||||||
appDataRepository.settings.save(
|
appDataRepository.settings.save(
|
||||||
copy(
|
copy(
|
||||||
isTunnelOnMobileDataEnabled = !this.isTunnelOnMobileDataEnabled,
|
isTunnelOnMobileDataEnabled = !isTunnelOnMobileDataEnabled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onToggleWildcards() = viewModelScope.launch {
|
fun onToggleWildcards() = viewModelScope.launch {
|
||||||
val wildcards = appDataRepository.appState.isWildcardsEnabled()
|
with(settings.value) {
|
||||||
appDataRepository.appState.setWildcardsEnabled(
|
appDataRepository.settings.save(
|
||||||
!wildcards
|
copy(
|
||||||
|
isWildcardsEnabled = !isWildcardsEnabled,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onDeleteTrustedSSID(ssid: String) = viewModelScope.launch {
|
fun onDeleteTrustedSSID(ssid: String) = viewModelScope.launch {
|
||||||
with(settings.value) {
|
with(settings.value) {
|
||||||
appDataRepository.settings.save(
|
appDataRepository.settings.save(
|
||||||
copy(
|
copy(
|
||||||
trustedNetworkSSIDs = (this.trustedNetworkSSIDs - ssid).toMutableList(),
|
trustedNetworkSSIDs = (trustedNetworkSSIDs - ssid).toMutableList(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onRootShellWifiToggle() = viewModelScope.launch {
|
||||||
|
requestRoot().onSuccess {
|
||||||
|
with(settings.value) {
|
||||||
|
appDataRepository.settings.save(
|
||||||
|
copy(isWifiNameByShellEnabled = !isWifiNameByShellEnabled),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun requestRoot(): Result<Unit> {
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
|
kotlin.runCatching {
|
||||||
|
rootShell.get().start()
|
||||||
|
SnackbarController.showMessage(StringValue.StringResource(R.string.root_accepted))
|
||||||
|
}.onFailure {
|
||||||
|
SnackbarController.showMessage(StringValue.StringResource(R.string.error_root_denied))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onToggleTunnelOnEthernet() = viewModelScope.launch {
|
fun onToggleTunnelOnEthernet() = viewModelScope.launch {
|
||||||
with(settings.value) {
|
with(settings.value) {
|
||||||
appDataRepository.settings.save(
|
appDataRepository.settings.save(
|
||||||
|
|
|
@ -32,7 +32,14 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun TrustedNetworkTextBox(trustedNetworks: List<String>, onDelete: (ssid: String) -> Unit, currentText: String, onSave : (ssid: String) -> Unit, onValueChange: (network: String) -> Unit, supporting: @Composable () -> Unit) {
|
fun TrustedNetworkTextBox(
|
||||||
|
trustedNetworks: List<String>,
|
||||||
|
onDelete: (ssid: String) -> Unit,
|
||||||
|
currentText: String,
|
||||||
|
onSave: (ssid: String) -> Unit,
|
||||||
|
onValueChange: (network: String) -> Unit,
|
||||||
|
supporting: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(10.dp.scaledHeight())) {
|
Column(verticalArrangement = Arrangement.spacedBy(10.dp.scaledHeight())) {
|
||||||
FlowRow(
|
FlowRow(
|
||||||
|
@ -93,6 +100,5 @@ fun TrustedNetworkTextBox(trustedNetworks: List<String>, onDelete: (ssid: String
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WildcardsLabel() {
|
fun WildcardsLabel() {
|
||||||
Text(
|
Text(
|
||||||
|
|
|
@ -14,7 +14,7 @@ import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||||
fun ForwardButton(modifier: Modifier = Modifier.focusable(), onClick: () -> Unit) {
|
fun ForwardButton(modifier: Modifier = Modifier.focusable(), onClick: () -> Unit) {
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onClick = onClick
|
onClick = onClick,
|
||||||
) {
|
) {
|
||||||
val icon = Icons.AutoMirrored.Outlined.ArrowForward
|
val icon = Icons.AutoMirrored.Outlined.ArrowForward
|
||||||
Icon(icon, icon.name, Modifier.size(iconSize))
|
Icon(icon, icon.name, Modifier.size(iconSize))
|
||||||
|
|
|
@ -62,21 +62,30 @@ fun LocationDisclosureScreen(appViewModel: AppViewModel, appUiState: AppUiState)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.prominent_background_location_message),
|
stringResource(R.string.prominent_background_location_message),
|
||||||
style = MaterialTheme.typography.bodyLarge
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
)
|
)
|
||||||
SurfaceSelectionGroupButton(
|
SurfaceSelectionGroupButton(
|
||||||
listOf(
|
listOf(
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.LocationOn,
|
Icons.Outlined.LocationOn,
|
||||||
title = { Text(stringResource(R.string.launch_app_settings), style = MaterialTheme.typography.bodyLarge.copy(MaterialTheme.colorScheme.onSurface)) },
|
title = {
|
||||||
onClick = { context.launchAppSettings().also {
|
Text(
|
||||||
|
stringResource(R.string.launch_app_settings),
|
||||||
|
style = MaterialTheme.typography.bodyLarge.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
context.launchAppSettings().also {
|
||||||
appViewModel.setLocationDisclosureShown()
|
appViewModel.setLocationDisclosureShown()
|
||||||
} },
|
|
||||||
trailing = {
|
|
||||||
ForwardButton { context.launchAppSettings().also {
|
|
||||||
appViewModel.setLocationDisclosureShown()
|
|
||||||
} }
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
trailing = {
|
||||||
|
ForwardButton {
|
||||||
|
context.launchAppSettings().also {
|
||||||
|
appViewModel.setLocationDisclosureShown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -87,7 +96,7 @@ fun LocationDisclosureScreen(appViewModel: AppViewModel, appUiState: AppUiState)
|
||||||
onClick = { appViewModel.setLocationDisclosureShown() },
|
onClick = { appViewModel.setLocationDisclosureShown() },
|
||||||
trailing = {
|
trailing = {
|
||||||
ForwardButton { appViewModel.setLocationDisclosureShown() }
|
ForwardButton { appViewModel.setLocationDisclosureShown() }
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -52,18 +52,27 @@ fun SupportScreen() {
|
||||||
listOf(
|
listOf(
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Filled.Book,
|
Icons.Filled.Book,
|
||||||
title = { Text(stringResource(R.string.docs_description), style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)) },
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.docs_description),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
|
},
|
||||||
trailing = {
|
trailing = {
|
||||||
ForwardButton { context.openWebUrl(context.getString(R.string.docs_url)) }
|
ForwardButton { context.openWebUrl(context.getString(R.string.docs_url)) }
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
context.openWebUrl(context.getString(R.string.docs_url))
|
context.openWebUrl(context.getString(R.string.docs_url))
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Filled.LineStyle,
|
Icons.Filled.LineStyle,
|
||||||
title = { Text(stringResource(R.string.read_logs),
|
title = {
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)) },
|
Text(
|
||||||
|
stringResource(R.string.read_logs),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
|
},
|
||||||
trailing = {
|
trailing = {
|
||||||
ForwardButton {
|
ForwardButton {
|
||||||
navController.navigate(Route.Logs)
|
navController.navigate(Route.Logs)
|
||||||
|
@ -71,26 +80,36 @@ fun SupportScreen() {
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
navController.navigate(Route.Logs)
|
navController.navigate(Route.Logs)
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Filled.Policy,
|
Icons.Filled.Policy,
|
||||||
title = { Text(stringResource(R.string.privacy_policy), style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)) },
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.privacy_policy),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
|
},
|
||||||
trailing = {
|
trailing = {
|
||||||
ForwardButton { context.openWebUrl(context.getString(R.string.privacy_policy_url)) }
|
ForwardButton { context.openWebUrl(context.getString(R.string.privacy_policy_url)) }
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
context.openWebUrl(context.getString(R.string.privacy_policy_url))
|
context.openWebUrl(context.getString(R.string.privacy_policy_url))
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
SurfaceSelectionGroupButton(
|
SurfaceSelectionGroupButton(
|
||||||
listOf(
|
listOf(
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
ImageVector.vectorResource(R.drawable.telegram),
|
ImageVector.vectorResource(R.drawable.telegram),
|
||||||
title = { Text(stringResource(R.string.chat_description), style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)) },
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.chat_description),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
|
},
|
||||||
trailing = {
|
trailing = {
|
||||||
ForwardButton {
|
ForwardButton {
|
||||||
context.openWebUrl(context.getString(R.string.telegram_url))
|
context.openWebUrl(context.getString(R.string.telegram_url))
|
||||||
|
@ -98,7 +117,7 @@ fun SupportScreen() {
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
context.openWebUrl(context.getString(R.string.telegram_url))
|
context.openWebUrl(context.getString(R.string.telegram_url))
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
ImageVector.vectorResource(R.drawable.github),
|
ImageVector.vectorResource(R.drawable.github),
|
||||||
|
@ -110,11 +129,16 @@ fun SupportScreen() {
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
context.openWebUrl(context.getString(R.string.github_url))
|
context.openWebUrl(context.getString(R.string.github_url))
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Filled.Mail,
|
Icons.Filled.Mail,
|
||||||
title = { Text(stringResource(R.string.email_description), style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)) },
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.email_description),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
|
},
|
||||||
trailing = {
|
trailing = {
|
||||||
ForwardButton {
|
ForwardButton {
|
||||||
context.launchSupportEmail()
|
context.launchSupportEmail()
|
||||||
|
@ -122,9 +146,9 @@ fun SupportScreen() {
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
context.launchSupportEmail()
|
context.launchSupportEmail()
|
||||||
}
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
|
||||||
)
|
)
|
||||||
VersionLabel()
|
VersionLabel()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,8 @@ import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.systemBars
|
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
|
|
@ -12,8 +12,6 @@ val BalticSea = Color(0xFF1C1B1F)
|
||||||
val Brick = Color(0xFFCE4257)
|
val Brick = Color(0xFFCE4257)
|
||||||
val Straw = Color(0xFFD4C483)
|
val Straw = Color(0xFFD4C483)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
sealed class ThemeColors(
|
sealed class ThemeColors(
|
||||||
val background: Color,
|
val background: Color,
|
||||||
val surface: Color,
|
val surface: Color,
|
||||||
|
|
|
@ -40,14 +40,11 @@ enum class Theme {
|
||||||
AUTOMATIC,
|
AUTOMATIC,
|
||||||
LIGHT,
|
LIGHT,
|
||||||
DARK,
|
DARK,
|
||||||
DYNAMIC
|
DYNAMIC,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WireguardAutoTunnelTheme(
|
fun WireguardAutoTunnelTheme(theme: Theme = Theme.AUTOMATIC, content: @Composable () -> Unit) {
|
||||||
theme: Theme = Theme.AUTOMATIC,
|
|
||||||
content: @Composable () -> Unit,
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var isDark = isSystemInDarkTheme()
|
var isDark = isSystemInDarkTheme()
|
||||||
val autoTheme = if (isDark) DarkColorScheme else LightColorScheme
|
val autoTheme = if (isDark) DarkColorScheme else LightColorScheme
|
||||||
|
@ -68,7 +65,9 @@ fun WireguardAutoTunnelTheme(
|
||||||
} else {
|
} else {
|
||||||
dynamicLightColorScheme(context)
|
dynamicLightColorScheme(context)
|
||||||
}
|
}
|
||||||
} else autoTheme
|
} else {
|
||||||
|
autoTheme
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.zaneschepke.wireguardautotunnel.util
|
||||||
|
|
||||||
|
open class SingletonHolder<out T : Any, in A>(creator: (A) -> T) {
|
||||||
|
private var creator: ((A) -> T)? = creator
|
||||||
|
|
||||||
|
@Volatile private var instance: T? = null
|
||||||
|
|
||||||
|
fun getInstance(arg: A): T {
|
||||||
|
val i = instance
|
||||||
|
if (i != null) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
return synchronized(this) {
|
||||||
|
val i2 = instance
|
||||||
|
if (i2 != null) {
|
||||||
|
i2
|
||||||
|
} else {
|
||||||
|
val created = creator!!(arg)
|
||||||
|
instance = created
|
||||||
|
creator = null
|
||||||
|
created
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,10 +2,12 @@ package com.zaneschepke.wireguardautotunnel.util.extensions
|
||||||
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Context.POWER_SERVICE
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.PowerManager
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.service.quicksettings.TileService
|
import android.service.quicksettings.TileService
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
@ -13,7 +15,6 @@ import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.TextUnit
|
import androidx.compose.ui.unit.TextUnit
|
||||||
import androidx.core.location.LocationManagerCompat
|
import androidx.core.location.LocationManagerCompat
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.receiver.BackgroundActionReceiver
|
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tile.AutoTunnelControlTile
|
import com.zaneschepke.wireguardautotunnel.service.tile.AutoTunnelControlTile
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tile.TunnelControlTile
|
import com.zaneschepke.wireguardautotunnel.service.tile.TunnelControlTile
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
|
@ -34,6 +35,11 @@ fun Context.openWebUrl(url: String): Result<Unit> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Context.isBatteryOptimizationsDisabled(): Boolean {
|
||||||
|
val pm = getSystemService(POWER_SERVICE) as PowerManager
|
||||||
|
return pm.isIgnoringBatteryOptimizations(packageName)
|
||||||
|
}
|
||||||
|
|
||||||
val Context.actionBarSize
|
val Context.actionBarSize
|
||||||
get() = theme.obtainStyledAttributes(intArrayOf(android.R.attr.actionBarSize))
|
get() = theme.obtainStyledAttributes(intArrayOf(android.R.attr.actionBarSize))
|
||||||
.let { attrs -> attrs.getDimension(0, 0F).toInt().also { attrs.recycle() } }
|
.let { attrs -> attrs.getDimension(0, 0F).toInt().also { attrs.recycle() } }
|
||||||
|
@ -159,23 +165,23 @@ fun Context.launchAppSettings() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.startTunnelBackground(tunnelId: Int) {
|
// fun Context.startTunnelBackground(tunnelId: Int) {
|
||||||
sendBroadcast(
|
// sendBroadcast(
|
||||||
Intent(this, BackgroundActionReceiver::class.java).apply {
|
// Intent(this, BackgroundActionReceiver::class.java).apply {
|
||||||
action = BackgroundActionReceiver.ACTION_CONNECT
|
// action = BackgroundActionReceiver.ACTION_CONNECT
|
||||||
putExtra(BackgroundActionReceiver.TUNNEL_ID_EXTRA_KEY, tunnelId)
|
// putExtra(BackgroundActionReceiver.TUNNEL_ID_EXTRA_KEY, tunnelId)
|
||||||
},
|
// },
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun Context.stopTunnelBackground(tunnelId: Int) {
|
// fun Context.stopTunnelBackground(tunnelId: Int) {
|
||||||
sendBroadcast(
|
// sendBroadcast(
|
||||||
Intent(this, BackgroundActionReceiver::class.java).apply {
|
// Intent(this, BackgroundActionReceiver::class.java).apply {
|
||||||
action = BackgroundActionReceiver.ACTION_DISCONNECT
|
// action = BackgroundActionReceiver.ACTION_DISCONNECT
|
||||||
putExtra(BackgroundActionReceiver.TUNNEL_ID_EXTRA_KEY, tunnelId)
|
// putExtra(BackgroundActionReceiver.TUNNEL_ID_EXTRA_KEY, tunnelId)
|
||||||
},
|
// },
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
fun Context.requestTunnelTileServiceStateUpdate() {
|
fun Context.requestTunnelTileServiceStateUpdate() {
|
||||||
TileService.requestListeningState(
|
TileService.requestListeningState(
|
||||||
|
|
|
@ -28,9 +28,9 @@ fun String.extractNameAndNumber(): Pair<String, Int>? {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun List<String>.isMatchingToWildcardList(value: String): Boolean {
|
fun List<String>.isMatchingToWildcardList(value: String): Boolean {
|
||||||
val excludeValues = this.filter { it.startsWith("!") }.map { it.removePrefix("!").toRegexWithWildcards() }
|
val excludeValues = this.filter { it.startsWith("!") }.map { it.removePrefix("!").transformWildcardsToRegex() }
|
||||||
Timber.d("Excluded values: $excludeValues")
|
Timber.d("Excluded values: $excludeValues")
|
||||||
val includedValues = this.filter { !it.startsWith("!") }.map { it.toRegexWithWildcards() }
|
val includedValues = this.filter { !it.startsWith("!") }.map { it.transformWildcardsToRegex() }
|
||||||
Timber.d("Included values: $includedValues")
|
Timber.d("Included values: $includedValues")
|
||||||
val matches = includedValues.filter { it.matches(value) }
|
val matches = includedValues.filter { it.matches(value) }
|
||||||
val excludedMatches = excludeValues.filter { it.matches(value) }
|
val excludedMatches = excludeValues.filter { it.matches(value) }
|
||||||
|
@ -39,6 +39,32 @@ fun List<String>.isMatchingToWildcardList(value: String): Boolean {
|
||||||
return matches.isNotEmpty() && excludedMatches.isEmpty()
|
return matches.isNotEmpty() && excludedMatches.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.toRegexWithWildcards(): Regex {
|
fun String.transformWildcardsToRegex(): Regex {
|
||||||
return this.replace("*", ".*").replace("?", ".").toRegex()
|
return this.replaceUnescapedChar("*", ".*").replaceUnescapedChar("?", ".").toRegex()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.replaceUnescapedChar(charToReplace: String, replacement: String): String {
|
||||||
|
val escapedChar = Regex.escape(charToReplace)
|
||||||
|
val regex = "(?<!\\\\)(?<!(?<!\\\\)\\\\)($escapedChar)".toRegex()
|
||||||
|
return regex.replace(this) { matchResult ->
|
||||||
|
if (matchResult.range.first == 0 ||
|
||||||
|
this[matchResult.range.first - 1] != '\\' ||
|
||||||
|
(matchResult.range.first > 1 && this[matchResult.range.first - 2] == '\\')
|
||||||
|
) {
|
||||||
|
replacement.toString()
|
||||||
|
} else {
|
||||||
|
matchResult.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.isCharacterEscaped(index: Int): Boolean {
|
||||||
|
if (index <= 0) return false
|
||||||
|
var backslashCount = 0
|
||||||
|
var currentIndex = index - 1
|
||||||
|
while (currentIndex >= 0 && this[currentIndex] == '\\') {
|
||||||
|
backslashCount++
|
||||||
|
currentIndex--
|
||||||
|
}
|
||||||
|
return backslashCount % 2 != 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,11 @@ import androidx.compose.ui.graphics.Color
|
||||||
import com.wireguard.android.util.RootShell
|
import com.wireguard.android.util.RootShell
|
||||||
import com.wireguard.config.Peer
|
import com.wireguard.config.Peer
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.Straw
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
|
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.theme.Straw
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||||
import org.amnezia.awg.config.Config
|
import org.amnezia.awg.config.Config
|
||||||
|
@ -21,6 +23,10 @@ fun TunnelStatistics.PeerStats.latestHandshakeSeconds(): Long? {
|
||||||
return NumberUtils.getSecondsBetweenTimestampAndNow(this.latestHandshakeEpochMillis)
|
return NumberUtils.getSecondsBetweenTimestampAndNow(this.latestHandshakeEpochMillis)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun VpnState.isDown(): Boolean {
|
||||||
|
return this.status == TunnelState.DOWN
|
||||||
|
}
|
||||||
|
|
||||||
fun TunnelStatistics.PeerStats.handshakeStatus(): HandshakeStatus {
|
fun TunnelStatistics.PeerStats.handshakeStatus(): HandshakeStatus {
|
||||||
// TODO add never connected status after duration
|
// TODO add never connected status after duration
|
||||||
return this.latestHandshakeSeconds().let {
|
return this.latestHandshakeSeconds().let {
|
||||||
|
|
|
@ -220,4 +220,9 @@
|
||||||
<string name="use_wildcards">Use name wildcards</string>
|
<string name="use_wildcards">Use name wildcards</string>
|
||||||
<string name="learn_more">Learn more</string>
|
<string name="learn_more">Learn more</string>
|
||||||
<string name="wildcards_active">Wildcards active</string>
|
<string name="wildcards_active">Wildcards active</string>
|
||||||
|
<string name="wifi_name_via_shell">Wifi name via shell</string>
|
||||||
|
<string name="use_root_shell_for_wifi">Use root shell to get wifi name</string>
|
||||||
|
<string name="kernel_not_supported">Kernel not supported</string>
|
||||||
|
<string name="start_auto">Start auto-tunnel</string>
|
||||||
|
<string name="stop_auto">Stop auto-tunnel</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -37,8 +37,8 @@
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:icon="@drawable/auto_play"
|
android:icon="@drawable/auto_play"
|
||||||
android:shortcutId="autoOn1"
|
android:shortcutId="autoOn1"
|
||||||
android:shortcutLongLabel="@string/auto_on"
|
android:shortcutLongLabel="@string/start_auto"
|
||||||
android:shortcutShortLabel="@string/auto_tun_on">
|
android:shortcutShortLabel="@string/start_auto">
|
||||||
<intent
|
<intent
|
||||||
android:action="START"
|
android:action="START"
|
||||||
android:targetClass="com.zaneschepke.wireguardautotunnel.service.shortcut.ShortcutsActivity"
|
android:targetClass="com.zaneschepke.wireguardautotunnel.service.shortcut.ShortcutsActivity"
|
||||||
|
@ -53,8 +53,8 @@
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:icon="@drawable/auto_pause"
|
android:icon="@drawable/auto_pause"
|
||||||
android:shortcutId="autoOff1"
|
android:shortcutId="autoOff1"
|
||||||
android:shortcutLongLabel="@string/auto_off"
|
android:shortcutLongLabel="@string/stop_auto"
|
||||||
android:shortcutShortLabel="@string/auto_tun_off">
|
android:shortcutShortLabel="@string/stop_auto">
|
||||||
<intent
|
<intent
|
||||||
android:action="STOP"
|
android:action="STOP"
|
||||||
android:targetClass="com.zaneschepke.wireguardautotunnel.service.shortcut.ShortcutsActivity"
|
android:targetClass="com.zaneschepke.wireguardautotunnel.service.shortcut.ShortcutsActivity"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
object Constants {
|
object Constants {
|
||||||
const val VERSION_NAME = "3.5.3"
|
const val VERSION_NAME = "3.6.0"
|
||||||
const val JVM_TARGET = "17"
|
const val JVM_TARGET = "17"
|
||||||
const val VERSION_CODE = 35300
|
const val VERSION_CODE = 36000
|
||||||
const val TARGET_SDK = 34
|
const val TARGET_SDK = 34
|
||||||
const val MIN_SDK = 26
|
const val MIN_SDK = 26
|
||||||
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
||||||
|
|
|
@ -6,7 +6,7 @@ androidx-junit = "1.2.1"
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.0"
|
||||||
biometricKtx = "1.2.0-alpha05"
|
biometricKtx = "1.2.0-alpha05"
|
||||||
coreGoogleShortcuts = "1.1.0"
|
coreGoogleShortcuts = "1.1.0"
|
||||||
coreKtx = "1.13.1"
|
coreKtx = "1.15.0"
|
||||||
datastorePreferences = "1.1.1"
|
datastorePreferences = "1.1.1"
|
||||||
desugar_jdk_libs = "2.1.2"
|
desugar_jdk_libs = "2.1.2"
|
||||||
espressoCore = "3.6.1"
|
espressoCore = "3.6.1"
|
||||||
|
@ -14,18 +14,18 @@ hiltAndroid = "2.52"
|
||||||
hiltNavigationCompose = "1.2.0"
|
hiltNavigationCompose = "1.2.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
kotlinx-serialization-json = "1.7.3"
|
kotlinx-serialization-json = "1.7.3"
|
||||||
lifecycle-runtime-compose = "2.8.6"
|
lifecycle-runtime-compose = "2.8.7"
|
||||||
material3 = "1.3.0"
|
material3 = "1.3.1"
|
||||||
navigationCompose = "2.8.3"
|
navigationCompose = "2.8.3"
|
||||||
pinLockCompose = "1.0.4"
|
pinLockCompose = "1.0.4"
|
||||||
roomVersion = "2.6.1"
|
roomVersion = "2.6.1"
|
||||||
timber = "5.0.1"
|
timber = "5.0.1"
|
||||||
tunnel = "1.2.1"
|
tunnel = "1.2.1"
|
||||||
androidGradlePlugin = "8.7.1"
|
androidGradlePlugin = "8.7.2"
|
||||||
kotlin = "2.0.21"
|
kotlin = "2.0.21"
|
||||||
ksp = "2.0.21-1.0.25"
|
ksp = "2.0.21-1.0.25"
|
||||||
composeBom = "2024.10.00"
|
composeBom = "2024.10.01"
|
||||||
compose = "1.7.4"
|
compose = "1.7.5"
|
||||||
zxingAndroidEmbedded = "4.3.0"
|
zxingAndroidEmbedded = "4.3.0"
|
||||||
coreSplashscreen = "1.0.1"
|
coreSplashscreen = "1.0.1"
|
||||||
gradlePlugins-grgit = "5.3.0"
|
gradlePlugins-grgit = "5.3.0"
|
||||||
|
|
Loading…
Reference in New Issue