diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..de4a9d84
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,85 @@
+[{*.kt,*.kts}]
+indent_style = space
+insert_final_newline = true
+max_line_length = 100
+indent_size = 4
+ij_continuation_indent_size = 4
+ij_java_names_count_to_use_import_on_demand = 9999
+ij_kotlin_align_in_columns_case_branch = false
+ij_kotlin_align_multiline_binary_operation = false
+ij_kotlin_align_multiline_extends_list = false
+ij_kotlin_align_multiline_method_parentheses = false
+ij_kotlin_align_multiline_parameters = true
+ij_kotlin_align_multiline_parameters_in_calls = false
+ij_kotlin_allow_trailing_comma = true
+ij_kotlin_allow_trailing_comma_on_call_site = true
+ij_kotlin_assignment_wrap = normal
+ij_kotlin_blank_lines_after_class_header = 0
+ij_kotlin_blank_lines_around_block_when_branches = 0
+ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1
+ij_kotlin_block_comment_at_first_column = true
+ij_kotlin_call_parameters_new_line_after_left_paren = true
+ij_kotlin_call_parameters_right_paren_on_new_line = false
+ij_kotlin_call_parameters_wrap = on_every_item
+ij_kotlin_catch_on_new_line = false
+ij_kotlin_class_annotation_wrap = split_into_lines
+ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
+ij_kotlin_continuation_indent_for_chained_calls = true
+ij_kotlin_continuation_indent_for_expression_bodies = true
+ij_kotlin_continuation_indent_in_argument_lists = true
+ij_kotlin_continuation_indent_in_elvis = false
+ij_kotlin_continuation_indent_in_if_conditions = false
+ij_kotlin_continuation_indent_in_parameter_lists = false
+ij_kotlin_continuation_indent_in_supertype_lists = false
+ij_kotlin_else_on_new_line = false
+ij_kotlin_enum_constants_wrap = off
+ij_kotlin_extends_list_wrap = normal
+ij_kotlin_field_annotation_wrap = split_into_lines
+ij_kotlin_finally_on_new_line = false
+ij_kotlin_if_rparen_on_new_line = false
+ij_kotlin_import_nested_classes = false
+ij_kotlin_insert_whitespaces_in_simple_one_line_method = true
+ij_kotlin_keep_blank_lines_before_right_brace = 2
+ij_kotlin_keep_blank_lines_in_code = 2
+ij_kotlin_keep_blank_lines_in_declarations = 2
+ij_kotlin_keep_first_column_comment = true
+ij_kotlin_keep_indents_on_empty_lines = false
+ij_kotlin_keep_line_breaks = true
+ij_kotlin_lbrace_on_next_line = false
+ij_kotlin_line_comment_add_space = false
+ij_kotlin_line_comment_at_first_column = true
+ij_kotlin_method_annotation_wrap = split_into_lines
+ij_kotlin_method_call_chain_wrap = normal
+ij_kotlin_method_parameters_new_line_after_left_paren = true
+ij_kotlin_method_parameters_right_paren_on_new_line = true
+ij_kotlin_method_parameters_wrap = on_every_item
+ij_kotlin_name_count_to_use_star_import = 9999
+ij_kotlin_name_count_to_use_star_import_for_members = 9999
+ij_kotlin_parameter_annotation_wrap = off
+ij_kotlin_space_after_comma = true
+ij_kotlin_space_after_extend_colon = true
+ij_kotlin_space_after_type_colon = true
+ij_kotlin_space_before_catch_parentheses = true
+ij_kotlin_space_before_comma = false
+ij_kotlin_space_before_extend_colon = true
+ij_kotlin_space_before_for_parentheses = true
+ij_kotlin_space_before_if_parentheses = true
+ij_kotlin_space_before_lambda_arrow = true
+ij_kotlin_space_before_type_colon = false
+ij_kotlin_space_before_when_parentheses = true
+ij_kotlin_space_before_while_parentheses = true
+ij_kotlin_spaces_around_additive_operators = true
+ij_kotlin_spaces_around_assignment_operators = true
+ij_kotlin_spaces_around_equality_operators = true
+ij_kotlin_spaces_around_function_type_arrow = true
+ij_kotlin_spaces_around_logical_operators = true
+ij_kotlin_spaces_around_multiplicative_operators = true
+ij_kotlin_spaces_around_range = false
+ij_kotlin_spaces_around_relational_operators = true
+ij_kotlin_spaces_around_unary_operator = false
+ij_kotlin_spaces_around_when_arrow = true
+ij_kotlin_variable_annotation_wrap = off
+ij_kotlin_while_on_new_line = false
+ij_kotlin_wrap_elvis_expressions = 1
+ij_kotlin_wrap_expression_body_functions = 1
+ij_kotlin_wrap_first_method_in_call_chain = false
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 3691c201..f406880e 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -11,12 +11,14 @@ assignees: zaneschepke
A clear and concise description of what the bug is.
**Smartphone (please complete the following information):**
- - Device: [e.g. iPhone6]
- - Android Version: [e.g. iOS8.1]
- - App Version [e.g. 22]
+
+- Device: [e.g. Pixel 4a]
+- Android Version: [e.g. Android 13]
+- App Version [e.g. 3.3.3]
**To Reproduce**
Steps to reproduce the behavior:
+
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md
index ac02825b..62eccbd3 100644
--- a/.github/SUPPORT.md
+++ b/.github/SUPPORT.md
@@ -1,6 +1,6 @@
# Support
-If you are experiencing issues with the app, the following resources are available to help you.
+If you are experiencing issues with the app, the following resources are available to help you.
-
diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index f81015e0..6062e6b1 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -9,7 +9,7 @@ on:
jobs:
build:
name: Build Signed APK
- # change to macos because of hilt issues on ubuntu in gradle 8.3
+
runs-on: ubuntu-latest
env:
@@ -70,7 +70,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# fix hardcode changelog file name
- body_path: ${{ github.workspace }}/fastlane/metadata/android/en-US/changelogs/33200.txt
+ body_path: ${{ github.workspace }}/fastlane/metadata/android/en-US/changelogs/33300.txt
tag_name: ${{ github.ref_name }}
name: Release ${{ github.ref_name }}
draft: false
diff --git a/README.md b/README.md
index 22889054..6606f6af 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,10 @@ WG Tunnel
-This is an alternative Android Application for [WireGuard](https://www.wireguard.com/) with added features. Built using the [wireguard-android](https://github.com/WireGuard/wireguard-android) library and [Jetpack Compose](https://developer.android.com/jetpack/compose), this application was inspired by the official [WireGuard Android](https://github.com/WireGuard/wireguard-android) app.
+This is an alternative Android Application for [WireGuard](https://www.wireguard.com/) with added
+features. Built using the [wireguard-android](https://github.com/WireGuard/wireguard-android)
+library and [Jetpack Compose](https://developer.android.com/jetpack/compose), this application was
+inspired by the official [WireGuard Android](https://github.com/WireGuard/wireguard-android) app.
@@ -47,7 +50,8 @@ This is an alternative Android Application for [WireGuard](https://www.wireguard
## Inspiration
-The original inspiration for this app came from the inconvenience of having to manually turn VPN off and on while on different networks. This app was created to offer a free solution to this problem.
+The original inspiration for this app came from the inconvenience of having to manually turn VPN off
+and on while on different networks. This app was created to offer a free solution to this problem.
## Features
@@ -63,9 +67,8 @@ The original inspiration for this app came from the inconvenience of having to m
* Automatic service restart after reboot
* Battery preservation measures
-
## Building
-
+
```
$ git clone https://github.com/zaneschepke/wgtunnel
$ cd wgtunnel
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index ae8e5ac7..bf10316a 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -19,9 +19,7 @@ android {
versionCode = Constants.VERSION_CODE
versionName = Constants.VERSION_NAME
- ksp {
- arg("room.schemaLocation", "$projectDir/schemas")
- }
+ ksp { arg("room.schemaLocation", "$projectDir/schemas") }
sourceSets {
getByName("debug").assets.srcDirs(files("$projectDir/schemas")) // Room
@@ -30,9 +28,7 @@ android {
resourceConfigurations.addAll(listOf("en"))
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
- vectorDrawables {
- useSupportLibrary = true
- }
+ vectorDrawables { useSupportLibrary = true }
}
signingConfigs {
@@ -47,32 +43,41 @@ android {
}
}
- // try to get secrets from env first for pipeline build, then properties file for local build
- storeFile = file(
- System.getenv().getOrDefault(
- Constants.KEY_STORE_PATH_VAR,
- properties.getProperty(Constants.KEY_STORE_PATH_VAR)
+ // try to get secrets from env first for pipeline build, then properties file for local
+ // build
+ storeFile =
+ file(
+ System.getenv()
+ .getOrDefault(
+ Constants.KEY_STORE_PATH_VAR,
+ properties.getProperty(Constants.KEY_STORE_PATH_VAR),
+ ),
)
- )
- storePassword = System.getenv().getOrDefault(
- Constants.STORE_PASS_VAR,
- properties.getProperty(Constants.STORE_PASS_VAR)
- )
- keyAlias = System.getenv().getOrDefault(
- Constants.KEY_ALIAS_VAR,
- properties.getProperty(Constants.KEY_ALIAS_VAR)
- )
- keyPassword = System.getenv().getOrDefault(
- Constants.KEY_PASS_VAR,
- properties.getProperty(Constants.KEY_PASS_VAR)
- )
+ storePassword =
+ System.getenv()
+ .getOrDefault(
+ Constants.STORE_PASS_VAR,
+ properties.getProperty(Constants.STORE_PASS_VAR),
+ )
+ keyAlias =
+ System.getenv()
+ .getOrDefault(
+ Constants.KEY_ALIAS_VAR,
+ properties.getProperty(Constants.KEY_ALIAS_VAR),
+ )
+ keyPassword =
+ System.getenv()
+ .getOrDefault(
+ Constants.KEY_PASS_VAR,
+ properties.getProperty(Constants.KEY_PASS_VAR),
+ )
}
}
buildTypes {
// don't strip
packaging.jniLibs.keepDebugSymbols.addAll(
- listOf("libwg-go.so", "libwg-quick.so", "libwg.so")
+ listOf("libwg-go.so", "libwg-quick.so", "libwg.so"),
)
applicationVariants.all {
@@ -91,13 +96,11 @@ android {
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
- "proguard-rules.pro"
+ "proguard-rules.pro",
)
signingConfig = signingConfigs.getByName(Constants.RELEASE)
}
- debug {
- isDebuggable = true
- }
+ debug { isDebuggable = true }
}
flavorDimensions.add(Constants.TYPE)
productFlavors {
@@ -118,24 +121,17 @@ android {
targetCompatibility = JavaVersion.VERSION_17
isCoreLibraryDesugaringEnabled = true
}
- kotlinOptions {
- jvmTarget = Constants.JVM_TARGET
- }
+ kotlinOptions { jvmTarget = Constants.JVM_TARGET }
buildFeatures {
compose = true
buildConfig = true
}
- composeOptions {
- kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get()
- }
- packaging {
- resources {
- excludes += "/META-INF/{AL2.0,LGPL2.1}"
- }
- }
+ composeOptions { kotlinCompilerExtensionVersion = Constants.COMPOSE_COMPILER_EXTENSION_VERSION }
+ packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } }
}
val generalImplementation by configurations
+
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
diff --git a/app/src/androidTest/java/com/zaneschepke/wireguardautotunnel/MigrationTest.kt b/app/src/androidTest/java/com/zaneschepke/wireguardautotunnel/MigrationTest.kt
index 987e4321..1ce565f6 100644
--- a/app/src/androidTest/java/com/zaneschepke/wireguardautotunnel/MigrationTest.kt
+++ b/app/src/androidTest/java/com/zaneschepke/wireguardautotunnel/MigrationTest.kt
@@ -14,10 +14,11 @@ class MigrationTest {
private val dbName = "migration-test"
@get:Rule
- val helper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java
- )
+ val helper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ )
@Test
@Throws(IOException::class)
@@ -27,34 +28,33 @@ class MigrationTest {
// You can't use DAO classes because they expect the latest schema.
execSQL(
"INSERT INTO Settings (is_tunnel_enabled," +
- "is_tunnel_on_mobile_data_enabled," +
- "trusted_network_ssids," +
- "default_tunnel," +
- "is_always_on_vpn_enabled," +
- "is_tunnel_on_ethernet_enabled," +
- "is_shortcuts_enabled," +
- "is_battery_saver_enabled," +
- "is_tunnel_on_wifi_enabled," +
- "is_kernel_enabled," +
- "is_restore_on_boot_enabled," +
- "is_multi_tunnel_enabled)" +
- " VALUES " +
- "('false'," +
- "'false'," +
- "'[trustedSSID1,trustedSSID2]'," +
- "'defaultTunnel'," +
- "'false'," +
- "'false'," +
- "'false'," +
- "'false'," +
- "'false'," +
- "'false'," +
- "'false'," +
- "'false')"
+ "is_tunnel_on_mobile_data_enabled," +
+ "trusted_network_ssids," +
+ "default_tunnel," +
+ "is_always_on_vpn_enabled," +
+ "is_tunnel_on_ethernet_enabled," +
+ "is_shortcuts_enabled," +
+ "is_battery_saver_enabled," +
+ "is_tunnel_on_wifi_enabled," +
+ "is_kernel_enabled," +
+ "is_restore_on_boot_enabled," +
+ "is_multi_tunnel_enabled)" +
+ " VALUES " +
+ "('false'," +
+ "'false'," +
+ "'[trustedSSID1,trustedSSID2]'," +
+ "'defaultTunnel'," +
+ "'false'," +
+ "'false'," +
+ "'false'," +
+ "'false'," +
+ "'false'," +
+ "'false'," +
+ "'false'," +
+ "'false')",
)
execSQL(
- "INSERT INTO TunnelConfig (name, wg_quick)" +
- " VALUES ('hello', 'hello')"
+ "INSERT INTO TunnelConfig (name, wg_quick)" + " VALUES ('hello', 'hello')",
)
// Prepare for the next version.
close()
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5a31be93..de5b101c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,56 +1,63 @@
+
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
-
+
-
-
+ android:name="android.hardware.screen.portrait"
+ android:required="false" />
+
+
-
+
+
-
+ android:finishOnTaskLaunch="true"
+ android:theme="@android:style/Theme.NoDisplay" />
+
-
+ tools:node="merge" />
-
-
+
+ android:permission="android.permission.BIND_VPN_SERVICE"
+ android:persistent="true"
+ tools:node="merge">
-
+
-
-
-
+
+
+
-
+
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt
index 7b4288aa..3b6507cd 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt
@@ -4,61 +4,31 @@ import android.app.Application
import android.content.ComponentName
import android.content.pm.PackageManager
import android.service.quicksettings.TileService
-import androidx.lifecycle.ProcessLifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import com.zaneschepke.wireguardautotunnel.data.datastore.DataStoreManager
-import com.zaneschepke.wireguardautotunnel.data.model.Settings
-import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
import com.zaneschepke.wireguardautotunnel.service.tile.TunnelControlTile
import dagger.hilt.android.HiltAndroidApp
-import kotlinx.coroutines.launch
import timber.log.Timber
-import java.io.IOException
-import javax.inject.Inject
@HiltAndroidApp
class WireGuardAutoTunnel : Application() {
- @Inject
- lateinit var settingsRepository: SettingsRepository
-
- @Inject
- lateinit var dataStoreManager: DataStoreManager
-
override fun onCreate() {
super.onCreate()
instance = this
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
- initSettings()
- with(ProcessLifecycleOwner.get()) {
- lifecycleScope.launch {
- try {
- // load preferences into memory
- dataStoreManager.init()
- requestTileServiceStateUpdate()
- } catch (e: IOException) {
- Timber.e("Failed to load preferences")
- }
- }
- }
- }
-
- private fun initSettings() {
- with(ProcessLifecycleOwner.get()) {
- lifecycleScope.launch {
- if (settingsRepository.getAll().isEmpty()) {
- settingsRepository.save(Settings())
- }
- }
- }
}
companion object {
- lateinit var instance: WireGuardAutoTunnel private set
+ lateinit var instance: WireGuardAutoTunnel
+ private set
+
fun isRunningOnAndroidTv(): Boolean {
return instance.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
}
+
fun requestTileServiceStateUpdate() {
- TileService.requestListeningState(instance, ComponentName(instance, TunnelControlTile::class.java))
+ TileService.requestListeningState(
+ instance,
+ ComponentName(instance, TunnelControlTile::class.java),
+ )
}
}
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/AppDatabase.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/AppDatabase.kt
index 666f813f..57934e67 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/AppDatabase.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/AppDatabase.kt
@@ -10,16 +10,20 @@ import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
@Database(
entities = [Settings::class, TunnelConfig::class],
version = 5,
- autoMigrations = [
- AutoMigration(from = 1, to = 2), AutoMigration(from = 2, to = 3), AutoMigration(
- from = 3,
- to = 4
- ),AutoMigration(
- from = 4,
- to = 5
- )
- ],
- exportSchema = true
+ autoMigrations =
+ [
+ AutoMigration(from = 1, to = 2),
+ AutoMigration(from = 2, to = 3),
+ AutoMigration(
+ from = 3,
+ to = 4,
+ ),
+ AutoMigration(
+ from = 4,
+ to = 5,
+ ),
+ ],
+ exportSchema = true,
)
@TypeConverters(DatabaseListConverters::class)
abstract class AppDatabase : RoomDatabase() {
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/SettingsDao.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/SettingsDao.kt
index ffd1574f..77061ae7 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/SettingsDao.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/SettingsDao.kt
@@ -10,27 +10,19 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface SettingsDao {
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- suspend fun save(t: Settings)
+ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun save(t: Settings)
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- suspend fun saveAll(t: List)
+ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun saveAll(t: List)
- @Query("SELECT * FROM settings WHERE id=:id")
- suspend fun getById(id: Long): Settings?
+ @Query("SELECT * FROM settings WHERE id=:id") suspend fun getById(id: Long): Settings?
- @Query("SELECT * FROM settings")
- suspend fun getAll(): List
+ @Query("SELECT * FROM settings") suspend fun getAll(): List
- @Query("SELECT * FROM settings LIMIT 1")
- fun getSettingsFlow(): Flow
+ @Query("SELECT * FROM settings LIMIT 1") fun getSettingsFlow(): Flow
- @Query("SELECT * FROM settings")
- fun getAllFlow(): Flow>
+ @Query("SELECT * FROM settings") fun getAllFlow(): Flow>
- @Delete
- suspend fun delete(t: Settings)
+ @Delete suspend fun delete(t: Settings)
- @Query("SELECT COUNT('id') FROM settings")
- suspend fun count(): Long
+ @Query("SELECT COUNT('id') FROM settings") suspend fun count(): Long
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/TunnelConfigDao.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/TunnelConfigDao.kt
index 8460ace6..930c261d 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/TunnelConfigDao.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/TunnelConfigDao.kt
@@ -10,24 +10,17 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface TunnelConfigDao {
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- suspend fun save(t: TunnelConfig)
+ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun save(t: TunnelConfig)
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- suspend fun saveAll(t: List)
+ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun saveAll(t: List)
- @Query("SELECT * FROM TunnelConfig WHERE id=:id")
- suspend fun getById(id: Long): TunnelConfig?
+ @Query("SELECT * FROM TunnelConfig WHERE id=:id") suspend fun getById(id: Long): TunnelConfig?
- @Query("SELECT * FROM TunnelConfig")
- suspend fun getAll(): List
+ @Query("SELECT * FROM TunnelConfig") suspend fun getAll(): List
- @Delete
- suspend fun delete(t: TunnelConfig)
+ @Delete suspend fun delete(t: TunnelConfig)
- @Query("SELECT COUNT('id') FROM TunnelConfig")
- suspend fun count(): Long
+ @Query("SELECT COUNT('id') FROM TunnelConfig") suspend fun count(): Long
- @Query("SELECT * FROM tunnelconfig")
- fun getAllFlow(): Flow>
+ @Query("SELECT * FROM tunnelconfig") fun getAllFlow(): Flow>
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/datastore/DataStoreManager.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/datastore/DataStoreManager.kt
index 6d9c67c0..8af02399 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/datastore/DataStoreManager.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/datastore/DataStoreManager.kt
@@ -1,4 +1,5 @@
package com.zaneschepke.wireguardautotunnel.data.datastore
+
import android.content.Context
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
@@ -11,29 +12,27 @@ import kotlinx.coroutines.flow.map
class DataStoreManager(private val context: Context) {
companion object {
val LOCATION_DISCLOSURE_SHOWN = booleanPreferencesKey("LOCATION_DISCLOSURE_SHOWN")
+ val BATTERY_OPTIMIZE_DISABLE_SHOWN = booleanPreferencesKey("BATTERY_OPTIMIZE_DISABLE_SHOWN")
}
// preferences
private val preferencesKey = "preferences"
- private val Context.dataStore by preferencesDataStore(
- name = preferencesKey
- )
+ private val Context.dataStore by
+ preferencesDataStore(
+ name = preferencesKey,
+ )
suspend fun init() {
context.dataStore.data.first()
}
suspend fun saveToDataStore(key: Preferences.Key, value: T) =
- context.dataStore.edit {
- it[key] = value
- }
- fun getFromStoreFlow(key: Preferences.Key) = context.dataStore.data.map {
- it[key]
- }
+ context.dataStore.edit { it[key] = value }
- suspend fun getFromStore(key: Preferences.Key) = context.dataStore.data.first { it.contains(key) }[key]
+ fun getFromStoreFlow(key: Preferences.Key) = context.dataStore.data.map { it[key] }
- val locationDisclosureFlow: Flow = context.dataStore.data.map {
- it[LOCATION_DISCLOSURE_SHOWN]
- }
+ suspend fun getFromStore(key: Preferences.Key) =
+ context.dataStore.data.first { it.contains(key) }[key]
+
+ val preferencesFlow: Flow = context.dataStore.data
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/model/Settings.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/model/Settings.kt
index d92c74e6..4aa3789c 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/model/Settings.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/model/Settings.kt
@@ -8,39 +8,49 @@ import androidx.room.PrimaryKey
data class Settings(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
@ColumnInfo(name = "is_tunnel_enabled") var isAutoTunnelEnabled: Boolean = false,
- @ColumnInfo(name = "is_tunnel_on_mobile_data_enabled") var isTunnelOnMobileDataEnabled: Boolean = false,
- @ColumnInfo(name = "trusted_network_ssids") var trustedNetworkSSIDs: MutableList = mutableListOf(),
+ @ColumnInfo(name = "is_tunnel_on_mobile_data_enabled")
+ var isTunnelOnMobileDataEnabled: Boolean = false,
+ @ColumnInfo(name = "trusted_network_ssids")
+ var trustedNetworkSSIDs: MutableList = mutableListOf(),
@ColumnInfo(name = "default_tunnel") var defaultTunnel: String? = null,
@ColumnInfo(name = "is_always_on_vpn_enabled") var isAlwaysOnVpnEnabled: Boolean = false,
- @ColumnInfo(name = "is_tunnel_on_ethernet_enabled") var isTunnelOnEthernetEnabled: Boolean = false,
+ @ColumnInfo(name = "is_tunnel_on_ethernet_enabled")
+ var isTunnelOnEthernetEnabled: Boolean = false,
@ColumnInfo(
name = "is_shortcuts_enabled",
- defaultValue = "false"
- ) var isShortcutsEnabled: Boolean = false,
+ defaultValue = "false",
+ )
+ var isShortcutsEnabled: Boolean = false,
@ColumnInfo(
name = "is_battery_saver_enabled",
- defaultValue = "false"
- ) var isBatterySaverEnabled: Boolean = false,
+ defaultValue = "false",
+ )
+ var isBatterySaverEnabled: Boolean = false,
@ColumnInfo(
name = "is_tunnel_on_wifi_enabled",
- defaultValue = "false"
- ) var isTunnelOnWifiEnabled: Boolean = false,
+ defaultValue = "false",
+ )
+ var isTunnelOnWifiEnabled: Boolean = false,
@ColumnInfo(
name = "is_kernel_enabled",
- defaultValue = "false"
- ) var isKernelEnabled: Boolean = false,
+ defaultValue = "false",
+ )
+ var isKernelEnabled: Boolean = false,
@ColumnInfo(
- name = "is_restore_on_boot_enabled",
- defaultValue = "false"
- ) var isRestoreOnBootEnabled: Boolean = false,
+ name = "is_restore_on_boot_enabled",
+ defaultValue = "false",
+ )
+ var isRestoreOnBootEnabled: Boolean = false,
@ColumnInfo(
- name = "is_multi_tunnel_enabled",
- defaultValue = "false"
- ) var isMultiTunnelEnabled: Boolean = false,
+ name = "is_multi_tunnel_enabled",
+ defaultValue = "false",
+ )
+ var isMultiTunnelEnabled: Boolean = false,
@ColumnInfo(
name = "is_auto_tunnel_paused",
- defaultValue = "false"
- ) var isAutoTunnelPaused: Boolean = false,
+ defaultValue = "false",
+ )
+ var isAutoTunnelPaused: Boolean = false,
) {
fun isTunnelConfigDefault(tunnelConfig: TunnelConfig): Boolean {
return if (defaultTunnel != null) {
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/SettingsRepository.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/SettingsRepository.kt
index 685fe5bb..2f27f905 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/SettingsRepository.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/SettingsRepository.kt
@@ -4,9 +4,11 @@ import com.zaneschepke.wireguardautotunnel.data.model.Settings
import kotlinx.coroutines.flow.Flow
interface SettingsRepository {
- suspend fun save(settings : Settings)
- fun getSettingsFlow() : Flow
+ suspend fun save(settings: Settings)
- suspend fun getSettings() : Settings
- suspend fun getAll() : List
-}
\ No newline at end of file
+ fun getSettingsFlow(): Flow
+
+ suspend fun getSettings(): Settings
+
+ suspend fun getAll(): List
+}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/SettingsRepositoryImpl.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/SettingsRepositoryImpl.kt
index 2c476629..abdadd93 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/SettingsRepositoryImpl.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/SettingsRepositoryImpl.kt
@@ -21,4 +21,4 @@ class SettingsRepositoryImpl(private val settingsDoa: SettingsDao) : SettingsRep
override suspend fun getAll(): List {
return settingsDoa.getAll()
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/TunnelConfigRepository.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/TunnelConfigRepository.kt
index f0c094df..1a122151 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/TunnelConfigRepository.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/TunnelConfigRepository.kt
@@ -6,9 +6,13 @@ import kotlinx.coroutines.flow.Flow
interface TunnelConfigRepository {
- fun getTunnelConfigsFlow() : Flow
- suspend fun getAll() : TunnelConfigs
+ fun getTunnelConfigsFlow(): Flow
+
+ suspend fun getAll(): TunnelConfigs
+
suspend fun save(tunnelConfig: TunnelConfig)
+
suspend fun delete(tunnelConfig: TunnelConfig)
- suspend fun count() : Int
-}
\ No newline at end of file
+
+ suspend fun count(): Int
+}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/TunnelConfigRepositoryImpl.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/TunnelConfigRepositoryImpl.kt
index f9e3be7d..b681ac18 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/TunnelConfigRepositoryImpl.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/TunnelConfigRepositoryImpl.kt
@@ -5,7 +5,8 @@ import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.util.TunnelConfigs
import kotlinx.coroutines.flow.Flow
-class TunnelConfigRepositoryImpl(private val tunnelConfigDao: TunnelConfigDao) : TunnelConfigRepository {
+class TunnelConfigRepositoryImpl(private val tunnelConfigDao: TunnelConfigDao) :
+ TunnelConfigRepository {
override fun getTunnelConfigsFlow(): Flow {
return tunnelConfigDao.getAllFlow()
}
@@ -25,4 +26,4 @@ class TunnelConfigRepositoryImpl(private val tunnelConfigDao: TunnelConfigDao) :
override suspend fun count(): Int {
return tunnelConfigDao.count().toInt()
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/DatabaseModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/DatabaseModule.kt
index 4634ecbd..3ca96959 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/DatabaseModule.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/DatabaseModule.kt
@@ -16,14 +16,12 @@ import javax.inject.Singleton
class DatabaseModule {
@Provides
@Singleton
- fun provideDatabase(
- @ApplicationContext context: Context
- ): AppDatabase {
+ fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
- context,
- AppDatabase::class.java,
- context.getString(R.string.db_name)
- )
+ context,
+ AppDatabase::class.java,
+ context.getString(R.string.db_name),
+ )
.fallbackToDestructiveMigration()
.build()
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/Kernel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/Kernel.kt
index a763c09d..c4b0d2d2 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/Kernel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/Kernel.kt
@@ -2,6 +2,4 @@ package com.zaneschepke.wireguardautotunnel.module
import javax.inject.Qualifier
-@Qualifier
-@Retention(AnnotationRetention.BINARY)
-annotation class Kernel
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class Kernel
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt
index dfa9510d..3a438b9e 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt
@@ -17,7 +17,9 @@ import dagger.hilt.android.scopes.ServiceScoped
abstract class ServiceModule {
@Binds
@ServiceScoped
- abstract fun provideNotificationService(wireGuardNotification: WireGuardNotification): NotificationService
+ abstract fun provideNotificationService(
+ wireGuardNotification: WireGuardNotification
+ ): NotificationService
@Binds
@ServiceScoped
@@ -25,9 +27,13 @@ abstract class ServiceModule {
@Binds
@ServiceScoped
- abstract fun provideMobileDataService(mobileDataService: MobileDataService): NetworkService
+ abstract fun provideMobileDataService(
+ mobileDataService: MobileDataService
+ ): NetworkService
@Binds
@ServiceScoped
- abstract fun provideEthernetService(ethernetService: EthernetService): NetworkService
+ abstract fun provideEthernetService(
+ ethernetService: EthernetService
+ ): NetworkService
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt
index d274ad02..bb490f6d 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt
@@ -21,28 +21,21 @@ import javax.inject.Singleton
class TunnelModule {
@Provides
@Singleton
- fun provideRootShell(
- @ApplicationContext context: Context
- ): RootShell {
+ fun provideRootShell(@ApplicationContext context: Context): RootShell {
return RootShell(context)
}
@Provides
@Singleton
@Userspace
- fun provideUserspaceBackend(
- @ApplicationContext context: Context
- ): Backend {
+ fun provideUserspaceBackend(@ApplicationContext context: Context): Backend {
return GoBackend(context)
}
@Provides
@Singleton
@Kernel
- fun provideKernelBackend(
- @ApplicationContext context: Context,
- rootShell: RootShell
- ): Backend {
+ fun provideKernelBackend(@ApplicationContext context: Context, rootShell: RootShell): Backend {
return WgQuickBackend(context, rootShell, ToolsInstaller(context, rootShell))
}
@@ -51,7 +44,7 @@ class TunnelModule {
fun provideVpnService(
@Userspace userspaceBackend: Backend,
@Kernel kernelBackend: Backend,
- settingsRepository : SettingsRepository
+ settingsRepository: SettingsRepository
): VpnService {
return WireGuardTunnel(userspaceBackend, kernelBackend, settingsRepository)
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/Userspace.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/Userspace.kt
index 8a85a7d3..f064aab0 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/Userspace.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/Userspace.kt
@@ -2,6 +2,4 @@ package com.zaneschepke.wireguardautotunnel.module
import javax.inject.Qualifier
-@Qualifier
-@Retention(AnnotationRetention.BINARY)
-annotation class Userspace
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class Userspace
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt
index 288e84d8..3092864c 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt
@@ -9,17 +9,15 @@ import com.zaneschepke.wireguardautotunnel.util.goAsync
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
-
@AndroidEntryPoint
class BootReceiver : BroadcastReceiver() {
- @Inject
- lateinit var settingsRepository: SettingsRepository
+ @Inject lateinit var settingsRepository: SettingsRepository
+
override fun onReceive(context: Context?, intent: Intent?) = goAsync {
if (Intent.ACTION_BOOT_COMPLETED != intent?.action) return@goAsync
- if(settingsRepository.getSettings().isAutoTunnelEnabled) {
+ if (settingsRepository.getSettings().isAutoTunnelEnabled) {
ServiceManager.startWatcherServiceForeground(context!!)
}
}
-
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt
index dfbd4c26..4ba46b61 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt
@@ -14,13 +14,9 @@ import javax.inject.Inject
@AndroidEntryPoint
class NotificationActionReceiver : BroadcastReceiver() {
- @Inject
- lateinit var settingsRepository: SettingsRepository
+ @Inject lateinit var settingsRepository: SettingsRepository
- override fun onReceive(
- context: Context,
- intent: Intent?
- ) = goAsync {
+ override fun onReceive(context: Context, intent: Intent?) = goAsync {
try {
val settings = settingsRepository.getSettings()
if (settings.defaultTunnel != null) {
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ForegroundService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ForegroundService.kt
index 0814e91b..9e3c8bb4 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ForegroundService.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ForegroundService.kt
@@ -15,18 +15,14 @@ open class ForegroundService : LifecycleService() {
return null
}
- override fun onStartCommand(
- intent: Intent?,
- flags: Int,
- startId: Int
- ): Int {
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
Timber.d("onStartCommand executed with startId: $startId")
if (intent != null) {
val action = intent.action
- Timber.d("using an intent with action $action")
when (action) {
- Action.START.name, Action.START_FOREGROUND.name -> startService(intent.extras)
+ Action.START.name,
+ Action.START_FOREGROUND.name -> startService(intent.extras)
Action.STOP.name -> stopService(intent.extras)
"android.net.VpnService" -> {
Timber.d("Always-on VPN starting service")
@@ -36,7 +32,7 @@ open class ForegroundService : LifecycleService() {
}
} else {
Timber.d(
- "with a null intent. It has been probably restarted by the system."
+ "with a null intent. It has been probably restarted by the system.",
)
}
// by returning this we make sure the service is restarted if the system kills the service
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ServiceManager.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ServiceManager.kt
index d6b2224f..b29202d7 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ServiceManager.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ServiceManager.kt
@@ -1,28 +1,27 @@
package com.zaneschepke.wireguardautotunnel.service.foreground
-import android.app.ActivityManager
import android.app.Service
import android.content.Context
-import android.content.Context.ACTIVITY_SERVICE
import android.content.Intent
import com.zaneschepke.wireguardautotunnel.R
import timber.log.Timber
object ServiceManager {
- @Suppress("DEPRECATION")
- private // Deprecated for third party Services.
- fun Context.isServiceRunning(service: Class) =
- (getSystemService(ACTIVITY_SERVICE) as ActivityManager)
- .getRunningServices(Integer.MAX_VALUE)
- .any { it.service.className == service.name }
- fun getServiceState(
- context: Context,
- cls: Class
- ): ServiceState {
- val isServiceRunning = context.isServiceRunning(cls)
- return if (isServiceRunning) ServiceState.STARTED else ServiceState.STOPPED
- }
+ // private
+ // fun Context.isServiceRunning(service: Class) =
+ // (getSystemService(ACTIVITY_SERVICE) as ActivityManager)
+ // .runningAppProcesses.any {
+ // it.processName == service.name
+ // }
+ //
+ // fun getServiceState(
+ // context: Context,
+ // cls: Class
+ // ): ServiceState {
+ // val isServiceRunning = context.isServiceRunning(cls)
+ // return if (isServiceRunning) ServiceState.STARTED else ServiceState.STOPPED
+ // }
private fun actionOnService(
action: Action,
@@ -30,14 +29,10 @@ object ServiceManager {
cls: Class,
extras: Map? = null
) {
- if (getServiceState(context, cls) == ServiceState.STOPPED && action == Action.STOP) return
- if (getServiceState(context, cls) == ServiceState.STARTED && action == Action.START) return
val intent =
Intent(context, cls).also {
it.action = action.name
- extras?.forEach { (k, v) ->
- it.putExtra(k, v)
- }
+ extras?.forEach { (k, v) -> it.putExtra(k, v) }
}
intent.component?.javaClass
try {
@@ -45,11 +40,9 @@ object ServiceManager {
Action.START_FOREGROUND -> {
context.startForegroundService(intent)
}
-
Action.START -> {
context.startService(intent)
}
-
Action.STOP -> context.startService(intent)
}
} catch (e: Exception) {
@@ -57,35 +50,30 @@ object ServiceManager {
}
}
- fun startVpnService(
- context: Context,
- tunnelConfig: String
- ) {
+ fun startVpnService(context: Context, tunnelConfig: String) {
actionOnService(
Action.START,
context,
WireGuardTunnelService::class.java,
- mapOf(context.getString(R.string.tunnel_extras_key) to tunnelConfig)
+ mapOf(context.getString(R.string.tunnel_extras_key) to tunnelConfig),
)
}
fun stopVpnService(context: Context) {
+ Timber.d("Stopping vpn service action")
actionOnService(
Action.STOP,
context,
- WireGuardTunnelService::class.java
+ WireGuardTunnelService::class.java,
)
}
- fun startVpnServiceForeground(
- context: Context,
- tunnelConfig: String
- ) {
+ fun startVpnServiceForeground(context: Context, tunnelConfig: String) {
actionOnService(
Action.START_FOREGROUND,
context,
WireGuardTunnelService::class.java,
- mapOf(context.getString(R.string.tunnel_extras_key) to tunnelConfig)
+ mapOf(context.getString(R.string.tunnel_extras_key) to tunnelConfig),
)
}
@@ -95,17 +83,15 @@ object ServiceManager {
actionOnService(
Action.START_FOREGROUND,
context,
- WireGuardConnectivityWatcherService::class.java
+ WireGuardConnectivityWatcherService::class.java,
)
}
- fun startWatcherService(
- context: Context
- ) {
+ fun startWatcherService(context: Context) {
actionOnService(
Action.START,
context,
- WireGuardConnectivityWatcherService::class.java
+ WireGuardConnectivityWatcherService::class.java,
)
}
@@ -113,7 +99,7 @@ object ServiceManager {
actionOnService(
Action.STOP,
context,
- WireGuardConnectivityWatcherService::class.java
+ WireGuardConnectivityWatcherService::class.java,
)
}
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt
index 715c7ef1..b6f62f66 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt
@@ -34,334 +34,361 @@ import javax.inject.Inject
@AndroidEntryPoint
class WireGuardConnectivityWatcherService : ForegroundService() {
- private val foregroundId = 122
+ private val foregroundId = 122
- @Inject lateinit var wifiService: NetworkService
+ @Inject lateinit var wifiService: NetworkService
- @Inject lateinit var mobileDataService: NetworkService
+ @Inject lateinit var mobileDataService: NetworkService
- @Inject lateinit var ethernetService: NetworkService
+ @Inject lateinit var ethernetService: NetworkService
- @Inject lateinit var settingsRepository: SettingsRepository
+ @Inject lateinit var settingsRepository: SettingsRepository
- @Inject lateinit var notificationService: NotificationService
+ @Inject lateinit var notificationService: NotificationService
- @Inject lateinit var vpnService: VpnService
+ @Inject lateinit var vpnService: VpnService
- private val networkEventsFlow = MutableStateFlow(WatcherState())
- data class WatcherState(
- val isWifiConnected: Boolean = false,
- val isVpnConnected : Boolean = false,
- val isEthernetConnected: Boolean = false,
- val isMobileDataConnected: Boolean = false,
- val currentNetworkSSID: String = "",
- val settings: Settings = Settings()
- )
+ private val networkEventsFlow = MutableStateFlow(WatcherState())
- private lateinit var watcherJob: Job
+ data class WatcherState(
+ val isWifiConnected: Boolean = false,
+ val isVpnConnected: Boolean = false,
+ val isEthernetConnected: Boolean = false,
+ val isMobileDataConnected: Boolean = false,
+ val currentNetworkSSID: String = "",
+ val settings: Settings = Settings()
+ )
- private var wakeLock: PowerManager.WakeLock? = null
- private val tag = this.javaClass.name
+ private lateinit var watcherJob: Job
- override fun onCreate() {
- super.onCreate()
- lifecycleScope.launch(Dispatchers.Main) {
- try {
- if(settingsRepository.getSettings().isAutoTunnelPaused) {
- launchWatcherPausedNotification()
- } else launchWatcherNotification()
- } catch (e: Exception) {
- Timber.e("Failed to start watcher service, not enough permissions")
- }
+ private var wakeLock: PowerManager.WakeLock? = null
+ private val tag = this.javaClass.name
+
+ override fun onCreate() {
+ super.onCreate()
+ lifecycleScope.launch(Dispatchers.Main) {
+ try {
+ if (settingsRepository.getSettings().isAutoTunnelPaused) {
+ launchWatcherPausedNotification()
+ } else launchWatcherNotification()
+ } catch (e: Exception) {
+ Timber.e("Failed to start watcher service, not enough permissions")
+ }
+ }
}
- }
- override fun startService(extras: Bundle?) {
- super.startService(extras)
- try {
- // we need this lock so our service gets not affected by Doze Mode
- lifecycleScope.launch { initWakeLock() }
- cancelWatcherJob()
- startWatcherJob()
- } catch (e: Exception) {
- Timber.e("Failed to launch watcher service, no permissions")
+ override fun startService(extras: Bundle?) {
+ super.startService(extras)
+ try {
+ // we need this lock so our service gets not affected by Doze Mode
+ lifecycleScope.launch { initWakeLock() }
+ cancelWatcherJob()
+ startWatcherJob()
+ } catch (e: Exception) {
+ Timber.e("Failed to launch watcher service, no permissions")
+ }
}
- }
- override fun stopService(extras: Bundle?) {
- super.stopService(extras)
- wakeLock?.let {
- if (it.isHeld) {
- it.release()
- }
+ override fun stopService(extras: Bundle?) {
+ super.stopService(extras)
+ wakeLock?.let {
+ if (it.isHeld) {
+ it.release()
+ }
+ }
+ cancelWatcherJob()
+ stopSelf()
}
- cancelWatcherJob()
- stopSelf()
- }
- private fun launchWatcherNotification(description: String = getString(R.string.watcher_notification_text_active)) {
- val notification =
- notificationService.createNotification(
- channelId = getString(R.string.watcher_channel_id),
- channelName = getString(R.string.watcher_channel_name),
- title = getString(R.string.auto_tunnel_title),
- description = description)
- ServiceCompat.startForeground(
- this, foregroundId, notification, Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID)
- }
+ private fun launchWatcherNotification(
+ description: String = getString(R.string.watcher_notification_text_active)
+ ) {
+ val notification =
+ notificationService.createNotification(
+ channelId = getString(R.string.watcher_channel_id),
+ channelName = getString(R.string.watcher_channel_name),
+ title = getString(R.string.auto_tunnel_title),
+ description = description,
+ )
+ ServiceCompat.startForeground(
+ this,
+ foregroundId,
+ notification,
+ Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID,
+ )
+ }
private fun launchWatcherPausedNotification() {
launchWatcherNotification(getString(R.string.watcher_notification_text_paused))
}
- // TODO could this be restarting service in a bad state?
- // try to start task again if killed
- override fun onTaskRemoved(rootIntent: Intent) {
- Timber.d("Task Removed called")
- val restartServiceIntent = Intent(rootIntent)
- val restartServicePendingIntent: PendingIntent =
- PendingIntent.getService(
- this,
- 1,
- restartServiceIntent,
- PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE)
- applicationContext.getSystemService(Context.ALARM_SERVICE)
- val alarmService: AlarmManager =
- applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
- alarmService.set(
- AlarmManager.ELAPSED_REALTIME,
- SystemClock.elapsedRealtime() + 1000,
- restartServicePendingIntent)
- }
-
- private suspend fun initWakeLock() {
- val isBatterySaverOn =
- withContext(lifecycleScope.coroutineContext) {
- settingsRepository.getSettings().isBatterySaverEnabled
- }
- wakeLock =
- (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
- newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "$tag::lock").apply {
- if (isBatterySaverOn) {
- Timber.d("Initiating wakelock with timeout")
- acquire(Constants.BATTERY_SAVER_WATCHER_WAKE_LOCK_TIMEOUT)
- } else {
- Timber.d("Initiating wakelock with zero timeout")
- acquire(Constants.DEFAULT_WATCHER_WAKE_LOCK_TIMEOUT)
- }
- }
- }
- }
-
- private fun cancelWatcherJob() {
- if (this::watcherJob.isInitialized) {
- watcherJob.cancel()
+ // TODO could this be restarting service in a bad state?
+ // try to start task again if killed
+ override fun onTaskRemoved(rootIntent: Intent) {
+ Timber.d("Task Removed called")
+ val restartServiceIntent = Intent(rootIntent)
+ val restartServicePendingIntent: PendingIntent =
+ PendingIntent.getService(
+ this,
+ 1,
+ restartServiceIntent,
+ PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
+ )
+ applicationContext.getSystemService(Context.ALARM_SERVICE)
+ val alarmService: AlarmManager =
+ applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+ alarmService.set(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + 1000,
+ restartServicePendingIntent,
+ )
}
- }
- private fun startWatcherJob() {
- watcherJob =
- lifecycleScope.launch(Dispatchers.IO) {
- val setting = settingsRepository.getSettings()
- launch {
- Timber.d("Starting wifi watcher")
- watchForWifiConnectivityChanges()
- }
- if (setting.isTunnelOnMobileDataEnabled) {
- launch {
- Timber.d("Starting mobile data watcher")
- watchForMobileDataConnectivityChanges()
+ private suspend fun initWakeLock() {
+ val isBatterySaverOn =
+ withContext(lifecycleScope.coroutineContext) {
+ settingsRepository.getSettings().isBatterySaverEnabled
}
- }
- if (setting.isTunnelOnEthernetEnabled) {
- launch {
- Timber.d("Starting ethernet data watcher")
- watchForEthernetConnectivityChanges()
+ wakeLock =
+ (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
+ newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "$tag::lock").apply {
+ if (isBatterySaverOn) {
+ Timber.d("Initiating wakelock with timeout")
+ acquire(Constants.BATTERY_SAVER_WATCHER_WAKE_LOCK_TIMEOUT)
+ } else {
+ Timber.d("Initiating wakelock with zero timeout")
+ acquire(Constants.DEFAULT_WATCHER_WAKE_LOCK_TIMEOUT)
+ }
+ }
}
- }
- launch {
- Timber.d("Starting vpn state watcher")
- watchForVpnConnectivityChanges()
- }
- launch {
- Timber.d("Starting settings watcher")
- watchForSettingsChanges()
- }
- launch {
- Timber.d("Starting management watcher")
- manageVpn()
- }
- }
- }
-
- private suspend fun watchForMobileDataConnectivityChanges() {
- mobileDataService.networkStatus.collect {
- when (it) {
- is NetworkStatus.Available -> {
- Timber.d("Gained Mobile data connection")
- networkEventsFlow.value = networkEventsFlow.value.copy(
- isMobileDataConnected = true
- )
- }
- is NetworkStatus.CapabilitiesChanged -> {
- networkEventsFlow.value = networkEventsFlow.value.copy(
- isMobileDataConnected = true
- )
- Timber.d("Mobile data capabilities changed")
- }
- is NetworkStatus.Unavailable -> {
- networkEventsFlow.value = networkEventsFlow.value.copy(
- isMobileDataConnected = false
- )
- Timber.d("Lost mobile data connection")
- }
- }
}
- }
+
+ private fun cancelWatcherJob() {
+ if (this::watcherJob.isInitialized) {
+ watcherJob.cancel()
+ }
+ }
+
+ private fun startWatcherJob() {
+ watcherJob =
+ lifecycleScope.launch(Dispatchers.IO) {
+ val setting = settingsRepository.getSettings()
+ launch {
+ Timber.d("Starting wifi watcher")
+ watchForWifiConnectivityChanges()
+ }
+ if (setting.isTunnelOnMobileDataEnabled) {
+ launch {
+ Timber.d("Starting mobile data watcher")
+ watchForMobileDataConnectivityChanges()
+ }
+ }
+ if (setting.isTunnelOnEthernetEnabled) {
+ launch {
+ Timber.d("Starting ethernet data watcher")
+ watchForEthernetConnectivityChanges()
+ }
+ }
+ launch {
+ Timber.d("Starting vpn state watcher")
+ watchForVpnConnectivityChanges()
+ }
+ launch {
+ Timber.d("Starting settings watcher")
+ watchForSettingsChanges()
+ }
+ launch {
+ Timber.d("Starting management watcher")
+ manageVpn()
+ }
+ }
+ }
+
+ private suspend fun watchForMobileDataConnectivityChanges() {
+ mobileDataService.networkStatus.collect {
+ when (it) {
+ is NetworkStatus.Available -> {
+ Timber.d("Gained Mobile data connection")
+ networkEventsFlow.value =
+ networkEventsFlow.value.copy(
+ isMobileDataConnected = true,
+ )
+ }
+ is NetworkStatus.CapabilitiesChanged -> {
+ networkEventsFlow.value =
+ networkEventsFlow.value.copy(
+ isMobileDataConnected = true,
+ )
+ Timber.d("Mobile data capabilities changed")
+ }
+ is NetworkStatus.Unavailable -> {
+ networkEventsFlow.value =
+ networkEventsFlow.value.copy(
+ isMobileDataConnected = false,
+ )
+ Timber.d("Lost mobile data connection")
+ }
+ }
+ }
+ }
+
private suspend fun watchForSettingsChanges() {
settingsRepository.getSettingsFlow().collect {
- if(networkEventsFlow.value.settings.isAutoTunnelPaused != it.isAutoTunnelPaused) {
- when(it.isAutoTunnelPaused) {
+ if (networkEventsFlow.value.settings.isAutoTunnelPaused != it.isAutoTunnelPaused) {
+ when (it.isAutoTunnelPaused) {
true -> launchWatcherPausedNotification()
false -> launchWatcherNotification()
}
}
- networkEventsFlow.value = networkEventsFlow.value.copy(
- settings = it
- )
+ networkEventsFlow.value =
+ networkEventsFlow.value.copy(
+ settings = it,
+ )
}
}
private suspend fun watchForVpnConnectivityChanges() {
vpnService.vpnState.collect {
- when(it.status) {
- Tunnel.State.DOWN -> networkEventsFlow.value = networkEventsFlow.value.copy(
- isVpnConnected = false
- )
- Tunnel.State.UP -> networkEventsFlow.value = networkEventsFlow.value.copy(
- isVpnConnected = true
- )
+ when (it.status) {
+ Tunnel.State.DOWN ->
+ networkEventsFlow.value =
+ networkEventsFlow.value.copy(
+ isVpnConnected = false,
+ )
+ Tunnel.State.UP ->
+ networkEventsFlow.value =
+ networkEventsFlow.value.copy(
+ isVpnConnected = true,
+ )
else -> {}
}
}
}
- private suspend fun watchForEthernetConnectivityChanges() {
- ethernetService.networkStatus.collect {
- when (it) {
- is NetworkStatus.Available -> {
- Timber.d("Gained Ethernet connection")
- networkEventsFlow.value = networkEventsFlow.value.copy(
- isEthernetConnected = true
- )
- }
- is NetworkStatus.CapabilitiesChanged -> {
- Timber.d("Ethernet capabilities changed")
- networkEventsFlow.value = networkEventsFlow.value.copy(
- isEthernetConnected = true
- )
- }
- is NetworkStatus.Unavailable -> {
- networkEventsFlow.value = networkEventsFlow.value.copy(
- isEthernetConnected = false
- )
- Timber.d("Lost Ethernet connection")
- }
- }
- }
- }
-
- private suspend fun watchForWifiConnectivityChanges() {
- wifiService.networkStatus.collect {
- when (it) {
- is NetworkStatus.Available -> {
- Timber.d("Gained Wi-Fi connection")
- networkEventsFlow.value = networkEventsFlow.value.copy(
- isWifiConnected = true
- )
- }
- is NetworkStatus.CapabilitiesChanged -> {
- Timber.d("Wifi capabilities changed")
- networkEventsFlow.value = networkEventsFlow.value.copy(
- isWifiConnected = true
- )
- val ssid = wifiService.getNetworkName(it.networkCapabilities) ?: ""
- Timber.d("Detected SSID: $ssid")
- networkEventsFlow.value = networkEventsFlow.value.copy(
- currentNetworkSSID = ssid
- )
- }
- is NetworkStatus.Unavailable -> {
- networkEventsFlow.value = networkEventsFlow.value.copy(
- isWifiConnected = false
- )
- Timber.d("Lost Wi-Fi connection")
- }
- }
- }
- }
-
- //TODO clean this up
- private suspend fun manageVpn() {
- networkEventsFlow.collectLatest {
- Timber.i("New watcher state: $it")
- if (!it.settings.isAutoTunnelPaused && it.settings.defaultTunnel != null) {
- delay(Constants.TOGGLE_TUNNEL_DELAY)
- when {
- ((it.isEthernetConnected &&
- it.settings.isTunnelOnEthernetEnabled &&
- !it.isVpnConnected)) -> {
- ServiceManager.startVpnServiceForeground(this, it.settings.defaultTunnel!!)
- Timber.i("Condition 1 met")
+ private suspend fun watchForEthernetConnectivityChanges() {
+ ethernetService.networkStatus.collect {
+ when (it) {
+ is NetworkStatus.Available -> {
+ Timber.d("Gained Ethernet connection")
+ networkEventsFlow.value =
+ networkEventsFlow.value.copy(
+ isEthernetConnected = true,
+ )
}
- (!it.isEthernetConnected &&
- it.settings.isTunnelOnMobileDataEnabled &&
- !it.isWifiConnected &&
- it.isMobileDataConnected &&
- !it.isVpnConnected) -> {
- ServiceManager.startVpnServiceForeground(this, it.settings.defaultTunnel!!)
- Timber.i("Condition 2 met")
+ is NetworkStatus.CapabilitiesChanged -> {
+ Timber.d("Ethernet capabilities changed")
+ networkEventsFlow.value =
+ networkEventsFlow.value.copy(
+ isEthernetConnected = true,
+ )
}
- (!it.isEthernetConnected &&
- !it.settings.isTunnelOnMobileDataEnabled &&
- !it.isWifiConnected &&
- it.isVpnConnected) -> {
- ServiceManager.stopVpnService(this)
- Timber.i("Condition 3 met")
- }
- (!it.isEthernetConnected &&
- it.isWifiConnected &&
- !it.settings.trustedNetworkSSIDs.contains(it.currentNetworkSSID) &&
- it.settings.isTunnelOnWifiEnabled &&
- (!it.isVpnConnected)) -> {
- ServiceManager.startVpnServiceForeground(this, it.settings.defaultTunnel!!)
- Timber.i("Condition 4 met")
- }
- (!it.isEthernetConnected &&
- (it.isWifiConnected && it.settings.trustedNetworkSSIDs.contains(it.currentNetworkSSID)) &&
- (it.isVpnConnected)) -> {
- ServiceManager.stopVpnService(this)
- Timber.i("Condition 5 met")
- }
- (!it.isEthernetConnected &&
- (it.isWifiConnected &&
- !it.settings.isTunnelOnWifiEnabled &&
- (it.isVpnConnected))) -> {
- ServiceManager.stopVpnService(this)
- Timber.i("Condition 6 met")
- }
- (!it.isEthernetConnected &&
- !it.isWifiConnected &&
- !it.isMobileDataConnected &&
- (it.isVpnConnected)) -> {
- ServiceManager.stopVpnService(this)
- Timber.i("Condition 7 met")
- }
- else -> {
- Timber.i("No condition met")
+ is NetworkStatus.Unavailable -> {
+ networkEventsFlow.value =
+ networkEventsFlow.value.copy(
+ isEthernetConnected = false,
+ )
+ Timber.d("Lost Ethernet connection")
+ }
+ }
+ }
+ }
+
+ private suspend fun watchForWifiConnectivityChanges() {
+ wifiService.networkStatus.collect {
+ when (it) {
+ is NetworkStatus.Available -> {
+ Timber.d("Gained Wi-Fi connection")
+ networkEventsFlow.value =
+ networkEventsFlow.value.copy(
+ isWifiConnected = true,
+ )
+ }
+ is NetworkStatus.CapabilitiesChanged -> {
+ Timber.d("Wifi capabilities changed")
+ networkEventsFlow.value =
+ networkEventsFlow.value.copy(
+ isWifiConnected = true,
+ )
+ val ssid = wifiService.getNetworkName(it.networkCapabilities) ?: ""
+ Timber.d("Detected SSID: $ssid")
+ networkEventsFlow.value =
+ networkEventsFlow.value.copy(
+ currentNetworkSSID = ssid,
+ )
+ }
+ is NetworkStatus.Unavailable -> {
+ networkEventsFlow.value =
+ networkEventsFlow.value.copy(
+ isWifiConnected = false,
+ )
+ Timber.d("Lost Wi-Fi connection")
+ }
+ }
+ }
+ }
+
+ // TODO clean this up
+ private suspend fun manageVpn() {
+ networkEventsFlow.collectLatest {
+ Timber.i("New watcher state: $it")
+ if (!it.settings.isAutoTunnelPaused && it.settings.defaultTunnel != null) {
+ delay(Constants.TOGGLE_TUNNEL_DELAY)
+ when {
+ ((it.isEthernetConnected &&
+ it.settings.isTunnelOnEthernetEnabled &&
+ !it.isVpnConnected)) -> {
+ ServiceManager.startVpnServiceForeground(this, it.settings.defaultTunnel!!)
+ Timber.i("Condition 1 met")
+ }
+ (!it.isEthernetConnected &&
+ it.settings.isTunnelOnMobileDataEnabled &&
+ !it.isWifiConnected &&
+ it.isMobileDataConnected &&
+ !it.isVpnConnected) -> {
+ ServiceManager.startVpnServiceForeground(this, it.settings.defaultTunnel!!)
+ Timber.i("Condition 2 met")
+ }
+ (!it.isEthernetConnected &&
+ !it.settings.isTunnelOnMobileDataEnabled &&
+ !it.isWifiConnected &&
+ it.isVpnConnected) -> {
+ ServiceManager.stopVpnService(this)
+ Timber.i("Condition 3 met")
+ }
+ (!it.isEthernetConnected &&
+ it.isWifiConnected &&
+ !it.settings.trustedNetworkSSIDs.contains(it.currentNetworkSSID) &&
+ it.settings.isTunnelOnWifiEnabled &&
+ (!it.isVpnConnected)) -> {
+ ServiceManager.startVpnServiceForeground(this, it.settings.defaultTunnel!!)
+ Timber.i("Condition 4 met")
+ }
+ (!it.isEthernetConnected &&
+ (it.isWifiConnected &&
+ it.settings.trustedNetworkSSIDs.contains(it.currentNetworkSSID)) &&
+ (it.isVpnConnected)) -> {
+ ServiceManager.stopVpnService(this)
+ Timber.i("Condition 5 met")
+ }
+ (!it.isEthernetConnected &&
+ (it.isWifiConnected &&
+ !it.settings.isTunnelOnWifiEnabled &&
+ (it.isVpnConnected))) -> {
+ ServiceManager.stopVpnService(this)
+ Timber.i("Condition 6 met")
+ }
+ (!it.isEthernetConnected &&
+ !it.isWifiConnected &&
+ !it.isMobileDataConnected &&
+ (it.isVpnConnected)) -> {
+ ServiceManager.stopVpnService(this)
+ Timber.i("Condition 7 met")
+ }
+ else -> {
+ Timber.i("No condition met")
+ }
}
}
}
}
- }
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardTunnelService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardTunnelService.kt
index 62641b97..f4501acd 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardTunnelService.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardTunnelService.kt
@@ -28,17 +28,13 @@ import javax.inject.Inject
class WireGuardTunnelService : ForegroundService() {
private val foregroundId = 123
- @Inject
- lateinit var vpnService: VpnService
+ @Inject lateinit var vpnService: VpnService
- @Inject
- lateinit var settingsRepository: SettingsRepository
+ @Inject lateinit var settingsRepository: SettingsRepository
- @Inject
- lateinit var tunnelConfigRepository: TunnelConfigRepository
+ @Inject lateinit var tunnelConfigRepository: TunnelConfigRepository
- @Inject
- lateinit var notificationService: NotificationService
+ @Inject lateinit var notificationService: NotificationService
private lateinit var job: Job
@@ -48,7 +44,7 @@ class WireGuardTunnelService : ForegroundService() {
override fun onCreate() {
super.onCreate()
lifecycleScope.launch(Dispatchers.Main) {
- if(tunnelConfigRepository.getAll().isNotEmpty()) {
+ if (tunnelConfigRepository.getAll().isNotEmpty()) {
launchVpnNotification()
}
}
@@ -58,11 +54,10 @@ class WireGuardTunnelService : ForegroundService() {
super.startService(extras)
cancelJob()
val tunnelConfigString = extras?.getString(getString(R.string.tunnel_extras_key))
- val tunnelConfig = tunnelConfigString?.let {
- TunnelConfig.from(it)
- }
+ val tunnelConfig = tunnelConfigString?.let { TunnelConfig.from(it) }
tunnelName = tunnelConfig?.name ?: ""
- job = lifecycleScope.launch(Dispatchers.IO) {
+ job =
+ lifecycleScope.launch(Dispatchers.IO) {
launch {
if (tunnelConfig != null) {
try {
@@ -77,22 +72,22 @@ class WireGuardTunnelService : ForegroundService() {
val settings = settingsRepository.getSettings()
val tunnels = tunnelConfigRepository.getAll()
if (settings.isAlwaysOnVpnEnabled) {
- val tunnel = if(settings.defaultTunnel != null) {
- TunnelConfig.from(settings.defaultTunnel!!)
- } else if(tunnels.isNotEmpty()) {
- tunnels.first()
- } else {
- null
- }
- if(tunnel != null) {
+ val tunnel =
+ if (settings.defaultTunnel != null) {
+ TunnelConfig.from(settings.defaultTunnel!!)
+ } else if (tunnels.isNotEmpty()) {
+ tunnels.first()
+ } else {
+ null
+ }
+ if (tunnel != null) {
tunnelName = tunnel.name
vpnService.startTunnel(tunnel)
}
-
}
}
}
- //TODO add failed to connect notification
+ // TODO add failed to connect notification
launch {
vpnService.vpnState.collect { state ->
state.statistics
@@ -101,14 +96,18 @@ class WireGuardTunnelService : ForegroundService() {
.let { statuses ->
when {
statuses?.all { it == HandshakeStatus.HEALTHY } == true -> {
- if(!didShowConnected){
+ if (!didShowConnected) {
delay(Constants.VPN_CONNECTED_NOTIFICATION_DELAY)
- launchVpnNotification(getString(R.string.tunnel_start_title),"${getString(R.string.tunnel_start_text)} $tunnelName")
+ launchVpnNotification(
+ getString(R.string.tunnel_start_title),
+ "${getString(R.string.tunnel_start_text)} $tunnelName",
+ )
didShowConnected = true
}
}
statuses?.any { it == HandshakeStatus.STALE } == true -> {}
- statuses?.all { it == HandshakeStatus.NOT_STARTED } == true -> {}
+ statuses?.all { it == HandshakeStatus.NOT_STARTED } ==
+ true -> {}
else -> {}
}
}
@@ -127,7 +126,10 @@ class WireGuardTunnelService : ForegroundService() {
stopSelf()
}
- private fun launchVpnNotification(title : String = getString(R.string.vpn_starting),description : String = getString(R.string.attempt_connection)) {
+ private fun launchVpnNotification(
+ title: String = getString(R.string.vpn_starting),
+ description: String = getString(R.string.attempt_connection)
+ ) {
val notification =
notificationService.createNotification(
channelId = getString(R.string.vpn_channel_id),
@@ -136,13 +138,13 @@ class WireGuardTunnelService : ForegroundService() {
onGoing = false,
vibration = false,
showTimestamp = true,
- description = description
+ description = description,
)
ServiceCompat.startForeground(
this,
foregroundId,
notification,
- Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID
+ Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID,
)
}
@@ -152,24 +154,24 @@ class WireGuardTunnelService : ForegroundService() {
channelId = getString(R.string.vpn_channel_id),
channelName = getString(R.string.vpn_channel_name),
action =
- PendingIntent.getBroadcast(
- this,
- 0,
- Intent(this, NotificationActionReceiver::class.java),
- PendingIntent.FLAG_IMMUTABLE
- ),
+ PendingIntent.getBroadcast(
+ this,
+ 0,
+ Intent(this, NotificationActionReceiver::class.java),
+ PendingIntent.FLAG_IMMUTABLE,
+ ),
actionText = getString(R.string.restart),
title = getString(R.string.vpn_connection_failed),
onGoing = false,
vibration = true,
showTimestamp = true,
- description = message
+ description = message,
)
ServiceCompat.startForeground(
this,
foregroundId,
notification,
- Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID
+ Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID,
)
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/BaseNetworkService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/BaseNetworkService.kt
index 4f5e4162..56be1883 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/BaseNetworkService.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/BaseNetworkService.kt
@@ -24,72 +24,69 @@ abstract class BaseNetworkService>(
private val wifiManager =
context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
- override val networkStatus =
- callbackFlow {
- val networkStatusCallback =
- when (Build.VERSION.SDK_INT) {
- in Build.VERSION_CODES.S..Int.MAX_VALUE -> {
- object : ConnectivityManager.NetworkCallback(
- FLAG_INCLUDE_LOCATION_INFO
+ override val networkStatus = callbackFlow {
+ val networkStatusCallback =
+ when (Build.VERSION.SDK_INT) {
+ in Build.VERSION_CODES.S..Int.MAX_VALUE -> {
+ object :
+ ConnectivityManager.NetworkCallback(
+ FLAG_INCLUDE_LOCATION_INFO,
) {
- override fun onAvailable(network: Network) {
- trySend(NetworkStatus.Available(network))
- }
-
- override fun onLost(network: Network) {
- trySend(NetworkStatus.Unavailable(network))
- }
-
- override fun onCapabilitiesChanged(
- network: Network,
- networkCapabilities: NetworkCapabilities
- ) {
- trySend(
- NetworkStatus.CapabilitiesChanged(
- network,
- networkCapabilities
- )
- )
- }
+ override fun onAvailable(network: Network) {
+ trySend(NetworkStatus.Available(network))
}
- }
- else -> {
- object : ConnectivityManager.NetworkCallback() {
- override fun onAvailable(network: Network) {
- trySend(NetworkStatus.Available(network))
- }
+ override fun onLost(network: Network) {
+ trySend(NetworkStatus.Unavailable(network))
+ }
- override fun onLost(network: Network) {
- trySend(NetworkStatus.Unavailable(network))
- }
-
- override fun onCapabilitiesChanged(
- network: Network,
- networkCapabilities: NetworkCapabilities
- ) {
- trySend(
- NetworkStatus.CapabilitiesChanged(
- network,
- networkCapabilities
- )
- )
- }
+ override fun onCapabilitiesChanged(
+ network: Network,
+ networkCapabilities: NetworkCapabilities
+ ) {
+ trySend(
+ NetworkStatus.CapabilitiesChanged(
+ network,
+ networkCapabilities,
+ ),
+ )
}
}
}
- val request =
- NetworkRequest.Builder()
- .addTransportType(networkCapability)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
- .build()
- connectivityManager.registerNetworkCallback(request, networkStatusCallback)
+ else -> {
+ object : ConnectivityManager.NetworkCallback() {
+ override fun onAvailable(network: Network) {
+ trySend(NetworkStatus.Available(network))
+ }
- awaitClose {
- connectivityManager.unregisterNetworkCallback(networkStatusCallback)
+ override fun onLost(network: Network) {
+ trySend(NetworkStatus.Unavailable(network))
+ }
+
+ override fun onCapabilitiesChanged(
+ network: Network,
+ networkCapabilities: NetworkCapabilities
+ ) {
+ trySend(
+ NetworkStatus.CapabilitiesChanged(
+ network,
+ networkCapabilities,
+ ),
+ )
+ }
+ }
+ }
}
- }
+ val request =
+ NetworkRequest.Builder()
+ .addTransportType(networkCapability)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build()
+ connectivityManager.registerNetworkCallback(request, networkStatusCallback)
+
+ awaitClose { connectivityManager.unregisterNetworkCallback(networkStatusCallback) }
+ }
override fun getNetworkName(networkCapabilities: NetworkCapabilities): String? {
var ssid: String? = getWifiNameFromCapabilities(networkCapabilities)
@@ -119,18 +116,16 @@ abstract class BaseNetworkService>(
inline fun Flow.map(
crossinline onUnavailable: suspend (network: Network) -> Result,
crossinline onAvailable: suspend (network: Network) -> Result,
- crossinline onCapabilitiesChanged: suspend (
- network: Network,
- networkCapabilities: NetworkCapabilities
- ) -> Result
-): Flow =
- map { status ->
- when (status) {
- is NetworkStatus.Unavailable -> onUnavailable(status.network)
- is NetworkStatus.Available -> onAvailable(status.network)
- is NetworkStatus.CapabilitiesChanged -> onCapabilitiesChanged(
+ crossinline onCapabilitiesChanged:
+ suspend (network: Network, networkCapabilities: NetworkCapabilities) -> Result
+): Flow = map { status ->
+ when (status) {
+ is NetworkStatus.Unavailable -> onUnavailable(status.network)
+ is NetworkStatus.Available -> onAvailable(status.network)
+ is NetworkStatus.CapabilitiesChanged ->
+ onCapabilitiesChanged(
status.network,
- status.networkCapabilities
+ status.networkCapabilities,
)
- }
}
+}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/EthernetService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/EthernetService.kt
index 1f868369..36f76eb7 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/EthernetService.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/EthernetService.kt
@@ -5,9 +5,5 @@ import android.net.NetworkCapabilities
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
-class EthernetService
-@Inject
-constructor(
- @ApplicationContext context: Context
-) :
+class EthernetService @Inject constructor(@ApplicationContext context: Context) :
BaseNetworkService(context, NetworkCapabilities.TRANSPORT_ETHERNET)
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/MobileDataService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/MobileDataService.kt
index a488e3aa..626ca4e0 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/MobileDataService.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/MobileDataService.kt
@@ -5,9 +5,5 @@ import android.net.NetworkCapabilities
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
-class MobileDataService
-@Inject
-constructor(
- @ApplicationContext context: Context
-) :
+class MobileDataService @Inject constructor(@ApplicationContext context: Context) :
BaseNetworkService(context, NetworkCapabilities.TRANSPORT_CELLULAR)
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/WifiService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/WifiService.kt
index ebc4797a..aafccb4f 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/WifiService.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/WifiService.kt
@@ -5,9 +5,5 @@ import android.net.NetworkCapabilities
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
-class WifiService
-@Inject
-constructor(
- @ApplicationContext context: Context
-) :
+class WifiService @Inject constructor(@ApplicationContext context: Context) :
BaseNetworkService(context, NetworkCapabilities.TRANSPORT_WIFI)
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationService.kt
index 005f1e62..cabd17b1 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationService.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationService.kt
@@ -3,7 +3,6 @@ package com.zaneschepke.wireguardautotunnel.service.notification
import android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
-import androidx.core.app.NotificationCompat
interface NotificationService {
fun createNotification(
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt
index 31803197..8a8cfbff 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt
@@ -13,23 +13,21 @@ import com.zaneschepke.wireguardautotunnel.ui.MainActivity
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
-class WireGuardNotification
-@Inject
-constructor(
- @ApplicationContext private val context: Context
-) : NotificationService {
+class WireGuardNotification @Inject constructor(@ApplicationContext private val context: Context) :
+ NotificationService {
private val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
private val watcherBuilder: NotificationCompat.Builder =
NotificationCompat.Builder(
context,
- context.getString(R.string.watcher_channel_id)
+ context.getString(R.string.watcher_channel_id),
+ )
+ private val tunnelBuilder: NotificationCompat.Builder =
+ NotificationCompat.Builder(
+ context,
+ context.getString(R.string.vpn_channel_id),
)
- private val tunnelBuilder: NotificationCompat.Builder = NotificationCompat.Builder(
- context,
- context.getString(R.string.vpn_channel_id)
- )
override fun createNotification(
channelId: String,
@@ -47,17 +45,18 @@ constructor(
): Notification {
val channel =
NotificationChannel(
- channelId,
- channelName,
- importance
- ).let {
- it.description = title
- it.enableLights(lights)
- it.lightColor = Color.RED
- it.enableVibration(vibration)
- it.vibrationPattern = longArrayOf(100,200,300)
- it
- }
+ channelId,
+ channelName,
+ importance,
+ )
+ .let {
+ it.description = title
+ it.enableLights(lights)
+ it.lightColor = Color.RED
+ it.enableVibration(vibration)
+ it.vibrationPattern = longArrayOf(100, 200, 300)
+ it
+ }
notificationManager.createNotificationChannel(channel)
val pendingIntent: PendingIntent =
Intent(context, MainActivity::class.java).let { notificationIntent ->
@@ -65,26 +64,26 @@ constructor(
context,
0,
notificationIntent,
- PendingIntent.FLAG_IMMUTABLE
+ PendingIntent.FLAG_IMMUTABLE,
)
}
- val builder = when(channelId) {
- context.getString(R.string.watcher_channel_id) -> watcherBuilder
- context.getString(R.string.vpn_channel_id) -> tunnelBuilder
- else -> {
- NotificationCompat.Builder(
- context,
- channelId
- )
+ val builder =
+ when (channelId) {
+ context.getString(R.string.watcher_channel_id) -> watcherBuilder
+ context.getString(R.string.vpn_channel_id) -> tunnelBuilder
+ else -> {
+ NotificationCompat.Builder(
+ context,
+ channelId,
+ )
+ }
}
- }
return builder.let {
if (action != null && actionText != null) {
it.addAction(
- NotificationCompat.Action.Builder(0, actionText, action)
- .build()
+ NotificationCompat.Action.Builder(0, actionText, action).build(),
)
it.setAutoCancel(true)
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsActivity.kt
index 6ab9c78c..f5d1e0f2 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsActivity.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsActivity.kt
@@ -12,33 +12,34 @@ import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelService
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
class ShortcutsActivity : ComponentActivity() {
- @Inject
- lateinit var settingsRepository: SettingsRepository
+ @Inject lateinit var settingsRepository: SettingsRepository
- @Inject
- lateinit var tunnelConfigRepository: TunnelConfigRepository
+ @Inject lateinit var tunnelConfigRepository: TunnelConfigRepository
private suspend fun toggleWatcherServicePause() {
- val settings = settingsRepository.getSettings()
- if (settings.isAutoTunnelEnabled) {
- val pauseAutoTunnel = !settings.isAutoTunnelPaused
- settingsRepository.save(settings.copy(
- isAutoTunnelPaused = pauseAutoTunnel
- ))
- }
+ val settings = settingsRepository.getSettings()
+ if (settings.isAutoTunnelEnabled) {
+ val pauseAutoTunnel = !settings.isAutoTunnelPaused
+ settingsRepository.save(
+ settings.copy(
+ isAutoTunnelPaused = pauseAutoTunnel,
+ ),
+ )
}
+ }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(View(this))
- if (intent.getStringExtra(CLASS_NAME_EXTRA_KEY)
+ if (
+ intent
+ .getStringExtra(CLASS_NAME_EXTRA_KEY)
.equals(WireGuardTunnelService::class.java.simpleName)
) {
lifecycleScope.launch(Dispatchers.Main) {
@@ -48,7 +49,9 @@ class ShortcutsActivity : ComponentActivity() {
val tunnelName = intent.getStringExtra(TUNNEL_NAME_EXTRA_KEY)
val tunnelConfig =
if (tunnelName != null) {
- tunnelConfigRepository.getAll().firstOrNull { it.name == tunnelName }
+ tunnelConfigRepository.getAll().firstOrNull {
+ it.name == tunnelName
+ }
} else {
if (settings.defaultTunnel == null) {
tunnelConfigRepository.getAll().first()
@@ -59,13 +62,15 @@ class ShortcutsActivity : ComponentActivity() {
tunnelConfig ?: return@launch
toggleWatcherServicePause()
when (intent.action) {
- Action.STOP.name -> ServiceManager.stopVpnService(
- this@ShortcutsActivity
- )
- Action.START.name -> ServiceManager.startVpnServiceForeground(
- this@ShortcutsActivity,
- tunnelConfig.toString()
- )
+ Action.STOP.name ->
+ ServiceManager.stopVpnService(
+ this@ShortcutsActivity,
+ )
+ Action.START.name ->
+ ServiceManager.startVpnServiceForeground(
+ this@ShortcutsActivity,
+ tunnelConfig.toString(),
+ )
}
} catch (e: Exception) {
Timber.e(e.message)
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt
index efc4a9d7..e385a90a 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt
@@ -20,44 +20,43 @@ import javax.inject.Inject
@AndroidEntryPoint
class TunnelControlTile() : TileService() {
- @Inject
- lateinit var tunnelConfigRepository: TunnelConfigRepository
+ @Inject lateinit var tunnelConfigRepository: TunnelConfigRepository
- @Inject
- lateinit var settingsRepository: SettingsRepository
+ @Inject lateinit var settingsRepository: SettingsRepository
- @Inject
- lateinit var vpnService: VpnService
+ @Inject lateinit var vpnService: VpnService
private val scope = CoroutineScope(Dispatchers.IO)
- private var tunnelName : String? = null
+ private var tunnelName: String? = null
override fun onStartListening() {
super.onStartListening()
Timber.d("On start listening called")
scope.launch {
vpnService.vpnState.collect {
- when(it.status) {
+ when (it.status) {
Tunnel.State.UP -> setActive()
Tunnel.State.DOWN -> setInactive()
else -> setInactive()
}
val tunnels = tunnelConfigRepository.getAll()
- if(tunnels.isEmpty()) {
+ if (tunnels.isEmpty()) {
setUnavailable()
return@collect
}
- tunnelName = it.name.ifBlank {
- val settings = settingsRepository.getSettings()
- if (settings.defaultTunnel != null) {
- TunnelConfig.from(settings.defaultTunnel!!).name
- } else tunnels.firstOrNull()?.name
- }
+ tunnelName =
+ it.name.ifBlank {
+ val settings = settingsRepository.getSettings()
+ if (settings.defaultTunnel != null) {
+ TunnelConfig.from(settings.defaultTunnel!!).name
+ } else tunnels.firstOrNull()?.name
+ }
setTileDescription(tunnelName ?: "")
}
}
}
+
override fun onDestroy() {
super.onDestroy()
scope.cancel()
@@ -73,14 +72,15 @@ class TunnelControlTile() : TileService() {
unlockAndRun {
scope.launch {
try {
- val tunnelConfig = tunnelConfigRepository.getAll().first { it.name == tunnelName }
+ val tunnelConfig =
+ tunnelConfigRepository.getAll().first { it.name == tunnelName }
toggleWatcherServicePause()
if (vpnService.getState() == Tunnel.State.UP) {
ServiceManager.stopVpnService(this@TunnelControlTile)
} else {
ServiceManager.startVpnServiceForeground(
this@TunnelControlTile,
- tunnelConfig.toString()
+ tunnelConfig.toString(),
)
}
} catch (e: Exception) {
@@ -97,9 +97,11 @@ class TunnelControlTile() : TileService() {
val settings = settingsRepository.getSettings()
if (settings.isAutoTunnelEnabled) {
val pauseAutoTunnel = !settings.isAutoTunnelPaused
- settingsRepository.save(settings.copy(
- isAutoTunnelPaused = pauseAutoTunnel
- ))
+ settingsRepository.save(
+ settings.copy(
+ isAutoTunnelPaused = pauseAutoTunnel,
+ ),
+ )
}
}
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/HandshakeStatus.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/HandshakeStatus.kt
index 87621cca..13bd8a7f 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/HandshakeStatus.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/HandshakeStatus.kt
@@ -4,13 +4,13 @@ enum class HandshakeStatus {
HEALTHY,
STALE,
UNKNOWN,
- NOT_STARTED
- ;
+ NOT_STARTED;
companion object {
private const val WG_TYPICAL_HANDSHAKE_INTERVAL_WHEN_HEALTHY_SEC = 180
const val STATUS_CHANGE_TIME_BUFFER = 30
- const val STALE_TIME_LIMIT_SEC = WG_TYPICAL_HANDSHAKE_INTERVAL_WHEN_HEALTHY_SEC + STATUS_CHANGE_TIME_BUFFER
+ const val STALE_TIME_LIMIT_SEC =
+ WG_TYPICAL_HANDSHAKE_INTERVAL_WHEN_HEALTHY_SEC + STATUS_CHANGE_TIME_BUFFER
const val NEVER_CONNECTED_TO_UNHEALTHY_TIME_LIMIT_SEC = 30
}
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnState.kt
index 077f7ada..730238c6 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnState.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnState.kt
@@ -4,7 +4,7 @@ import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Tunnel
data class VpnState(
- val status : Tunnel.State = Tunnel.State.DOWN,
- val name : String = "",
- val statistics : Statistics? = null
+ val status: Tunnel.State = Tunnel.State.DOWN,
+ val name: String = "",
+ val statistics: Statistics? = null
)
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt
index 53c039c4..a6974f9f 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt
@@ -67,7 +67,7 @@ constructor(
backend.setState(
this,
State.UP,
- config
+ config,
)
emitTunnelState(state)
state
@@ -80,24 +80,24 @@ constructor(
private fun emitTunnelState(state: State) {
_vpnState.tryEmit(
_vpnState.value.copy(
- status = state
- )
+ status = state,
+ ),
)
}
private fun emitBackendStatistics(statistics: Statistics) {
_vpnState.tryEmit(
_vpnState.value.copy(
- statistics = statistics
- )
+ statistics = statistics,
+ ),
)
}
private suspend fun emitTunnelName(name: String) {
_vpnState.emit(
_vpnState.value.copy(
- name = name
- )
+ name = name,
+ ),
)
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/ActivityViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/ActivityViewModel.kt
index 375611c7..bee1b9ba 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/ActivityViewModel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/ActivityViewModel.kt
@@ -6,8 +6,8 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
-class ActivityViewModel @Inject constructor(
+class ActivityViewModel
+@Inject
+constructor(
private val settingsRepo: SettingsDao,
-) : ViewModel() {
-
-}
+) : ViewModel() {}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt
index d4a0ca20..6b83a611 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt
@@ -28,6 +28,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
@@ -37,6 +38,9 @@ import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.wireguard.android.backend.GoBackend
import com.zaneschepke.wireguardautotunnel.R
+import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
+import com.zaneschepke.wireguardautotunnel.data.datastore.DataStoreManager
+import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
import com.zaneschepke.wireguardautotunnel.ui.common.PermissionRequestFailedScreen
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.CustomSnackBar
@@ -50,19 +54,40 @@ import com.zaneschepke.wireguardautotunnel.util.Constants
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import timber.log.Timber
+import java.io.IOException
+import javax.inject.Inject
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
+
+ @Inject
+ lateinit var dataStoreManager: DataStoreManager
+
+ @Inject lateinit var settingsRepository: SettingsRepository
@OptIn(
- ExperimentalPermissionsApi::class
+ ExperimentalPermissionsApi::class,
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
+ // load preferences into memory and init data
+ lifecycleScope.launch {
+ try {
+ dataStoreManager.init()
+ if (settingsRepository.getAll().isEmpty()) {
+ settingsRepository.save(com.zaneschepke.wireguardautotunnel.data.model.Settings())
+ }
+ WireGuardAutoTunnel.requestTileServiceStateUpdate()
+ } catch (e: IOException) {
+ Timber.e("Failed to load preferences")
+ }
+ }
setContent {
-// val activityViewModel = hiltViewModel()
+ // val activityViewModel = hiltViewModel()
val navController = rememberNavController()
- val focusRequester = remember { FocusRequester()}
+ val focusRequester = remember { FocusRequester() }
WireguardAutoTunnelTheme {
TransparentSystemBars()
@@ -73,7 +98,10 @@ class MainActivity : AppCompatActivity() {
rememberPermissionState(Manifest.permission.POST_NOTIFICATIONS)
fun requestNotificationPermission() {
- if (!notificationPermissionState.status.isGranted && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (
+ !notificationPermissionState.status.isGranted &&
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+ ) {
notificationPermissionState.launchPermissionRequest()
}
}
@@ -87,7 +115,7 @@ class MainActivity : AppCompatActivity() {
if (accepted) {
vpnIntent = null
}
- }
+ },
)
LaunchedEffect(vpnIntent) {
if (vpnIntent != null) {
@@ -99,13 +127,15 @@ class MainActivity : AppCompatActivity() {
fun showSnackBarMessage(message: String) {
lifecycleScope.launch(Dispatchers.Main) {
- val result = snackbarHostState.showSnackbar(
+ val result =
+ snackbarHostState.showSnackbar(
message = message,
actionLabel = applicationContext.getString(R.string.okay),
- duration = SnackbarDuration.Short
+ duration = SnackbarDuration.Short,
)
when (result) {
- SnackbarResult.ActionPerformed, SnackbarResult.Dismissed -> {
+ SnackbarResult.ActionPerformed,
+ SnackbarResult.Dismissed -> {
snackbarHostState.currentSnackbarData?.dismiss()
}
}
@@ -118,29 +148,36 @@ class MainActivity : AppCompatActivity() {
CustomSnackBar(
snackbarData.visuals.message,
isRtl = false,
- containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
- 2.dp
- )
+ containerColor =
+ MaterialTheme.colorScheme.surfaceColorAtElevation(
+ 2.dp,
+ ),
)
}
},
modifier = Modifier.focusable().focusProperties { up = focusRequester },
bottomBar =
- if (vpnIntent == null && notificationPermissionState.status.isGranted) {
- { BottomNavBar(navController, listOf(
- Screen.Main.navItem,
- Screen.Settings.navItem,
- Screen.Support.navItem)) }
- } else {
- {}
- }
+ if (vpnIntent == null && notificationPermissionState.status.isGranted) {
+ {
+ BottomNavBar(
+ navController,
+ listOf(
+ Screen.Main.navItem,
+ Screen.Settings.navItem,
+ Screen.Support.navItem,
+ ),
+ )
+ }
+ } else {
+ {}
+ },
) { padding ->
if (vpnIntent != null) {
PermissionRequestFailedScreen(
padding = padding,
onRequestAgain = { vpnActivityResultState.launch(vpnIntent) },
message = getString(R.string.vpn_permission_required),
- getString(R.string.retry)
+ getString(R.string.retry),
)
return@Scaffold
}
@@ -154,12 +191,12 @@ class MainActivity : AppCompatActivity() {
Uri.fromParts(
Constants.URI_PACKAGE_SCHEME,
this.packageName,
- null
+ null,
)
startActivity(intentSettings)
},
message = getString(R.string.notification_permission_required),
- getString(R.string.open_settings)
+ getString(R.string.open_settings),
)
return@Scaffold
}
@@ -167,34 +204,42 @@ class MainActivity : AppCompatActivity() {
composable(
Screen.Main.route,
) {
- MainScreen(padding = padding, focusRequester = focusRequester, showSnackbarMessage = { message ->
- showSnackBarMessage(message)
- }, navController = navController)
+ MainScreen(
+ padding = padding,
+ focusRequester = focusRequester,
+ showSnackbarMessage = { message -> showSnackBarMessage(message) },
+ navController = navController,
+ )
}
- composable(Screen.Settings.route,
+ composable(
+ Screen.Settings.route,
) {
- SettingsScreen(padding = padding, showSnackbarMessage = { message ->
- showSnackBarMessage(message)
- }, focusRequester = focusRequester)
+ SettingsScreen(
+ padding = padding,
+ showSnackbarMessage = { message -> showSnackBarMessage(message) },
+ focusRequester = focusRequester,
+ )
}
- composable(Screen.Support.route,
+ composable(
+ Screen.Support.route,
) {
- SupportScreen(padding = padding, focusRequester = focusRequester,
- showSnackbarMessage = { message ->
- showSnackBarMessage(message)
- })
+ SupportScreen(
+ padding = padding,
+ focusRequester = focusRequester,
+ showSnackbarMessage = { message -> showSnackBarMessage(message) },
+ )
}
composable("${Screen.Config.route}/{id}") {
val id = it.arguments?.getString("id")
if (!id.isNullOrBlank()) {
- //https://dagger.dev/hilt/view-model#assisted-injection
+ // https://dagger.dev/hilt/view-model#assisted-injection
ConfigScreen(
navController = navController,
id = id,
showSnackbarMessage = { message ->
showSnackBarMessage(message)
},
- focusRequester = focusRequester
+ focusRequester = focusRequester,
)
}
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Screen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Screen.kt
index 686de5d4..cd4f5eab 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Screen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Screen.kt
@@ -6,28 +6,33 @@ import androidx.compose.material.icons.rounded.QuestionMark
import androidx.compose.material.icons.rounded.Settings
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavItem
-sealed class Screen(val route : String) {
- data object Main: Screen("main") {
- val navItem = BottomNavItem(
- name = "Tunnels",
- route = route,
- icon = Icons.Rounded.Home
- )
+sealed class Screen(val route: String) {
+ data object Main : Screen("main") {
+ val navItem =
+ BottomNavItem(
+ name = "Tunnels",
+ route = route,
+ icon = Icons.Rounded.Home,
+ )
}
- data object Settings: Screen("settings") {
- val navItem = BottomNavItem(
- name = "Settings",
- route = route,
- icon = Icons.Rounded.Settings
- )
- }
- data object Support: Screen("support") {
- val navItem = BottomNavItem(
- name = "Support",
- route = route,
- icon = Icons.Rounded.QuestionMark
- )
- }
- data object Config : Screen("config")
-}
\ No newline at end of file
+ data object Settings : Screen("settings") {
+ val navItem =
+ BottomNavItem(
+ name = "Settings",
+ route = route,
+ icon = Icons.Rounded.Settings,
+ )
+ }
+
+ data object Support : Screen("support") {
+ val navItem =
+ BottomNavItem(
+ name = "Support",
+ route = route,
+ icon = Icons.Rounded.QuestionMark,
+ )
+ }
+
+ data object Config : Screen("config")
+}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/ClickableIconButton.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/ClickableIconButton.kt
index 3bccc4ad..51990dd0 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/ClickableIconButton.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/ClickableIconButton.kt
@@ -23,7 +23,7 @@ fun ClickableIconButton(
) {
TextButton(
onClick = onClick,
- enabled = enabled
+ enabled = enabled,
) {
Text(text, Modifier.weight(1f, false))
Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing))
@@ -31,11 +31,11 @@ fun ClickableIconButton(
imageVector = icon,
contentDescription = stringResource(R.string.delete),
modifier =
- Modifier.size(ButtonDefaults.IconSize).weight(1f, false).clickable {
- if (enabled) {
- onIconClick()
- }
- }
+ Modifier.size(ButtonDefaults.IconSize).weight(1f, false).clickable {
+ if (enabled) {
+ onIconClick()
+ }
+ },
)
}
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/PermissionRequestFailedScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/PermissionRequestFailedScreen.kt
index 686c73a5..11058fb4 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/PermissionRequestFailedScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/PermissionRequestFailedScreen.kt
@@ -26,17 +26,12 @@ fun PermissionRequestFailedScreen(
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
- modifier =
- Modifier
- .fillMaxSize()
- .padding(padding)
+ modifier = Modifier.fillMaxSize().padding(padding),
) {
Text(message, textAlign = TextAlign.Center, modifier = Modifier.padding(15.dp))
- Button(onClick = {
- scope.launch {
- onRequestAgain()
- }
- }) {
+ Button(
+ onClick = { scope.launch { onRequestAgain() } },
+ ) {
Text(buttonText)
}
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/RowListItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/RowListItem.kt
index 9e059a32..1ed4599c 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/RowListItem.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/RowListItem.kt
@@ -34,30 +34,22 @@ fun RowListItem(
) {
Box(
modifier =
- Modifier
- .animateContentSize()
- .clip(RoundedCornerShape(30.dp))
- .combinedClickable(
- onClick = {
- onClick()
- },
- onLongClick = {
- onHold()
- }
- )
+ Modifier.animateContentSize()
+ .clip(RoundedCornerShape(30.dp))
+ .combinedClickable(
+ onClick = { onClick() },
+ onLongClick = { onHold() },
+ ),
) {
Column {
Row(
- modifier =
- Modifier
- .fillMaxWidth()
- .padding(horizontal = 15.dp, vertical = 5.dp),
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 15.dp, vertical = 5.dp),
verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceBetween
+ horizontalArrangement = Arrangement.SpaceBetween,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.fillMaxWidth(.60f)
+ modifier = Modifier.fillMaxWidth(.60f),
) {
icon()
Text(text)
@@ -68,11 +60,10 @@ fun RowListItem(
statistics?.peers()?.forEach {
Row(
modifier =
- Modifier
- .fillMaxWidth()
- .padding(end = 10.dp, bottom = 10.dp, start = 10.dp),
+ Modifier.fillMaxWidth()
+ .padding(end = 10.dp, bottom = 10.dp, start = 10.dp),
verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceEvenly
+ horizontalArrangement = Arrangement.SpaceEvenly,
) {
val handshakeEpoch = statistics.peer(it)!!.latestHandshakeEpochMillis
val peerTx = statistics.peer(it)!!.txBytes
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/SearchBar.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/SearchBar.kt
index 4016f319..95ade9e8 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/SearchBar.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/SearchBar.kt
@@ -47,7 +47,7 @@ fun SearchBar(onQuery: (queryString: String) -> Unit) {
Icon(
imageVector = Icons.Rounded.Search,
tint = MaterialTheme.colorScheme.onBackground,
- contentDescription = stringResource(id = R.string.search_icon)
+ contentDescription = stringResource(id = R.string.search_icon),
)
},
trailingIcon = {
@@ -56,25 +56,24 @@ fun SearchBar(onQuery: (queryString: String) -> Unit) {
Icon(
imageVector = Icons.Rounded.Clear,
tint = MaterialTheme.colorScheme.onBackground,
- contentDescription = stringResource(id = R.string.clear_icon)
+ contentDescription = stringResource(id = R.string.clear_icon),
)
}
}
},
maxLines = 1,
colors =
- TextFieldDefaults.colors(
- focusedContainerColor = Color.Transparent,
- unfocusedContainerColor = Color.Transparent,
- disabledContainerColor = Color.Transparent
- ),
+ TextFieldDefaults.colors(
+ focusedContainerColor = Color.Transparent,
+ unfocusedContainerColor = Color.Transparent,
+ disabledContainerColor = Color.Transparent,
+ ),
placeholder = { Text(text = stringResource(R.string.hint_search_packages)) },
textStyle = MaterialTheme.typography.bodySmall,
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),
modifier =
- Modifier
- .fillMaxWidth()
- .background(color = MaterialTheme.colorScheme.background, shape = RectangleShape)
+ Modifier.fillMaxWidth()
+ .background(color = MaterialTheme.colorScheme.background, shape = RectangleShape),
)
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/ConfigurationTextBox.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/ConfigurationTextBox.kt
index 2bb3dfa7..1226162f 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/ConfigurationTextBox.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/ConfigurationTextBox.kt
@@ -22,19 +22,15 @@ fun ConfigurationTextBox(
modifier = modifier,
value = value,
singleLine = true,
- onValueChange = {
- onValueChange(it)
- },
+ onValueChange = { onValueChange(it) },
label = { Text(label) },
maxLines = 1,
- placeholder = {
- Text(hint)
- },
+ placeholder = { Text(hint) },
keyboardOptions =
- KeyboardOptions(
- capitalization = KeyboardCapitalization.None,
- imeAction = ImeAction.Done
- ),
- keyboardActions = keyboardActions
+ KeyboardOptions(
+ capitalization = KeyboardCapitalization.None,
+ imeAction = ImeAction.Done,
+ ),
+ keyboardActions = keyboardActions,
)
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/ConfigurationToggle.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/ConfigurationToggle.kt
index a4ae21ff..80146c03 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/ConfigurationToggle.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/ConfigurationToggle.kt
@@ -21,21 +21,16 @@ fun ConfigurationToggle(
modifier: Modifier = Modifier
) {
Row(
- modifier =
- Modifier
- .fillMaxWidth()
- .padding(padding),
+ modifier = Modifier.fillMaxWidth().padding(padding),
verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceBetween
+ horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(label)
Switch(
modifier = modifier,
enabled = enabled,
checked = checked,
- onCheckedChange = {
- onCheckChanged()
- }
+ onCheckedChange = { onCheckChanged() },
)
}
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavBar.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavBar.kt
index 1c3e8199..ef9cd207 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavBar.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavBar.kt
@@ -11,14 +11,11 @@ import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState
@Composable
-fun BottomNavBar(
- navController: NavController,
- bottomNavItems: List
-) {
+fun BottomNavBar(navController: NavController, bottomNavItems: List) {
val backStackEntry = navController.currentBackStackEntryAsState()
NavigationBar(
- containerColor = MaterialTheme.colorScheme.background
+ containerColor = MaterialTheme.colorScheme.background,
) {
bottomNavItems.forEach { item ->
val selected = item.route == backStackEntry.value?.destination?.route
@@ -29,15 +26,15 @@ fun BottomNavBar(
label = {
Text(
text = item.name,
- fontWeight = FontWeight.SemiBold
+ fontWeight = FontWeight.SemiBold,
)
},
icon = {
Icon(
imageVector = item.icon,
- contentDescription = "${item.name} Icon"
+ contentDescription = "${item.name} Icon",
)
- }
+ },
)
}
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/prompt/AuthorizationPrompt.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/prompt/AuthorizationPrompt.kt
index 7da6b2e9..bf25174e 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/prompt/AuthorizationPrompt.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/prompt/AuthorizationPrompt.kt
@@ -11,51 +11,40 @@ import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
@Composable
-fun AuthorizationPrompt(
- onSuccess: () -> Unit,
- onFailure: () -> Unit,
- onError: (String) -> Unit
-) {
+fun AuthorizationPrompt(onSuccess: () -> Unit, onFailure: () -> Unit, onError: (String) -> Unit) {
val context = LocalContext.current
val biometricManager = BiometricManager.from(context)
val bio = biometricManager.canAuthenticate(BIOMETRIC_WEAK or DEVICE_CREDENTIAL)
- val isBiometricAvailable =
- remember {
- when (bio) {
- BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> {
- onError("Biometrics not available")
- false
- }
-
- BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
- onError("Biometrics not created")
- false
- }
-
- BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> {
- onError("Biometric hardware not found")
- false
- }
-
- BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> {
- onError("Biometric security update required")
- false
- }
-
- BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> {
- onError("Biometrics not supported")
- false
- }
-
- BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> {
- onError("Biometrics status unknown")
- false
- }
-
- BiometricManager.BIOMETRIC_SUCCESS -> true
- else -> false
+ val isBiometricAvailable = remember {
+ when (bio) {
+ BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> {
+ onError("Biometrics not available")
+ false
}
+ BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
+ onError("Biometrics not created")
+ false
+ }
+ BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> {
+ onError("Biometric hardware not found")
+ false
+ }
+ BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> {
+ onError("Biometric security update required")
+ false
+ }
+ BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> {
+ onError("Biometrics not supported")
+ false
+ }
+ BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> {
+ onError("Biometrics status unknown")
+ false
+ }
+ BiometricManager.BIOMETRIC_SUCCESS -> true
+ else -> false
}
+ }
if (isBiometricAvailable) {
val executor = remember { ContextCompat.getMainExecutor(context) }
@@ -71,10 +60,7 @@ fun AuthorizationPrompt(
context as FragmentActivity,
executor,
object : BiometricPrompt.AuthenticationCallback() {
- override fun onAuthenticationError(
- errorCode: Int,
- errString: CharSequence
- ) {
+ override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
onFailure()
}
@@ -90,7 +76,7 @@ fun AuthorizationPrompt(
super.onAuthenticationFailed()
onFailure()
}
- }
+ },
)
biometricPrompt.authenticate(promptInfo)
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/prompt/CustomSnackbar.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/prompt/CustomSnackbar.kt
index cb55319c..dd46f9f9 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/prompt/CustomSnackbar.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/prompt/CustomSnackbar.kt
@@ -37,25 +37,25 @@ fun CustomSnackBar(
Snackbar(
containerColor = containerColor,
modifier =
- Modifier.fillMaxWidth(
- if (WireGuardAutoTunnel.isRunningOnAndroidTv()) 1 / 3f else 2 / 3f
- ).padding(bottom = 100.dp),
- shape = RoundedCornerShape(16.dp)
+ Modifier.fillMaxWidth(
+ if (WireGuardAutoTunnel.isRunningOnAndroidTv()) 1 / 3f else 2 / 3f,
+ )
+ .padding(bottom = 100.dp),
+ shape = RoundedCornerShape(16.dp),
) {
CompositionLocalProvider(
- LocalLayoutDirection provides
- if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr
+ LocalLayoutDirection provides if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr,
) {
Row(
modifier = Modifier.width(IntrinsicSize.Max).height(IntrinsicSize.Min),
verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.Start
+ horizontalArrangement = Arrangement.Start,
) {
Icon(
Icons.Rounded.Info,
contentDescription = stringResource(R.string.info),
tint = Color.White,
- modifier = Modifier.padding(end = 10.dp)
+ modifier = Modifier.padding(end = 10.dp),
)
Text(message, color = Color.White, modifier = Modifier.padding(end = 5.dp))
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/screen/LoadingScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/screen/LoadingScreen.kt
index 0e6df0fd..ed722733 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/screen/LoadingScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/screen/LoadingScreen.kt
@@ -13,10 +13,11 @@ import androidx.compose.ui.unit.dp
@Composable
fun LoadingScreen() {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Top,
- modifier = Modifier.fillMaxSize().focusable().padding()) {
- Column(modifier = Modifier.padding(120.dp)) { CircularProgressIndicator() }
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Top,
+ modifier = Modifier.fillMaxSize().focusable().padding(),
+ ) {
+ Column(modifier = Modifier.padding(120.dp)) { CircularProgressIndicator() }
}
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/text/SectionTitle.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/text/SectionTitle.kt
index 07d543f1..2d56e662 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/text/SectionTitle.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/text/SectionTitle.kt
@@ -12,14 +12,11 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
-fun SectionTitle(
- title: String,
- padding: Dp
-) {
+fun SectionTitle(title: String, padding: Dp) {
Text(
title,
textAlign = TextAlign.Center,
style = TextStyle(fontSize = 18.sp, fontWeight = FontWeight.ExtraBold),
- modifier = Modifier.padding(padding, bottom = 5.dp, top = 5.dp)
+ modifier = Modifier.padding(padding, bottom = 5.dp, top = 5.dp),
)
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/models/InterfaceProxy.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/models/InterfaceProxy.kt
index 8a443240..caf8b7f3 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/models/InterfaceProxy.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/models/InterfaceProxy.kt
@@ -17,13 +17,13 @@ data class InterfaceProxy(
privateKey = i.keyPair.privateKey.toBase64().trim(),
addresses = i.addresses.joinToString(", ").trim(),
dnsServers = i.dnsServers.joinToString(", ").replace("/", "").trim(),
- listenPort = if (i.listenPort.isPresent) {
- i.listenPort.get().toString()
- .trim()
- } else {
- ""
- },
- mtu = if (i.mtu.isPresent) i.mtu.get().toString().trim() else ""
+ listenPort =
+ if (i.listenPort.isPresent) {
+ i.listenPort.get().toString().trim()
+ } else {
+ ""
+ },
+ mtu = if (i.mtu.isPresent) i.mtu.get().toString().trim() else "",
)
}
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/models/PeerProxy.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/models/PeerProxy.kt
index 3b57ee73..ad5c3cb3 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/models/PeerProxy.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/models/PeerProxy.kt
@@ -13,36 +13,60 @@ data class PeerProxy(
fun from(peer: Peer): PeerProxy {
return PeerProxy(
publicKey = peer.publicKey.toBase64(),
- preSharedKey = if (peer.preSharedKey.isPresent) {
- peer.preSharedKey.get().toBase64()
- .trim()
- } else {
- ""
- },
- persistentKeepalive = if (peer.persistentKeepalive.isPresent) {
- peer.persistentKeepalive.get()
- .toString().trim()
- } else {
- ""
- },
- endpoint = if (peer.endpoint.isPresent) {
- peer.endpoint.get().toString()
- .trim()
- } else {
- ""
- },
- allowedIps = peer.allowedIps.joinToString(", ").trim()
+ preSharedKey =
+ if (peer.preSharedKey.isPresent) {
+ peer.preSharedKey.get().toBase64().trim()
+ } else {
+ ""
+ },
+ persistentKeepalive =
+ if (peer.persistentKeepalive.isPresent) {
+ peer.persistentKeepalive.get().toString().trim()
+ } else {
+ ""
+ },
+ endpoint =
+ if (peer.endpoint.isPresent) {
+ peer.endpoint.get().toString().trim()
+ } else {
+ ""
+ },
+ allowedIps = peer.allowedIps.joinToString(", ").trim(),
)
}
val IPV4_PUBLIC_NETWORKS =
setOf(
- "0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3",
- "64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12",
- "172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7",
- "176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16",
- "192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10",
- "193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4"
+ "0.0.0.0/5",
+ "8.0.0.0/7",
+ "11.0.0.0/8",
+ "12.0.0.0/6",
+ "16.0.0.0/4",
+ "32.0.0.0/3",
+ "64.0.0.0/2",
+ "128.0.0.0/3",
+ "160.0.0.0/5",
+ "168.0.0.0/6",
+ "172.0.0.0/12",
+ "172.32.0.0/11",
+ "172.64.0.0/10",
+ "172.128.0.0/9",
+ "173.0.0.0/8",
+ "174.0.0.0/7",
+ "176.0.0.0/4",
+ "192.0.0.0/9",
+ "192.128.0.0/11",
+ "192.160.0.0/13",
+ "192.169.0.0/16",
+ "192.170.0.0/15",
+ "192.172.0.0/14",
+ "192.176.0.0/12",
+ "192.192.0.0/10",
+ "193.0.0.0/8",
+ "194.0.0.0/7",
+ "196.0.0.0/6",
+ "200.0.0.0/5",
+ "208.0.0.0/4",
)
val IPV4_WILDCARD = setOf("0.0.0.0/0")
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt
index 45f3bf14..bab109c8 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt
@@ -88,7 +88,8 @@ import kotlinx.coroutines.delay
@OptIn(
ExperimentalComposeUiApi::class,
ExperimentalMaterial3Api::class,
- ExperimentalFoundationApi::class)
+ ExperimentalFoundationApi::class,
+)
@Composable
fun ConfigScreen(
viewModel: ConfigViewModel = hiltViewModel(),
@@ -97,347 +98,374 @@ fun ConfigScreen(
showSnackbarMessage: (String) -> Unit,
id: String
) {
- val context = LocalContext.current
- val clipboardManager: ClipboardManager = LocalClipboardManager.current
- val keyboardController = LocalSoftwareKeyboardController.current
- var showApplicationsDialog by remember { mutableStateOf(false) }
- var showAuthPrompt by remember { mutableStateOf(false) }
- var isAuthenticated by remember { mutableStateOf(false) }
+ val context = LocalContext.current
+ val clipboardManager: ClipboardManager = LocalClipboardManager.current
+ val keyboardController = LocalSoftwareKeyboardController.current
+ var showApplicationsDialog by remember { mutableStateOf(false) }
+ var showAuthPrompt by remember { mutableStateOf(false) }
+ var isAuthenticated by remember { mutableStateOf(false) }
- val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
- LaunchedEffect(Unit) {
- viewModel.init(id)
- }
+ LaunchedEffect(Unit) { viewModel.init(id) }
- LaunchedEffect(uiState.loading) {
- if(!uiState.loading && WireGuardAutoTunnel.isRunningOnAndroidTv()) {
- delay(Constants.FOCUS_REQUEST_DELAY)
- focusRequester.requestFocus()
- }
- }
-
- if (uiState.loading) {
- LoadingScreen()
- return
- }
-
- val keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() })
-
- val keyboardOptions =
- KeyboardOptions(imeAction = ImeAction.Done)
-
- val fillMaxHeight = .85f
- val fillMaxWidth = .85f
- val screenPadding = 5.dp
-
- val applicationButtonText = {
- "Tunneling apps: " +
- if (uiState.isAllApplicationsEnabled) {
- "all"
- } else {
- "${uiState.checkedPackageNames.size} " + (if (uiState.include) "included" else "excluded")
+ LaunchedEffect(uiState.loading) {
+ if (!uiState.loading && WireGuardAutoTunnel.isRunningOnAndroidTv()) {
+ delay(Constants.FOCUS_REQUEST_DELAY)
+ focusRequester.requestFocus()
}
- }
+ }
- if (showAuthPrompt) {
- AuthorizationPrompt(
- onSuccess = {
- showAuthPrompt = false
- isAuthenticated = true
- },
- onError = { error ->
- showAuthPrompt = false
- showSnackbarMessage(Event.Error.AuthenticationFailed.message)
- },
- onFailure = {
- showAuthPrompt = false
- showSnackbarMessage(Event.Error.AuthorizationFailed.message)
- })
- }
+ if (uiState.loading) {
+ LoadingScreen()
+ return
+ }
- if (showApplicationsDialog) {
- val sortedPackages =
- remember(uiState.packages) { uiState.packages.sortedBy { viewModel.getPackageLabel(it) } }
- AlertDialog(onDismissRequest = { showApplicationsDialog = false }) {
- Surface(
- tonalElevation = 2.dp,
- shadowElevation = 2.dp,
- shape = RoundedCornerShape(12.dp),
- color = MaterialTheme.colorScheme.surface,
- modifier =
- Modifier.fillMaxWidth()
- .fillMaxHeight(if (uiState.isAllApplicationsEnabled) 1 / 5f else 4 / 5f)) {
- Column(modifier = Modifier.fillMaxWidth()) {
- Row(
- modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp, vertical = 7.dp),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceBetween) {
- Text(stringResource(id = R.string.tunnel_all))
- Switch(
- checked = uiState.isAllApplicationsEnabled,
- onCheckedChange = { viewModel.onAllApplicationsChange(it) })
- }
- if (!uiState.isAllApplicationsEnabled) {
- Row(
- modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp, vertical = 7.dp),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceBetween) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceBetween) {
- Text(stringResource(id = R.string.include))
- Checkbox(
- checked = uiState.include,
- onCheckedChange = { viewModel.onIncludeChange(!uiState.include) })
- }
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceBetween) {
- Text(stringResource(id = R.string.exclude))
- Checkbox(
- checked = !uiState.include,
- onCheckedChange = { viewModel.onIncludeChange(!uiState.include) })
- }
+ val keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() })
+
+ val keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done)
+
+ val fillMaxHeight = .85f
+ val fillMaxWidth = .85f
+ val screenPadding = 5.dp
+
+ val applicationButtonText = {
+ "Tunneling apps: " +
+ if (uiState.isAllApplicationsEnabled) {
+ "all"
+ } else {
+ "${uiState.checkedPackageNames.size} " +
+ (if (uiState.include) "included" else "excluded")
+ }
+ }
+
+ if (showAuthPrompt) {
+ AuthorizationPrompt(
+ onSuccess = {
+ showAuthPrompt = false
+ isAuthenticated = true
+ },
+ onError = { error ->
+ showAuthPrompt = false
+ showSnackbarMessage(Event.Error.AuthenticationFailed.message)
+ },
+ onFailure = {
+ showAuthPrompt = false
+ showSnackbarMessage(Event.Error.AuthorizationFailed.message)
+ },
+ )
+ }
+
+ if (showApplicationsDialog) {
+ val sortedPackages =
+ remember(uiState.packages) {
+ uiState.packages.sortedBy { viewModel.getPackageLabel(it) }
+ }
+ AlertDialog(onDismissRequest = { showApplicationsDialog = false }) {
+ Surface(
+ tonalElevation = 2.dp,
+ shadowElevation = 2.dp,
+ shape = RoundedCornerShape(12.dp),
+ color = MaterialTheme.colorScheme.surface,
+ modifier =
+ Modifier.fillMaxWidth()
+ .fillMaxHeight(if (uiState.isAllApplicationsEnabled) 1 / 5f else 4 / 5f),
+ ) {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ Row(
+ modifier =
+ Modifier.fillMaxWidth().padding(horizontal = 20.dp, vertical = 7.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Text(stringResource(id = R.string.tunnel_all))
+ Switch(
+ checked = uiState.isAllApplicationsEnabled,
+ onCheckedChange = { viewModel.onAllApplicationsChange(it) },
+ )
}
- Row(
- modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp, vertical = 7.dp),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceBetween) {
- SearchBar(viewModel::emitQueriedPackages)
- }
- Spacer(Modifier.padding(5.dp))
- LazyColumn(
- horizontalAlignment = Alignment.Start,
- verticalArrangement = Arrangement.Top,
- modifier = Modifier.fillMaxHeight(4 / 5f)) {
- items(sortedPackages, key = { it.packageName }) { pack ->
+ if (!uiState.isAllApplicationsEnabled) {
Row(
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(horizontal = 20.dp, vertical = 7.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
- modifier = Modifier.fillMaxSize().padding(5.dp)) {
- Row(modifier = Modifier.fillMaxWidth(fillMaxWidth)) {
- val drawable =
- pack.applicationInfo?.loadIcon(context.packageManager)
- if (drawable != null) {
- Image(
- painter = DrawablePainter(drawable),
- stringResource(id = R.string.icon),
- modifier = Modifier.size(50.dp, 50.dp))
- } else {
- Icon(
- Icons.Rounded.Android,
- stringResource(id = R.string.edit),
- modifier = Modifier.size(50.dp, 50.dp))
- }
- Text(
- viewModel.getPackageLabel(pack),
- modifier = Modifier.padding(5.dp))
- }
- Checkbox(
- modifier = Modifier.fillMaxSize(),
- checked =
- (uiState.checkedPackageNames.contains(pack.packageName)),
- onCheckedChange = {
- if (it) {
- viewModel.onAddCheckedPackage(pack.packageName)
- } else {
- viewModel.onRemoveCheckedPackage(pack.packageName)
- }
- })
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Text(stringResource(id = R.string.include))
+ Checkbox(
+ checked = uiState.include,
+ onCheckedChange = {
+ viewModel.onIncludeChange(!uiState.include)
+ },
+ )
}
- }
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Text(stringResource(id = R.string.exclude))
+ Checkbox(
+ checked = !uiState.include,
+ onCheckedChange = {
+ viewModel.onIncludeChange(!uiState.include)
+ },
+ )
+ }
+ }
+ Row(
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(horizontal = 20.dp, vertical = 7.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ SearchBar(viewModel::emitQueriedPackages)
+ }
+ Spacer(Modifier.padding(5.dp))
+ LazyColumn(
+ horizontalAlignment = Alignment.Start,
+ verticalArrangement = Arrangement.Top,
+ modifier = Modifier.fillMaxHeight(4 / 5f),
+ ) {
+ items(sortedPackages, key = { it.packageName }) { pack ->
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxSize().padding(5.dp),
+ ) {
+ Row(modifier = Modifier.fillMaxWidth(fillMaxWidth)) {
+ val drawable =
+ pack.applicationInfo?.loadIcon(context.packageManager)
+ if (drawable != null) {
+ Image(
+ painter = DrawablePainter(drawable),
+ stringResource(id = R.string.icon),
+ modifier = Modifier.size(50.dp, 50.dp),
+ )
+ } else {
+ Icon(
+ Icons.Rounded.Android,
+ stringResource(id = R.string.edit),
+ modifier = Modifier.size(50.dp, 50.dp),
+ )
+ }
+ Text(
+ viewModel.getPackageLabel(pack),
+ modifier = Modifier.padding(5.dp),
+ )
+ }
+ Checkbox(
+ modifier = Modifier.fillMaxSize(),
+ checked =
+ (uiState.checkedPackageNames.contains(
+ pack.packageName
+ )),
+ onCheckedChange = {
+ if (it) {
+ viewModel.onAddCheckedPackage(pack.packageName)
+ } else {
+ viewModel.onRemoveCheckedPackage(pack.packageName)
+ }
+ },
+ )
+ }
+ }
+ }
}
- }
- Row(
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.fillMaxSize().padding(top = 5.dp),
- horizontalArrangement = Arrangement.Center) {
- TextButton(onClick = { showApplicationsDialog = false }) {
- Text(stringResource(R.string.done))
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxSize().padding(top = 5.dp),
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ TextButton(onClick = { showApplicationsDialog = false }) {
+ Text(stringResource(R.string.done))
+ }
}
- }
+ }
}
- }
+ }
}
- }
Scaffold(
floatingActionButtonPosition = FabPosition.End,
floatingActionButton = {
- val secondaryColor = MaterialTheme.colorScheme.secondary
- val hoverColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
- var fobColor by remember { mutableStateOf(secondaryColor) }
- FloatingActionButton(
- modifier =
- Modifier.padding(bottom = 90.dp).onFocusChanged {
- if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
- fobColor = if (it.isFocused) hoverColor else secondaryColor
+ val secondaryColor = MaterialTheme.colorScheme.secondary
+ val hoverColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
+ var fobColor by remember { mutableStateOf(secondaryColor) }
+ FloatingActionButton(
+ modifier =
+ Modifier.padding(bottom = 90.dp).onFocusChanged {
+ if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
+ fobColor = if (it.isFocused) hoverColor else secondaryColor
+ }
+ },
+ onClick = {
+ viewModel.onSaveAllChanges().let {
+ when (it) {
+ is Result.Success -> {
+ showSnackbarMessage(it.data.message)
+ navController.navigate(Screen.Main.route)
+ }
+ is Result.Error -> showSnackbarMessage(it.error.message)
+ }
}
- },
- onClick = {
- viewModel.onSaveAllChanges().let {
- when (it) {
- is Result.Success -> {
- showSnackbarMessage(it.data.message)
- navController.navigate(Screen.Main.route)
- }
- is Result.Error -> showSnackbarMessage(it.error.message)
- }
- }
- },
- containerColor = fobColor,
- shape = RoundedCornerShape(16.dp)) {
+ },
+ containerColor = fobColor,
+ shape = RoundedCornerShape(16.dp),
+ ) {
Icon(
imageVector = Icons.Rounded.Save,
contentDescription = stringResource(id = R.string.save_changes),
- tint = Color.DarkGray)
- }
- }) {
- Column {
+ tint = Color.DarkGray,
+ )
+ }
+ },
+ ) {
+ Column {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
modifier =
- Modifier.verticalScroll(rememberScrollState()).weight(1f, true).fillMaxSize()) {
- Surface(
- tonalElevation = 2.dp,
- shadowElevation = 2.dp,
- shape = RoundedCornerShape(12.dp),
- color = MaterialTheme.colorScheme.surface,
- modifier =
- (if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
+ Modifier.verticalScroll(rememberScrollState()).weight(1f, true).fillMaxSize(),
+ ) {
+ Surface(
+ tonalElevation = 2.dp,
+ shadowElevation = 2.dp,
+ shape = RoundedCornerShape(12.dp),
+ color = MaterialTheme.colorScheme.surface,
+ modifier =
+ (if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
Modifier.fillMaxHeight(fillMaxHeight).fillMaxWidth(fillMaxWidth)
- } else {
+ } else {
Modifier.fillMaxWidth(fillMaxWidth)
- })
- .padding(top = 50.dp, bottom = 10.dp)) {
- Column(
- horizontalAlignment = Alignment.Start,
- verticalArrangement = Arrangement.Top,
- modifier = Modifier.padding(15.dp).focusGroup()) {
- SectionTitle(
- stringResource(R.string.interface_), padding = screenPadding)
- ConfigurationTextBox(
- value = uiState.tunnelName,
- onValueChange = { value -> viewModel.onTunnelNameChange(value) },
- keyboardActions = keyboardActions,
- label = stringResource(R.string.name),
- hint = stringResource(R.string.tunnel_name).lowercase(),
- modifier =
- Modifier
- .fillMaxWidth()
- .focusRequester(focusRequester))
- OutlinedTextField(
- modifier =
- Modifier.fillMaxWidth().clickable {
- showAuthPrompt = true
- },
- value = uiState.interfaceProxy.privateKey,
- visualTransformation =
- if ((id == Constants.MANUAL_TUNNEL_CONFIG_ID) ||
- isAuthenticated)
- VisualTransformation.None
- else PasswordVisualTransformation(),
- enabled =
- (id == Constants.MANUAL_TUNNEL_CONFIG_ID) || isAuthenticated,
- onValueChange = { value -> viewModel.onPrivateKeyChange(value) },
- trailingIcon = {
- IconButton(
- modifier = Modifier.focusRequester(FocusRequester.Default),
- onClick = { viewModel.generateKeyPair() }) {
- Icon(
- Icons.Rounded.Refresh,
- stringResource(R.string.rotate_keys),
- tint = Color.White)
- }
- },
- label = { Text(stringResource(R.string.private_key)) },
- singleLine = true,
- placeholder = { Text(stringResource(R.string.base64_key)) },
- keyboardOptions = keyboardOptions,
- keyboardActions = keyboardActions)
- OutlinedTextField(
- modifier =
- Modifier
- .fillMaxWidth()
- .focusRequester(FocusRequester.Default),
- value = uiState.interfaceProxy.publicKey,
- enabled = false,
- onValueChange = {},
- trailingIcon = {
- IconButton(
- modifier = Modifier.focusRequester(FocusRequester.Default),
- onClick = {
- clipboardManager.setText(
- AnnotatedString(uiState.interfaceProxy.publicKey))
- }) {
- Icon(
- Icons.Rounded.ContentCopy,
- stringResource(R.string.copy_public_key),
- tint = Color.White)
- }
- },
- label = { Text(stringResource(R.string.public_key)) },
- singleLine = true,
- placeholder = { Text(stringResource(R.string.base64_key)) },
- keyboardOptions = keyboardOptions,
- keyboardActions = keyboardActions)
- Row(modifier = Modifier.fillMaxWidth()) {
- ConfigurationTextBox(
- value = uiState.interfaceProxy.addresses,
- onValueChange = { value ->
- viewModel.onAddressesChanged(value)
+ })
+ .padding(top = 50.dp, bottom = 10.dp),
+ ) {
+ Column(
+ horizontalAlignment = Alignment.Start,
+ verticalArrangement = Arrangement.Top,
+ modifier = Modifier.padding(15.dp).focusGroup(),
+ ) {
+ SectionTitle(
+ stringResource(R.string.interface_),
+ padding = screenPadding,
+ )
+ ConfigurationTextBox(
+ value = uiState.tunnelName,
+ onValueChange = { value -> viewModel.onTunnelNameChange(value) },
+ keyboardActions = keyboardActions,
+ label = stringResource(R.string.name),
+ hint = stringResource(R.string.tunnel_name).lowercase(),
+ modifier = Modifier.fillMaxWidth().focusRequester(focusRequester),
+ )
+ OutlinedTextField(
+ modifier = Modifier.fillMaxWidth().clickable { showAuthPrompt = true },
+ value = uiState.interfaceProxy.privateKey,
+ visualTransformation =
+ if ((id == Constants.MANUAL_TUNNEL_CONFIG_ID) || isAuthenticated)
+ VisualTransformation.None
+ else PasswordVisualTransformation(),
+ enabled = (id == Constants.MANUAL_TUNNEL_CONFIG_ID) || isAuthenticated,
+ onValueChange = { value -> viewModel.onPrivateKeyChange(value) },
+ trailingIcon = {
+ IconButton(
+ modifier = Modifier.focusRequester(FocusRequester.Default),
+ onClick = { viewModel.generateKeyPair() },
+ ) {
+ Icon(
+ Icons.Rounded.Refresh,
+ stringResource(R.string.rotate_keys),
+ tint = Color.White,
+ )
+ }
+ },
+ label = { Text(stringResource(R.string.private_key)) },
+ singleLine = true,
+ placeholder = { Text(stringResource(R.string.base64_key)) },
+ keyboardOptions = keyboardOptions,
+ keyboardActions = keyboardActions,
+ )
+ OutlinedTextField(
+ modifier =
+ Modifier.fillMaxWidth().focusRequester(FocusRequester.Default),
+ value = uiState.interfaceProxy.publicKey,
+ enabled = false,
+ onValueChange = {},
+ trailingIcon = {
+ IconButton(
+ modifier = Modifier.focusRequester(FocusRequester.Default),
+ onClick = {
+ clipboardManager.setText(
+ AnnotatedString(uiState.interfaceProxy.publicKey),
+ )
},
- keyboardActions = keyboardActions,
- label = stringResource(R.string.addresses),
- hint = stringResource(R.string.comma_separated_list),
- modifier =
- Modifier
- .fillMaxWidth(3 / 5f)
- .padding(end = 5.dp))
- ConfigurationTextBox(
- value = uiState.interfaceProxy.listenPort,
- onValueChange = { value ->
- viewModel.onListenPortChanged(value)
- },
- keyboardActions = keyboardActions,
- label = stringResource(R.string.listen_port),
- hint = stringResource(R.string.random),
- modifier = Modifier.width(IntrinsicSize.Min))
- }
- Row(modifier = Modifier.fillMaxWidth()) {
- ConfigurationTextBox(
- value = uiState.interfaceProxy.dnsServers,
- onValueChange = { value ->
- viewModel.onDnsServersChanged(value)
- },
- keyboardActions = keyboardActions,
- label = stringResource(R.string.dns_servers),
- hint = stringResource(R.string.comma_separated_list),
- modifier =
- Modifier
- .fillMaxWidth(3 / 5f)
- .padding(end = 5.dp))
- ConfigurationTextBox(
- value = uiState.interfaceProxy.mtu,
- onValueChange = { value -> viewModel.onMtuChanged(value) },
- keyboardActions = keyboardActions,
- label = stringResource(R.string.mtu),
- hint = stringResource(R.string.auto),
- modifier = Modifier.width(IntrinsicSize.Min))
- }
- Row(
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.fillMaxSize().padding(top = 5.dp),
- horizontalArrangement = Arrangement.Center) {
- TextButton(onClick = { showApplicationsDialog = true }) {
- Text(applicationButtonText())
- }
- }
+ ) {
+ Icon(
+ Icons.Rounded.ContentCopy,
+ stringResource(R.string.copy_public_key),
+ tint = Color.White,
+ )
+ }
+ },
+ label = { Text(stringResource(R.string.public_key)) },
+ singleLine = true,
+ placeholder = { Text(stringResource(R.string.base64_key)) },
+ keyboardOptions = keyboardOptions,
+ keyboardActions = keyboardActions,
+ )
+ Row(modifier = Modifier.fillMaxWidth()) {
+ ConfigurationTextBox(
+ value = uiState.interfaceProxy.addresses,
+ onValueChange = { value -> viewModel.onAddressesChanged(value) },
+ keyboardActions = keyboardActions,
+ label = stringResource(R.string.addresses),
+ hint = stringResource(R.string.comma_separated_list),
+ modifier = Modifier.fillMaxWidth(3 / 5f).padding(end = 5.dp),
+ )
+ ConfigurationTextBox(
+ value = uiState.interfaceProxy.listenPort,
+ onValueChange = { value -> viewModel.onListenPortChanged(value) },
+ keyboardActions = keyboardActions,
+ label = stringResource(R.string.listen_port),
+ hint = stringResource(R.string.random),
+ modifier = Modifier.width(IntrinsicSize.Min),
+ )
+ }
+ Row(modifier = Modifier.fillMaxWidth()) {
+ ConfigurationTextBox(
+ value = uiState.interfaceProxy.dnsServers,
+ onValueChange = { value -> viewModel.onDnsServersChanged(value) },
+ keyboardActions = keyboardActions,
+ label = stringResource(R.string.dns_servers),
+ hint = stringResource(R.string.comma_separated_list),
+ modifier = Modifier.fillMaxWidth(3 / 5f).padding(end = 5.dp),
+ )
+ ConfigurationTextBox(
+ value = uiState.interfaceProxy.mtu,
+ onValueChange = { value -> viewModel.onMtuChanged(value) },
+ keyboardActions = keyboardActions,
+ label = stringResource(R.string.mtu),
+ hint = stringResource(R.string.auto),
+ modifier = Modifier.width(IntrinsicSize.Min),
+ )
+ }
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxSize().padding(top = 5.dp),
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ TextButton(onClick = { showApplicationsDialog = true }) {
+ Text(applicationButtonText())
}
- }
- uiState.proxyPeers.forEachIndexed { index, peer ->
+ }
+ }
+ }
+ uiState.proxyPeers.forEachIndexed { index, peer ->
Surface(
tonalElevation = 2.dp,
shadowElevation = 2.dp,
@@ -445,106 +473,118 @@ fun ConfigScreen(
color = MaterialTheme.colorScheme.surface,
modifier =
(if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
- Modifier.fillMaxHeight(fillMaxHeight).fillMaxWidth(fillMaxWidth)
+ Modifier.fillMaxHeight(fillMaxHeight).fillMaxWidth(fillMaxWidth)
} else {
- Modifier.fillMaxWidth(fillMaxWidth)
+ Modifier.fillMaxWidth(fillMaxWidth)
})
- .padding(top = 10.dp, bottom = 10.dp)) {
- Column(
- horizontalAlignment = Alignment.Start,
- verticalArrangement = Arrangement.Top,
- modifier =
- Modifier.padding(horizontal = 15.dp).padding(bottom = 10.dp)) {
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.fillMaxWidth().padding(horizontal = 5.dp)) {
- SectionTitle(
- stringResource(R.string.peer), padding = screenPadding)
- IconButton(onClick = { viewModel.onDeletePeer(index) }) {
- Icon(Icons.Rounded.Delete, stringResource(R.string.delete))
- }
- }
-
- ConfigurationTextBox(
- value = peer.publicKey,
- onValueChange = { value ->
- viewModel.onPeerPublicKeyChange(index, value)
- },
- keyboardActions = keyboardActions,
- label = stringResource(R.string.public_key),
- hint = stringResource(R.string.base64_key),
- modifier = Modifier.fillMaxWidth())
- ConfigurationTextBox(
- value = peer.preSharedKey,
- onValueChange = { value ->
- viewModel.onPreSharedKeyChange(index, value)
- },
- keyboardActions = keyboardActions,
- label = stringResource(R.string.preshared_key),
- hint = stringResource(R.string.optional),
- modifier = Modifier.fillMaxWidth())
- OutlinedTextField(
- modifier = Modifier.fillMaxWidth(),
- value = peer.persistentKeepalive,
- enabled = true,
- onValueChange = { value ->
- viewModel.onPersistentKeepaliveChanged(index, value)
- },
- trailingIcon = {
- Text(
- stringResource(R.string.seconds),
- modifier = Modifier.padding(end = 10.dp))
- },
- label = { Text(stringResource(R.string.persistent_keepalive)) },
- singleLine = true,
- placeholder = {
- Text(stringResource(R.string.optional_no_recommend))
- },
- keyboardOptions = keyboardOptions,
- keyboardActions = keyboardActions)
- ConfigurationTextBox(
- value = peer.endpoint,
- onValueChange = { value ->
- viewModel.onEndpointChange(index, value)
- },
- keyboardActions = keyboardActions,
- label = stringResource(R.string.endpoint),
- hint = stringResource(R.string.endpoint).lowercase(),
- modifier = Modifier.fillMaxWidth())
- OutlinedTextField(
- modifier = Modifier.fillMaxWidth(),
- value = peer.allowedIps,
- enabled = true,
- onValueChange = { value ->
- viewModel.onAllowedIpsChange(index, value)
- },
- label = { Text(stringResource(R.string.allowed_ips)) },
- singleLine = true,
- placeholder = {
- Text(stringResource(R.string.comma_separated_list))
- },
- keyboardOptions = keyboardOptions,
- keyboardActions = keyboardActions)
- }
- }
- }
- Row(
- horizontalArrangement = Arrangement.SpaceEvenly,
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.fillMaxSize().padding(bottom = 140.dp)) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.Center) {
- TextButton(onClick = { viewModel.addEmptyPeer() }) {
- Text(stringResource(R.string.add_peer))
- }
+ .padding(top = 10.dp, bottom = 10.dp),
+ ) {
+ Column(
+ horizontalAlignment = Alignment.Start,
+ verticalArrangement = Arrangement.Top,
+ modifier = Modifier.padding(horizontal = 15.dp).padding(bottom = 10.dp),
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 5.dp),
+ ) {
+ SectionTitle(
+ stringResource(R.string.peer),
+ padding = screenPadding,
+ )
+ IconButton(onClick = { viewModel.onDeletePeer(index) }) {
+ Icon(Icons.Rounded.Delete, stringResource(R.string.delete))
+ }
}
- }
+
+ ConfigurationTextBox(
+ value = peer.publicKey,
+ onValueChange = { value ->
+ viewModel.onPeerPublicKeyChange(index, value)
+ },
+ keyboardActions = keyboardActions,
+ label = stringResource(R.string.public_key),
+ hint = stringResource(R.string.base64_key),
+ modifier = Modifier.fillMaxWidth(),
+ )
+ ConfigurationTextBox(
+ value = peer.preSharedKey,
+ onValueChange = { value ->
+ viewModel.onPreSharedKeyChange(index, value)
+ },
+ keyboardActions = keyboardActions,
+ label = stringResource(R.string.preshared_key),
+ hint = stringResource(R.string.optional),
+ modifier = Modifier.fillMaxWidth(),
+ )
+ OutlinedTextField(
+ modifier = Modifier.fillMaxWidth(),
+ value = peer.persistentKeepalive,
+ enabled = true,
+ onValueChange = { value ->
+ viewModel.onPersistentKeepaliveChanged(index, value)
+ },
+ trailingIcon = {
+ Text(
+ stringResource(R.string.seconds),
+ modifier = Modifier.padding(end = 10.dp),
+ )
+ },
+ label = { Text(stringResource(R.string.persistent_keepalive)) },
+ singleLine = true,
+ placeholder = {
+ Text(stringResource(R.string.optional_no_recommend))
+ },
+ keyboardOptions = keyboardOptions,
+ keyboardActions = keyboardActions,
+ )
+ ConfigurationTextBox(
+ value = peer.endpoint,
+ onValueChange = { value ->
+ viewModel.onEndpointChange(index, value)
+ },
+ keyboardActions = keyboardActions,
+ label = stringResource(R.string.endpoint),
+ hint = stringResource(R.string.endpoint).lowercase(),
+ modifier = Modifier.fillMaxWidth(),
+ )
+ OutlinedTextField(
+ modifier = Modifier.fillMaxWidth(),
+ value = peer.allowedIps,
+ enabled = true,
+ onValueChange = { value ->
+ viewModel.onAllowedIpsChange(index, value)
+ },
+ label = { Text(stringResource(R.string.allowed_ips)) },
+ singleLine = true,
+ placeholder = {
+ Text(stringResource(R.string.comma_separated_list))
+ },
+ keyboardOptions = keyboardOptions,
+ keyboardActions = keyboardActions,
+ )
+ }
+ }
+ }
+ Row(
+ horizontalArrangement = Arrangement.SpaceEvenly,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxSize().padding(bottom = 140.dp),
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ TextButton(onClick = { viewModel.addEmptyPeer() }) {
+ Text(stringResource(R.string.add_peer))
+ }
+ }
}
- if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
- Spacer(modifier = Modifier.weight(.17f))
}
- }
+ if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
+ Spacer(modifier = Modifier.weight(.17f))
+ }
}
+ }
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigUiState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigUiState.kt
index dbeba0a7..7abc66c1 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigUiState.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigUiState.kt
@@ -11,8 +11,8 @@ data class ConfigUiState(
val packages: Packages = emptyList(),
val checkedPackageNames: List = emptyList(),
val include: Boolean = true,
- val isAllApplicationsEnabled : Boolean = false,
+ val isAllApplicationsEnabled: Boolean = false,
val loading: Boolean = true,
val tunnel: TunnelConfig? = null,
val tunnelName: String = ""
-)
\ No newline at end of file
+)
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt
index 82a29907..f6254e4c 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt
@@ -41,272 +41,312 @@ constructor(
private val settingsRepository: SettingsRepository,
) : ViewModel() {
- private val packageManager = application.packageManager
+ private val packageManager = application.packageManager
- private val _uiState = MutableStateFlow(ConfigUiState())
- val uiState = _uiState.asStateFlow()
+ private val _uiState = MutableStateFlow(ConfigUiState())
+ val uiState = _uiState.asStateFlow()
- fun init(tunnelId : String) = viewModelScope.launch(Dispatchers.IO) {
- val packages = getQueriedPackages("")
- val state = if(tunnelId != Constants.MANUAL_TUNNEL_CONFIG_ID) {
- val tunnelConfig =
- tunnelConfigRepository.getAll().firstOrNull { it.id.toString() == tunnelId }
- if (tunnelConfig != null) {
- val config = TunnelConfig.configFromQuick(tunnelConfig.wgQuick)
- val proxyPeers = config.peers.map { PeerProxy.from(it) }
- val proxyInterface = InterfaceProxy.from(config.`interface`)
- var include = true
- var isAllApplicationsEnabled = false
- val checkedPackages =
- if (config.`interface`.includedApplications.isNotEmpty()) {
- config.`interface`.includedApplications
- } else if (config.`interface`.excludedApplications.isNotEmpty()) {
- include = false
- config.`interface`.excludedApplications
+ fun init(tunnelId: String) =
+ viewModelScope.launch(Dispatchers.IO) {
+ val packages = getQueriedPackages("")
+ val state =
+ if (tunnelId != Constants.MANUAL_TUNNEL_CONFIG_ID) {
+ val tunnelConfig =
+ tunnelConfigRepository.getAll().firstOrNull { it.id.toString() == tunnelId }
+ if (tunnelConfig != null) {
+ val config = TunnelConfig.configFromQuick(tunnelConfig.wgQuick)
+ val proxyPeers = config.peers.map { PeerProxy.from(it) }
+ val proxyInterface = InterfaceProxy.from(config.`interface`)
+ var include = true
+ var isAllApplicationsEnabled = false
+ val checkedPackages =
+ if (config.`interface`.includedApplications.isNotEmpty()) {
+ config.`interface`.includedApplications
+ } else if (config.`interface`.excludedApplications.isNotEmpty()) {
+ include = false
+ config.`interface`.excludedApplications
+ } else {
+ isAllApplicationsEnabled = true
+ emptySet()
+ }
+ ConfigUiState(
+ proxyPeers,
+ proxyInterface,
+ packages,
+ checkedPackages.toList(),
+ include,
+ isAllApplicationsEnabled,
+ false,
+ tunnelConfig,
+ tunnelConfig.name,
+ )
} else {
- isAllApplicationsEnabled = true
- emptySet()
+ ConfigUiState(loading = false, packages = packages)
}
- ConfigUiState(
- proxyPeers,
- proxyInterface,
- packages,
- checkedPackages.toList(),
- include,
- isAllApplicationsEnabled,
- false,
- tunnelConfig,
- tunnelConfig.name)
- } else {
- ConfigUiState(loading = false, packages = packages)
- }
+ } else {
+ ConfigUiState(loading = false, packages = packages)
+ }
+ _uiState.value = state
+ }
+
+ fun onTunnelNameChange(name: String) {
+ _uiState.value = _uiState.value.copy(tunnelName = name)
+ }
+
+ fun onIncludeChange(include: Boolean) {
+ _uiState.value = _uiState.value.copy(include = include)
+ }
+
+ fun onAddCheckedPackage(packageName: String) {
+ _uiState.value =
+ _uiState.value.copy(
+ checkedPackageNames = _uiState.value.checkedPackageNames + packageName
+ )
+ }
+
+ fun onAllApplicationsChange(isAllApplicationsEnabled: Boolean) {
+ _uiState.value = _uiState.value.copy(isAllApplicationsEnabled = isAllApplicationsEnabled)
+ }
+
+ fun onRemoveCheckedPackage(packageName: String) {
+ _uiState.value =
+ _uiState.value.copy(
+ checkedPackageNames = _uiState.value.checkedPackageNames - packageName
+ )
+ }
+
+ private fun getQueriedPackages(query: String): List {
+ return getAllInternetCapablePackages().filter {
+ getPackageLabel(it).lowercase().contains(query.lowercase())
+ }
+ }
+
+ fun getPackageLabel(packageInfo: PackageInfo): String {
+ return packageInfo.applicationInfo.loadLabel(application.packageManager).toString()
+ }
+
+ private fun getAllInternetCapablePackages(): List {
+ return getPackagesHoldingPermissions(arrayOf(Manifest.permission.INTERNET))
+ }
+
+ private fun getPackagesHoldingPermissions(permissions: Array): List {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ packageManager.getPackagesHoldingPermissions(
+ permissions,
+ PackageManager.PackageInfoFlags.of(0L),
+ )
} else {
- ConfigUiState(loading = false, packages = packages)
+ packageManager.getPackagesHoldingPermissions(permissions, 0)
}
- _uiState.value = state
}
- fun onTunnelNameChange(name: String) {
- _uiState.value = _uiState.value.copy(tunnelName = name)
- }
- fun onIncludeChange(include: Boolean) {
- _uiState.value = _uiState.value.copy(include = include)
- }
-
- fun onAddCheckedPackage(packageName: String) {
- _uiState.value =
- _uiState.value.copy(checkedPackageNames = _uiState.value.checkedPackageNames + packageName)
- }
-
- fun onAllApplicationsChange(isAllApplicationsEnabled: Boolean) {
- _uiState.value = _uiState.value.copy(isAllApplicationsEnabled = isAllApplicationsEnabled)
- }
-
- fun onRemoveCheckedPackage(packageName: String) {
- _uiState.value =
- _uiState.value.copy(checkedPackageNames = _uiState.value.checkedPackageNames - packageName)
- }
-
- private fun getQueriedPackages(query: String): List {
- return getAllInternetCapablePackages().filter {
- getPackageLabel(it).lowercase().contains(query.lowercase())
+ private fun isAllApplicationsEnabled(): Boolean {
+ return _uiState.value.isAllApplicationsEnabled
}
- }
- fun getPackageLabel(packageInfo: PackageInfo): String {
- return packageInfo.applicationInfo.loadLabel(application.packageManager).toString()
- }
+ private fun saveConfig(tunnelConfig: TunnelConfig) =
+ viewModelScope.launch { tunnelConfigRepository.save(tunnelConfig) }
- private fun getAllInternetCapablePackages(): List {
- return getPackagesHoldingPermissions(arrayOf(Manifest.permission.INTERNET))
- }
-
- private fun getPackagesHoldingPermissions(permissions: Array): List {
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- packageManager.getPackagesHoldingPermissions(
- permissions, PackageManager.PackageInfoFlags.of(0L))
- } else {
- packageManager.getPackagesHoldingPermissions(permissions, 0)
- }
- }
-
- private fun isAllApplicationsEnabled(): Boolean {
- return _uiState.value.isAllApplicationsEnabled
- }
-
- private fun saveConfig(tunnelConfig: TunnelConfig) =
- viewModelScope.launch {
- tunnelConfigRepository.save(tunnelConfig)
- }
-
- private fun updateTunnelConfig(tunnelConfig: TunnelConfig?) =
- viewModelScope.launch {
- if (tunnelConfig != null) {
- saveConfig(tunnelConfig).join()
- WireGuardAutoTunnel.requestTileServiceStateUpdate()
- updateSettingsDefaultTunnel(tunnelConfig)
+ private fun updateTunnelConfig(tunnelConfig: TunnelConfig?) =
+ viewModelScope.launch {
+ if (tunnelConfig != null) {
+ saveConfig(tunnelConfig).join()
+ WireGuardAutoTunnel.requestTileServiceStateUpdate()
+ updateSettingsDefaultTunnel(tunnelConfig)
+ }
}
- }
- private suspend fun updateSettingsDefaultTunnel(tunnelConfig: TunnelConfig) {
- val settings = settingsRepository.getSettingsFlow().first()
- if (settings.defaultTunnel != null) {
- if (tunnelConfig.id == TunnelConfig.from(settings.defaultTunnel!!).id) {
- settingsRepository.save(settings.copy(defaultTunnel = tunnelConfig.toString()))
- }
- }
- }
-
- private fun buildPeerListFromProxyPeers(): List {
- return _uiState.value.proxyPeers.map {
- val builder = Peer.Builder()
- if (it.allowedIps.isNotEmpty()) builder.parseAllowedIPs(it.allowedIps.trim())
- if (it.publicKey.isNotEmpty()) builder.parsePublicKey(it.publicKey.trim())
- if (it.preSharedKey.isNotEmpty()) builder.parsePreSharedKey(it.preSharedKey.trim())
- if (it.endpoint.isNotEmpty()) builder.parseEndpoint(it.endpoint.trim())
- if (it.persistentKeepalive.isNotEmpty()) {
- builder.parsePersistentKeepalive(it.persistentKeepalive.trim())
- }
- builder.build()
- }
- }
-
- private fun emptyCheckedPackagesList() {
- _uiState.value = _uiState.value.copy(checkedPackageNames = emptyList())
- }
-
- private fun buildInterfaceListFromProxyInterface(): Interface {
- val builder = Interface.Builder()
- builder.parsePrivateKey(_uiState.value.interfaceProxy.privateKey.trim())
- builder.parseAddresses(_uiState.value.interfaceProxy.addresses.trim())
- builder.parseDnsServers(_uiState.value.interfaceProxy.dnsServers.trim())
- if (_uiState.value.interfaceProxy.mtu.isNotEmpty())
- builder.parseMtu(_uiState.value.interfaceProxy.mtu.trim())
- if (_uiState.value.interfaceProxy.listenPort.isNotEmpty()) {
- builder.parseListenPort(_uiState.value.interfaceProxy.listenPort.trim())
- }
- if (isAllApplicationsEnabled()) emptyCheckedPackagesList()
- if (_uiState.value.include) builder.includeApplications(_uiState.value.checkedPackageNames)
- if (!_uiState.value.include) builder.excludeApplications(_uiState.value.checkedPackageNames)
- return builder.build()
- }
-
- fun onSaveAllChanges(): Result {
- return try {
- val peerList = buildPeerListFromProxyPeers()
- val wgInterface = buildInterfaceListFromProxyInterface()
- val config = Config.Builder().addPeers(peerList).setInterface(wgInterface).build()
- val tunnelConfig =
- _uiState.value.tunnel?.copy(
- name = _uiState.value.tunnelName, wgQuick = config.toWgQuickString())
- updateTunnelConfig(tunnelConfig)
- Result.Success(Event.Message.ConfigSaved)
- } catch (e: Exception) {
- Result.Error(Event.Error.Exception(e))
- }
- }
-
- fun onPeerPublicKeyChange(index: Int, value: String) {
- _uiState.value =
- _uiState.value.copy(
- proxyPeers =
- _uiState.value.proxyPeers.update(
- index, _uiState.value.proxyPeers[index].copy(publicKey = value)))
- }
-
- fun onPreSharedKeyChange(index: Int, value: String) {
- _uiState.value =
- _uiState.value.copy(
- proxyPeers =
- _uiState.value.proxyPeers.update(
- index, _uiState.value.proxyPeers[index].copy(preSharedKey = value)))
- }
-
- fun onEndpointChange(index: Int, value: String) {
- _uiState.value =
- _uiState.value.copy(
- proxyPeers =
- _uiState.value.proxyPeers.update(
- index, _uiState.value.proxyPeers[index].copy(endpoint = value)))
- }
-
- fun onAllowedIpsChange(index: Int, value: String) {
- _uiState.value =
- _uiState.value.copy(
- proxyPeers =
- _uiState.value.proxyPeers.update(
- index, _uiState.value.proxyPeers[index].copy(allowedIps = value)))
- }
-
- fun onPersistentKeepaliveChanged(index: Int, value: String) {
- _uiState.value =
- _uiState.value.copy(
- proxyPeers =
- _uiState.value.proxyPeers.update(
- index, _uiState.value.proxyPeers[index].copy(persistentKeepalive = value)))
- }
-
- fun onDeletePeer(index: Int) {
- _uiState.value = _uiState.value.copy(
- proxyPeers = _uiState.value.proxyPeers.removeAt(index)
- )
- }
-
- fun addEmptyPeer() {
- _uiState.value = _uiState.value.copy(proxyPeers = _uiState.value.proxyPeers + PeerProxy())
- }
-
- fun generateKeyPair() {
- val keyPair = KeyPair()
- _uiState.value =
- _uiState.value.copy(
- interfaceProxy =
- _uiState.value.interfaceProxy.copy(
- privateKey = keyPair.privateKey.toBase64(),
- publicKey = keyPair.publicKey.toBase64()))
- }
-
- fun onAddressesChanged(value: String) {
- _uiState.value =
- _uiState.value.copy(interfaceProxy = _uiState.value.interfaceProxy.copy(addresses = value))
- }
-
- fun onListenPortChanged(value: String) {
- _uiState.value =
- _uiState.value.copy(interfaceProxy = _uiState.value.interfaceProxy.copy(listenPort = value))
- }
-
- fun onDnsServersChanged(value: String) {
- _uiState.value =
- _uiState.value.copy(interfaceProxy = _uiState.value.interfaceProxy.copy(dnsServers = value))
- }
-
- fun onMtuChanged(value: String) {
- _uiState.value =
- _uiState.value.copy(interfaceProxy = _uiState.value.interfaceProxy.copy(mtu = value))
- }
-
- private fun onInterfacePublicKeyChange(value: String) {
- _uiState.value =
- _uiState.value.copy(interfaceProxy = _uiState.value.interfaceProxy.copy(publicKey = value))
- }
-
- fun onPrivateKeyChange(value: String) {
- _uiState.value =
- _uiState.value.copy(interfaceProxy = _uiState.value.interfaceProxy.copy(privateKey = value))
- if (NumberUtils.isValidKey(value)) {
- val pair = KeyPair(Key.fromBase64(value))
- onInterfacePublicKeyChange(pair.publicKey.toBase64())
- } else {
- onInterfacePublicKeyChange("")
- }
- }
-
- fun emitQueriedPackages(query: String) {
- val packages =
- getAllInternetCapablePackages().filter {
- getPackageLabel(it).lowercase().contains(query.lowercase())
+ private suspend fun updateSettingsDefaultTunnel(tunnelConfig: TunnelConfig) {
+ val settings = settingsRepository.getSettingsFlow().first()
+ if (settings.defaultTunnel != null) {
+ if (tunnelConfig.id == TunnelConfig.from(settings.defaultTunnel!!).id) {
+ settingsRepository.save(settings.copy(defaultTunnel = tunnelConfig.toString()))
+ }
}
- _uiState.value = _uiState.value.copy(packages = packages)
- }
+ }
+
+ private fun buildPeerListFromProxyPeers(): List {
+ return _uiState.value.proxyPeers.map {
+ val builder = Peer.Builder()
+ if (it.allowedIps.isNotEmpty()) builder.parseAllowedIPs(it.allowedIps.trim())
+ if (it.publicKey.isNotEmpty()) builder.parsePublicKey(it.publicKey.trim())
+ if (it.preSharedKey.isNotEmpty()) builder.parsePreSharedKey(it.preSharedKey.trim())
+ if (it.endpoint.isNotEmpty()) builder.parseEndpoint(it.endpoint.trim())
+ if (it.persistentKeepalive.isNotEmpty()) {
+ builder.parsePersistentKeepalive(it.persistentKeepalive.trim())
+ }
+ builder.build()
+ }
+ }
+
+ private fun emptyCheckedPackagesList() {
+ _uiState.value = _uiState.value.copy(checkedPackageNames = emptyList())
+ }
+
+ private fun buildInterfaceListFromProxyInterface(): Interface {
+ val builder = Interface.Builder()
+ builder.parsePrivateKey(_uiState.value.interfaceProxy.privateKey.trim())
+ builder.parseAddresses(_uiState.value.interfaceProxy.addresses.trim())
+ if (_uiState.value.interfaceProxy.dnsServers.isNotEmpty()) {
+ builder.parseDnsServers(_uiState.value.interfaceProxy.dnsServers.trim())
+ }
+ if (_uiState.value.interfaceProxy.mtu.isNotEmpty())
+ builder.parseMtu(_uiState.value.interfaceProxy.mtu.trim())
+ if (_uiState.value.interfaceProxy.listenPort.isNotEmpty()) {
+ builder.parseListenPort(_uiState.value.interfaceProxy.listenPort.trim())
+ }
+ if (isAllApplicationsEnabled()) emptyCheckedPackagesList()
+ if (_uiState.value.include) builder.includeApplications(_uiState.value.checkedPackageNames)
+ if (!_uiState.value.include) builder.excludeApplications(_uiState.value.checkedPackageNames)
+ return builder.build()
+ }
+
+ fun onSaveAllChanges(): Result {
+ return try {
+ val peerList = buildPeerListFromProxyPeers()
+ val wgInterface = buildInterfaceListFromProxyInterface()
+ val config = Config.Builder().addPeers(peerList).setInterface(wgInterface).build()
+ val tunnelConfig =
+ _uiState.value.tunnel?.copy(
+ name = _uiState.value.tunnelName,
+ wgQuick = config.toWgQuickString(),
+ )
+ updateTunnelConfig(tunnelConfig)
+ Result.Success(Event.Message.ConfigSaved)
+ } catch (e: Exception) {
+ Result.Error(Event.Error.Exception(e))
+ }
+ }
+
+ fun onPeerPublicKeyChange(index: Int, value: String) {
+ _uiState.value =
+ _uiState.value.copy(
+ proxyPeers =
+ _uiState.value.proxyPeers.update(
+ index,
+ _uiState.value.proxyPeers[index].copy(publicKey = value),
+ ),
+ )
+ }
+
+ fun onPreSharedKeyChange(index: Int, value: String) {
+ _uiState.value =
+ _uiState.value.copy(
+ proxyPeers =
+ _uiState.value.proxyPeers.update(
+ index,
+ _uiState.value.proxyPeers[index].copy(preSharedKey = value),
+ ),
+ )
+ }
+
+ fun onEndpointChange(index: Int, value: String) {
+ _uiState.value =
+ _uiState.value.copy(
+ proxyPeers =
+ _uiState.value.proxyPeers.update(
+ index,
+ _uiState.value.proxyPeers[index].copy(endpoint = value),
+ ),
+ )
+ }
+
+ fun onAllowedIpsChange(index: Int, value: String) {
+ _uiState.value =
+ _uiState.value.copy(
+ proxyPeers =
+ _uiState.value.proxyPeers.update(
+ index,
+ _uiState.value.proxyPeers[index].copy(allowedIps = value),
+ ),
+ )
+ }
+
+ fun onPersistentKeepaliveChanged(index: Int, value: String) {
+ _uiState.value =
+ _uiState.value.copy(
+ proxyPeers =
+ _uiState.value.proxyPeers.update(
+ index,
+ _uiState.value.proxyPeers[index].copy(persistentKeepalive = value),
+ ),
+ )
+ }
+
+ fun onDeletePeer(index: Int) {
+ _uiState.value =
+ _uiState.value.copy(
+ proxyPeers = _uiState.value.proxyPeers.removeAt(index),
+ )
+ }
+
+ fun addEmptyPeer() {
+ _uiState.value = _uiState.value.copy(proxyPeers = _uiState.value.proxyPeers + PeerProxy())
+ }
+
+ fun generateKeyPair() {
+ val keyPair = KeyPair()
+ _uiState.value =
+ _uiState.value.copy(
+ interfaceProxy =
+ _uiState.value.interfaceProxy.copy(
+ privateKey = keyPair.privateKey.toBase64(),
+ publicKey = keyPair.publicKey.toBase64(),
+ ),
+ )
+ }
+
+ fun onAddressesChanged(value: String) {
+ _uiState.value =
+ _uiState.value.copy(
+ interfaceProxy = _uiState.value.interfaceProxy.copy(addresses = value)
+ )
+ }
+
+ fun onListenPortChanged(value: String) {
+ _uiState.value =
+ _uiState.value.copy(
+ interfaceProxy = _uiState.value.interfaceProxy.copy(listenPort = value)
+ )
+ }
+
+ fun onDnsServersChanged(value: String) {
+ _uiState.value =
+ _uiState.value.copy(
+ interfaceProxy = _uiState.value.interfaceProxy.copy(dnsServers = value)
+ )
+ }
+
+ fun onMtuChanged(value: String) {
+ _uiState.value =
+ _uiState.value.copy(interfaceProxy = _uiState.value.interfaceProxy.copy(mtu = value))
+ }
+
+ private fun onInterfacePublicKeyChange(value: String) {
+ _uiState.value =
+ _uiState.value.copy(
+ interfaceProxy = _uiState.value.interfaceProxy.copy(publicKey = value)
+ )
+ }
+
+ fun onPrivateKeyChange(value: String) {
+ _uiState.value =
+ _uiState.value.copy(
+ interfaceProxy = _uiState.value.interfaceProxy.copy(privateKey = value)
+ )
+ if (NumberUtils.isValidKey(value)) {
+ val pair = KeyPair(Key.fromBase64(value))
+ onInterfacePublicKeyChange(pair.publicKey.toBase64())
+ } else {
+ onInterfacePublicKeyChange("")
+ }
+ }
+
+ fun emitQueriedPackages(query: String) {
+ val packages =
+ getAllInternetCapablePackages().filter {
+ getPackageLabel(it).lowercase().contains(query.lowercase())
+ }
+ _uiState.value = _uiState.value.copy(packages = packages)
+ }
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt
index dfc2ab7a..b45f2d25 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt
@@ -107,6 +107,7 @@ import com.zaneschepke.wireguardautotunnel.util.mapPeerStats
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
+import timber.log.Timber
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@@ -118,370 +119,472 @@ fun MainScreen(
showSnackbarMessage: (String) -> Unit,
navController: NavController
) {
- val haptic = LocalHapticFeedback.current
- val context = LocalContext.current
- val isVisible = rememberSaveable { mutableStateOf(true) }
- val scope = rememberCoroutineScope { Dispatchers.IO }
+ val haptic = LocalHapticFeedback.current
+ val context = LocalContext.current
+ val isVisible = rememberSaveable { mutableStateOf(true) }
+ val scope = rememberCoroutineScope { Dispatchers.IO }
- val sheetState = rememberModalBottomSheetState()
- var showBottomSheet by remember { mutableStateOf(false) }
+ val sheetState = rememberModalBottomSheetState()
+ var showBottomSheet by remember { mutableStateOf(false) }
- var showPrimaryChangeAlertDialog by remember { mutableStateOf(false) }
- var selectedTunnel by remember { mutableStateOf(null) }
- val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+ var showPrimaryChangeAlertDialog by remember { mutableStateOf(false) }
+ var showDeleteTunnelAlertDialog by remember { mutableStateOf(false) }
+ var selectedTunnel by remember { mutableStateOf(null) }
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
- LaunchedEffect(uiState.loading) {
- if (!uiState.loading && WireGuardAutoTunnel.isRunningOnAndroidTv()) {
- delay(Constants.FOCUS_REQUEST_DELAY)
- focusRequester.requestFocus()
+ LaunchedEffect(uiState.loading) {
+ if (!uiState.loading && WireGuardAutoTunnel.isRunningOnAndroidTv()) {
+ delay(Constants.FOCUS_REQUEST_DELAY)
+ focusRequester.requestFocus()
+ }
}
- }
- if (uiState.loading) {
- LoadingScreen()
- return
- }
+ if (uiState.loading) {
+ LoadingScreen()
+ return
+ }
- val tunnelFileImportResultLauncher =
- rememberLauncherForActivityResult(
- object : ActivityResultContracts.GetContent() {
- override fun createIntent(context: Context, input: String): Intent {
- val intent = super.createIntent(context, input)
+ val tunnelFileImportResultLauncher =
+ rememberLauncherForActivityResult(
+ object : ActivityResultContracts.GetContent() {
+ override fun createIntent(context: Context, input: String): Intent {
+ val intent = super.createIntent(context, input)
- /* AndroidTV now comes with stubs that do nothing but display a Toast less helpful than
- * what we can do, so detect this and throw an exception that we can catch later. */
- val activitiesToResolveIntent =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- context.packageManager.queryIntentActivities(
- intent,
- PackageManager.ResolveInfoFlags.of(
- PackageManager.MATCH_DEFAULT_ONLY.toLong()))
- } else {
- context.packageManager.queryIntentActivities(
- intent, PackageManager.MATCH_DEFAULT_ONLY)
- }
- if (activitiesToResolveIntent.all {
- val name = it.activityInfo.packageName
- name.startsWith(Constants.GOOGLE_TV_EXPLORER_STUB) ||
- name.startsWith(Constants.ANDROID_TV_EXPLORER_STUB)
- }) {
- showSnackbarMessage(Event.Error.FileExplorerRequired.message)
- }
- return intent
- }
- }) { data ->
+ /* AndroidTV now comes with stubs that do nothing but display a Toast less helpful than
+ * what we can do, so detect this and throw an exception that we can catch later. */
+ val activitiesToResolveIntent =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ context.packageManager.queryIntentActivities(
+ intent,
+ PackageManager.ResolveInfoFlags.of(
+ PackageManager.MATCH_DEFAULT_ONLY.toLong(),
+ ),
+ )
+ } else {
+ context.packageManager.queryIntentActivities(
+ intent,
+ PackageManager.MATCH_DEFAULT_ONLY,
+ )
+ }
+ if (
+ activitiesToResolveIntent.all {
+ val name = it.activityInfo.packageName
+ name.startsWith(Constants.GOOGLE_TV_EXPLORER_STUB) ||
+ name.startsWith(Constants.ANDROID_TV_EXPLORER_STUB)
+ }
+ ) {
+ showSnackbarMessage(Event.Error.FileExplorerRequired.message)
+ }
+ return intent
+ }
+ },
+ ) { data ->
if (data == null) return@rememberLauncherForActivityResult
scope.launch {
- viewModel.onTunnelFileSelected(data).let {
- when (it) {
- is Result.Error -> showSnackbarMessage(it.error.message)
- is Result.Success -> {}
+ viewModel.onTunnelFileSelected(data).let {
+ when (it) {
+ is Result.Error -> showSnackbarMessage(it.error.message)
+ is Result.Success -> {}
+ }
}
- }
}
- }
- val scanLauncher =
- rememberLauncherForActivityResult(
- contract = ScanContract(),
- onResult = {
- if (it.contents != null) {
- scope.launch {
- viewModel.onTunnelQrResult(it.contents).let { result ->
- when (result) {
- is Result.Success -> {}
- is Result.Error -> showSnackbarMessage(result.error.message)
- }
- }
- }
- }
- })
-
- AnimatedVisibility(showPrimaryChangeAlertDialog) {
- AlertDialog(
- onDismissRequest = { showPrimaryChangeAlertDialog = false },
- confirmButton = {
- TextButton(
- onClick = {
- viewModel.onDefaultTunnelChange(selectedTunnel)
- showPrimaryChangeAlertDialog = false
- selectedTunnel = null
- }) {
- Text(text = stringResource(R.string.okay))
- }
- },
- dismissButton = {
- TextButton(onClick = { showPrimaryChangeAlertDialog = false }) {
- Text(text = stringResource(R.string.cancel))
- }
- },
- title = { Text(text = stringResource(R.string.primary_tunnel_change)) },
- text = { Text(text = stringResource(R.string.primary_tunnel_change_question)) })
- }
-
- fun onTunnelToggle(checked: Boolean, tunnel: TunnelConfig) {
- if (checked) viewModel.onTunnelStart(tunnel) else viewModel.onTunnelStop()
- }
-
- Scaffold(
- modifier =
- Modifier.pointerInput(Unit) {
- detectTapGestures(
- onTap = {
- if (!WireGuardAutoTunnel.isRunningOnAndroidTv()) selectedTunnel = null
- })
- },
- floatingActionButtonPosition = FabPosition.End,
- topBar = {
- if (uiState.settings.isAutoTunnelEnabled)
- TopAppBar(
- title = {
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.requiredWidth(LocalConfiguration.current.screenWidthDp.dp).padding(end = 5.dp)) {
- Row {
- Icon(
- Icons.Rounded.Bolt,
- stringResource(id = R.string.auto),
- modifier = Modifier.size(25.dp),
- tint = if(uiState.settings.isAutoTunnelPaused) Color.Gray else mint)
- Text(
- "Auto-tunneling: ${if(uiState.settings.isAutoTunnelPaused) "paused" else "active" }",
- style = typography.bodyLarge,
- modifier = Modifier.padding(start = 10.dp))
- }
- if(uiState.settings.isAutoTunnelPaused) TextButton(
- onClick = { viewModel.resumeAutoTunneling() },
- modifier = Modifier.padding(end = 10.dp)) {
- Text("Resume")
- } else TextButton(
- onClick = { viewModel.pauseAutoTunneling() },
- modifier = Modifier.padding(end = 10.dp)) {
- Text("Pause")
- }
- }
- },
- )
- },
- floatingActionButton = {
- AnimatedVisibility(
- visible = isVisible.value,
- enter = slideInVertically(initialOffsetY = { it * 2 }),
- exit = slideOutVertically(targetOffsetY = { it * 2 })) {
- val secondaryColor = MaterialTheme.colorScheme.secondary
- val hoverColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
- var fobColor by remember { mutableStateOf(secondaryColor) }
- FloatingActionButton(
- modifier =
- (if (WireGuardAutoTunnel.isRunningOnAndroidTv() &&
- uiState.tunnels.isEmpty())
- Modifier.focusRequester(focusRequester)
- else Modifier)
- .padding(bottom = 90.dp)
- .onFocusChanged {
- if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
- fobColor = if (it.isFocused) hoverColor else secondaryColor
+ }
+ val scanLauncher =
+ rememberLauncherForActivityResult(
+ contract = ScanContract(),
+ onResult = {
+ if (it.contents != null) {
+ scope.launch {
+ viewModel.onTunnelQrResult(it.contents).let { result ->
+ when (result) {
+ is Result.Success -> {}
+ is Result.Error -> showSnackbarMessage(result.error.message)
}
- },
- onClick = { showBottomSheet = true },
- containerColor = fobColor,
- shape = RoundedCornerShape(16.dp)) {
+ }
+ }
+ }
+ },
+ )
+
+ AnimatedVisibility(showPrimaryChangeAlertDialog) {
+ AlertDialog(
+ onDismissRequest = { showPrimaryChangeAlertDialog = false },
+ confirmButton = {
+ TextButton(
+ onClick = {
+ viewModel.onDefaultTunnelChange(selectedTunnel)
+ showPrimaryChangeAlertDialog = false
+ selectedTunnel = null
+ },
+ ) {
+ Text(text = stringResource(R.string.okay))
+ }
+ },
+ dismissButton = {
+ TextButton(onClick = { showPrimaryChangeAlertDialog = false }) {
+ Text(text = stringResource(R.string.cancel))
+ }
+ },
+ title = { Text(text = stringResource(R.string.primary_tunnel_change)) },
+ text = { Text(text = stringResource(R.string.primary_tunnel_change_question)) },
+ )
+ }
+
+ AnimatedVisibility(showDeleteTunnelAlertDialog) {
+ AlertDialog(
+ onDismissRequest = { showDeleteTunnelAlertDialog = false },
+ confirmButton = {
+ TextButton(
+ onClick = {
+ selectedTunnel?.let { viewModel.onDelete(it) }
+ showDeleteTunnelAlertDialog = false
+ selectedTunnel = null
+ },
+ ) {
+ Text(text = stringResource(R.string.yes))
+ }
+ },
+ dismissButton = {
+ TextButton(onClick = { showDeleteTunnelAlertDialog = false }) {
+ Text(text = stringResource(R.string.cancel))
+ }
+ },
+ title = { Text(text = stringResource(R.string.delete_tunnel)) },
+ text = { Text(text = stringResource(R.string.delete_tunnel_message)) },
+ )
+ }
+
+ fun onTunnelToggle(checked: Boolean, tunnel: TunnelConfig) {
+ if (checked) viewModel.onTunnelStart(tunnel) else viewModel.onTunnelStop()
+ }
+
+ Scaffold(
+ modifier =
+ Modifier.pointerInput(Unit) {
+ detectTapGestures(
+ onTap = {
+ if (!WireGuardAutoTunnel.isRunningOnAndroidTv()) selectedTunnel = null
+ },
+ )
+ },
+ floatingActionButtonPosition = FabPosition.End,
+ topBar = {
+ if (uiState.settings.isAutoTunnelEnabled)
+ TopAppBar(
+ title = {
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ Modifier.requiredWidth(LocalConfiguration.current.screenWidthDp.dp)
+ .padding(end = 5.dp),
+ ) {
+ Row {
+ Icon(
+ Icons.Rounded.Bolt,
+ stringResource(id = R.string.auto),
+ modifier = Modifier.size(25.dp),
+ tint =
+ if (uiState.settings.isAutoTunnelPaused) Color.Gray
+ else mint,
+ )
+ Text(
+ "Auto-tunneling: ${if (uiState.settings.isAutoTunnelPaused) "paused" else "active"}",
+ style = typography.bodyLarge,
+ modifier = Modifier.padding(start = 10.dp),
+ )
+ }
+ if (uiState.settings.isAutoTunnelPaused)
+ TextButton(
+ onClick = { viewModel.resumeAutoTunneling() },
+ modifier = Modifier.padding(end = 10.dp),
+ ) {
+ Text("Resume")
+ }
+ else
+ TextButton(
+ onClick = { viewModel.pauseAutoTunneling() },
+ modifier = Modifier.padding(end = 10.dp),
+ ) {
+ Text("Pause")
+ }
+ }
+ },
+ )
+ },
+ floatingActionButton = {
+ AnimatedVisibility(
+ visible = isVisible.value,
+ enter = slideInVertically(initialOffsetY = { it * 2 }),
+ exit = slideOutVertically(targetOffsetY = { it * 2 }),
+ ) {
+ val secondaryColor = MaterialTheme.colorScheme.secondary
+ val hoverColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
+ var fobColor by remember { mutableStateOf(secondaryColor) }
+ FloatingActionButton(
+ modifier =
+ (if (
+ WireGuardAutoTunnel.isRunningOnAndroidTv() &&
+ uiState.tunnels.isEmpty()
+ )
+ Modifier.focusRequester(focusRequester)
+ else Modifier)
+ .padding(bottom = 90.dp)
+ .onFocusChanged {
+ if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
+ fobColor = if (it.isFocused) hoverColor else secondaryColor
+ }
+ },
+ onClick = { showBottomSheet = true },
+ containerColor = fobColor,
+ shape = RoundedCornerShape(16.dp),
+ ) {
Icon(
imageVector = Icons.Rounded.Add,
contentDescription = stringResource(id = R.string.add_tunnel),
- tint = Color.DarkGray)
- }
+ tint = Color.DarkGray,
+ )
+ }
}
- }) { innerPadding ->
+ },
+ ) { innerPadding ->
AnimatedVisibility(uiState.tunnels.isEmpty(), exit = fadeOut(), enter = fadeIn()) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center,
- modifier = Modifier.fillMaxSize().padding(padding)) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ modifier = Modifier.fillMaxSize().padding(padding),
+ ) {
Text(text = stringResource(R.string.no_tunnels), fontStyle = FontStyle.Italic)
- }
+ }
}
if (showBottomSheet) {
- ModalBottomSheet(
- onDismissRequest = { showBottomSheet = false }, sheetState = sheetState) {
+ ModalBottomSheet(
+ onDismissRequest = { showBottomSheet = false },
+ sheetState = sheetState,
+ ) {
// Sheet content
Row(
modifier =
Modifier.fillMaxWidth()
.clickable {
- showBottomSheet = false
- tunnelFileImportResultLauncher.launch(Constants.ALLOWED_FILE_TYPES)
+ showBottomSheet = false
+ tunnelFileImportResultLauncher.launch(Constants.ALLOWED_FILE_TYPES)
}
- .padding(10.dp)) {
- Icon(
- Icons.Filled.FileOpen,
- contentDescription = stringResource(id = R.string.open_file),
- modifier = Modifier.padding(10.dp))
- Text(
- stringResource(id = R.string.add_tunnels_text),
- modifier = Modifier.padding(10.dp))
- }
+ .padding(10.dp),
+ ) {
+ Icon(
+ Icons.Filled.FileOpen,
+ contentDescription = stringResource(id = R.string.open_file),
+ modifier = Modifier.padding(10.dp),
+ )
+ Text(
+ stringResource(id = R.string.add_tunnels_text),
+ modifier = Modifier.padding(10.dp),
+ )
+ }
if (!WireGuardAutoTunnel.isRunningOnAndroidTv()) {
- Divider()
- Row(
- modifier =
- Modifier.fillMaxWidth()
- .clickable {
- scope.launch {
- showBottomSheet = false
- val scanOptions = ScanOptions()
- scanOptions.setDesiredBarcodeFormats(ScanOptions.QR_CODE)
- scanOptions.setOrientationLocked(true)
- scanOptions.setPrompt(context.getString(R.string.scanning_qr))
- scanOptions.setBeepEnabled(false)
- scanOptions.captureActivity = CaptureActivityPortrait::class.java
- scanLauncher.launch(scanOptions)
+ Divider()
+ Row(
+ modifier =
+ Modifier.fillMaxWidth()
+ .clickable {
+ scope.launch {
+ showBottomSheet = false
+ val scanOptions = ScanOptions()
+ scanOptions.setDesiredBarcodeFormats(ScanOptions.QR_CODE)
+ scanOptions.setOrientationLocked(true)
+ scanOptions.setPrompt(
+ context.getString(R.string.scanning_qr)
+ )
+ scanOptions.setBeepEnabled(false)
+ scanOptions.captureActivity =
+ CaptureActivityPortrait::class.java
+ scanLauncher.launch(scanOptions)
+ }
}
- }
- .padding(10.dp)) {
+ .padding(10.dp),
+ ) {
Icon(
Icons.Filled.QrCode,
contentDescription = stringResource(id = R.string.qr_scan),
- modifier = Modifier.padding(10.dp))
+ modifier = Modifier.padding(10.dp),
+ )
Text(
stringResource(id = R.string.add_from_qr),
- modifier = Modifier.padding(10.dp))
- }
+ modifier = Modifier.padding(10.dp),
+ )
+ }
}
Divider()
Row(
modifier =
Modifier.fillMaxWidth()
.clickable {
- showBottomSheet = false
- navController.navigate(
- "${Screen.Config.route}/${Constants.MANUAL_TUNNEL_CONFIG_ID}")
+ showBottomSheet = false
+ navController.navigate(
+ "${Screen.Config.route}/${Constants.MANUAL_TUNNEL_CONFIG_ID}",
+ )
}
- .padding(10.dp)) {
- Icon(
- Icons.Filled.Create,
- contentDescription = stringResource(id = R.string.create_import),
- modifier = Modifier.padding(10.dp))
- Text(
- stringResource(id = R.string.create_import),
- modifier = Modifier.padding(10.dp))
- }
- }
+ .padding(10.dp),
+ ) {
+ Icon(
+ Icons.Filled.Create,
+ contentDescription = stringResource(id = R.string.create_import),
+ modifier = Modifier.padding(10.dp),
+ )
+ Text(
+ stringResource(id = R.string.create_import),
+ modifier = Modifier.padding(10.dp),
+ )
+ }
+ }
}
LazyColumn(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top,
modifier =
- Modifier.fillMaxWidth().fillMaxHeight(.90f).overscroll(ScrollableDefaults.overscrollEffect()).padding(innerPadding),
+ Modifier.fillMaxWidth()
+ .fillMaxHeight(.90f)
+ .overscroll(ScrollableDefaults.overscrollEffect())
+ .padding(innerPadding),
state = rememberLazyListState(0, uiState.tunnels.count()),
userScrollEnabled = true,
reverseLayout = true,
- flingBehavior = ScrollableDefaults.flingBehavior()) {
- items(uiState.tunnels,
- key = { tunnel -> tunnel.id }) { tunnel ->
- val leadingIconColor =
- (if (uiState.vpnState.name == tunnel.name &&
- uiState.vpnState.status == Tunnel.State.UP) {
- uiState.vpnState.statistics
- ?.mapPeerStats()
- ?.map { it.value?.handshakeStatus() }
- .let { statuses ->
+ flingBehavior = ScrollableDefaults.flingBehavior(),
+ ) {
+ items(
+ uiState.tunnels,
+ key = { tunnel -> tunnel.id },
+ ) { tunnel ->
+ val leadingIconColor =
+ (if (
+ uiState.vpnState.name == tunnel.name &&
+ uiState.vpnState.status == Tunnel.State.UP
+ ) {
+ uiState.vpnState.statistics
+ ?.mapPeerStats()
+ ?.map { it.value?.handshakeStatus() }
+ .let { statuses ->
when {
- statuses?.all { it == HandshakeStatus.HEALTHY } == true -> mint
- statuses?.any { it == HandshakeStatus.STALE } == true -> corn
- statuses?.all { it == HandshakeStatus.NOT_STARTED } == true ->
- Color.Gray
- else -> {
- Color.Gray
- }
+ statuses?.all { it == HandshakeStatus.HEALTHY } == true -> mint
+ statuses?.any { it == HandshakeStatus.STALE } == true -> corn
+ statuses?.all { it == HandshakeStatus.NOT_STARTED } == true ->
+ Color.Gray
+ else -> {
+ Color.Gray
+ }
}
- }
- } else {
- Color.Gray
- })
- val expanded = remember { mutableStateOf(false) }
- RowListItem(
- icon = {
- if (uiState.settings.isTunnelConfigDefault(tunnel)) {
+ }
+ } else {
+ Color.Gray
+ })
+ val expanded = remember { mutableStateOf(false) }
+ RowListItem(
+ icon = {
+ if (uiState.settings.isTunnelConfigDefault(tunnel)) {
Icon(
Icons.Rounded.Star,
stringResource(R.string.status),
tint = leadingIconColor,
- modifier = Modifier.padding(end = 10.dp).size(20.dp))
- } else {
+ modifier = Modifier.padding(end = 10.dp).size(20.dp),
+ )
+ } else {
Icon(
Icons.Rounded.Circle,
stringResource(R.string.status),
tint = leadingIconColor,
- modifier = Modifier.padding(end = 15.dp).size(15.dp))
- }
- },
- text = tunnel.name,
- onHold = {
- if ((uiState.vpnState.status == Tunnel.State.UP) &&
- (tunnel.name == uiState.vpnState.name)) {
+ modifier = Modifier.padding(end = 15.dp).size(15.dp),
+ )
+ }
+ },
+ text = tunnel.name,
+ onHold = {
+ if (
+ (uiState.vpnState.status == Tunnel.State.UP) &&
+ (tunnel.name == uiState.vpnState.name)
+ ) {
showSnackbarMessage(Event.Message.TunnelOffAction.message)
return@RowListItem
- }
- haptic.performHapticFeedback(HapticFeedbackType.LongPress)
- selectedTunnel = tunnel
- },
- onClick = {
- if (!WireGuardAutoTunnel.isRunningOnAndroidTv()) {
- if (uiState.vpnState.status == Tunnel.State.UP &&
- (uiState.vpnState.name == tunnel.name)) {
- expanded.value = !expanded.value
+ }
+ haptic.performHapticFeedback(HapticFeedbackType.LongPress)
+ selectedTunnel = tunnel
+ },
+ onClick = {
+ if (!WireGuardAutoTunnel.isRunningOnAndroidTv()) {
+ if (
+ uiState.vpnState.status == Tunnel.State.UP &&
+ (uiState.vpnState.name == tunnel.name)
+ ) {
+ expanded.value = !expanded.value
}
- } else {
+ } else {
selectedTunnel = tunnel
focusRequester.requestFocus()
- }
- },
- statistics = uiState.vpnState.statistics,
- expanded = expanded.value,
- rowButton = {
- if (tunnel.id == selectedTunnel?.id &&
- !WireGuardAutoTunnel.isRunningOnAndroidTv()) {
+ }
+ },
+ statistics = uiState.vpnState.statistics,
+ expanded = expanded.value,
+ rowButton = {
+ if (
+ tunnel.id == selectedTunnel?.id &&
+ !WireGuardAutoTunnel.isRunningOnAndroidTv()
+ ) {
Row {
- if (!uiState.settings.isTunnelConfigDefault(tunnel)) {
+ if (!uiState.settings.isTunnelConfigDefault(tunnel)) {
+ IconButton(
+ onClick = {
+ if (
+ uiState.settings.isAutoTunnelEnabled &&
+ !uiState.settings.isAutoTunnelPaused
+ ) {
+ showSnackbarMessage(
+ Event.Message.AutoTunnelOffAction.message,
+ )
+ } else {
+ showPrimaryChangeAlertDialog = true
+ }
+ },
+ ) {
+ Icon(
+ Icons.Rounded.Star,
+ stringResource(id = R.string.set_primary),
+ )
+ }
+ }
IconButton(
onClick = {
- if (uiState.settings.isAutoTunnelEnabled && !uiState.settings.isAutoTunnelPaused) {
- showSnackbarMessage(
- Event.Message.AutoTunnelOffAction.message)
- } else {
- showPrimaryChangeAlertDialog = true
- }
- }) {
- Icon(
- Icons.Rounded.Star,
- stringResource(id = R.string.set_primary))
- }
- }
- IconButton(
- onClick = {
- if (uiState.settings.isAutoTunnelEnabled && uiState.settings.isTunnelConfigDefault(tunnel)
- && !uiState.settings.isAutoTunnelPaused) {
- showSnackbarMessage(
- Event.Message.AutoTunnelOffAction.message)
- } else navController.navigate(
- "${Screen.Config.route}/${selectedTunnel?.id}")
- }) {
+ if (
+ uiState.settings.isAutoTunnelEnabled &&
+ uiState.settings.isTunnelConfigDefault(
+ tunnel,
+ ) &&
+ !uiState.settings.isAutoTunnelPaused
+ ) {
+ showSnackbarMessage(
+ Event.Message.AutoTunnelOffAction.message,
+ )
+ } else
+ navController.navigate(
+ "${Screen.Config.route}/${selectedTunnel?.id}",
+ )
+ },
+ ) {
Icon(Icons.Rounded.Edit, stringResource(id = R.string.edit))
- }
- IconButton(
- modifier = Modifier.focusable(),
- onClick = { viewModel.onDelete(tunnel) }) {
+ }
+ IconButton(
+ modifier = Modifier.focusable(),
+ onClick = { showDeleteTunnelAlertDialog = true },
+ ) {
Icon(Icons.Rounded.Delete, stringResource(id = R.string.delete))
- }
+ }
}
- } else {
+ } else {
val checked by remember {
- derivedStateOf {
- (uiState.vpnState.status == Tunnel.State.UP &&
- tunnel.name == uiState.vpnState.name)
- }
+ derivedStateOf {
+ (uiState.vpnState.status == Tunnel.State.UP &&
+ tunnel.name == uiState.vpnState.name)
+ }
}
if (!checked) expanded.value = false
@@ -491,72 +594,94 @@ fun MainScreen(
modifier = Modifier.focusRequester(focusRequester),
checked = checked,
onCheckedChange = { checked ->
- if (!checked) expanded.value = false
- onTunnelToggle(checked, tunnel)
- })
+ if (!checked) expanded.value = false
+ onTunnelToggle(checked, tunnel)
+ },
+ )
if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
- Row {
- if (!uiState.settings.isTunnelConfigDefault(tunnel)) {
- IconButton(
- onClick = {
- if (uiState.settings.isAutoTunnelEnabled) {
- showSnackbarMessage(
- Event.Message.AutoTunnelOffAction.message)
- } else {
- selectedTunnel = tunnel
- showPrimaryChangeAlertDialog = true
+ Row {
+ if (!uiState.settings.isTunnelConfigDefault(tunnel)) {
+ IconButton(
+ onClick = {
+ if (uiState.settings.isAutoTunnelEnabled) {
+ showSnackbarMessage(
+ Event.Message.AutoTunnelOffAction.message,
+ )
+ } else {
+ selectedTunnel = tunnel
+ showPrimaryChangeAlertDialog = true
+ }
+ },
+ ) {
+ Icon(
+ Icons.Rounded.Star,
+ stringResource(id = R.string.set_primary),
+ )
}
- }) {
+ }
+ IconButton(
+ modifier = Modifier.focusRequester(focusRequester),
+ onClick = {
+ if (
+ uiState.vpnState.status == Tunnel.State.UP &&
+ (uiState.vpnState.name == tunnel.name)
+ ) {
+ expanded.value = !expanded.value
+ } else {
+ showSnackbarMessage(
+ Event.Message.TunnelOnAction.message
+ )
+ }
+ },
+ ) {
+ Icon(Icons.Rounded.Info, stringResource(R.string.info))
+ }
+ IconButton(
+ onClick = {
+ if (
+ uiState.vpnState.status == Tunnel.State.UP &&
+ tunnel.name == uiState.vpnState.name
+ ) {
+ showSnackbarMessage(
+ Event.Message.TunnelOffAction.message
+ )
+ } else {
+ navController.navigate(
+ "${Screen.Config.route}/${tunnel.id}",
+ )
+ }
+ },
+ ) {
+ Icon(Icons.Rounded.Edit, stringResource(id = R.string.edit))
+ }
+ IconButton(
+ onClick = {
+ if (
+ uiState.vpnState.status == Tunnel.State.UP &&
+ tunnel.name == uiState.vpnState.name
+ ) {
+ showSnackbarMessage(
+ Event.Message.TunnelOffAction.message
+ )
+ } else {
+ showDeleteTunnelAlertDialog = true
+ }
+ },
+ ) {
Icon(
- Icons.Rounded.Star,
- stringResource(id = R.string.set_primary))
- }
+ Icons.Rounded.Delete,
+ stringResource(id = R.string.delete),
+ )
+ }
+ TunnelSwitch()
}
- IconButton(
- modifier = Modifier.focusRequester(focusRequester),
- onClick = {
- if (uiState.vpnState.status == Tunnel.State.UP &&
- (uiState.vpnState.name == tunnel.name)) {
- expanded.value = !expanded.value
- } else {
- showSnackbarMessage(Event.Message.TunnelOnAction.message)
- }
- }) {
- Icon(Icons.Rounded.Info, stringResource(R.string.info))
- }
- IconButton(
- onClick = {
- if (uiState.vpnState.status == Tunnel.State.UP &&
- tunnel.name == uiState.vpnState.name) {
- showSnackbarMessage(Event.Message.TunnelOffAction.message)
- } else {
- navController.navigate(
- "${Screen.Config.route}/${tunnel.id}")
- }
- }) {
- Icon(Icons.Rounded.Edit, stringResource(id = R.string.edit))
- }
- IconButton(
- onClick = {
- if (uiState.vpnState.status == Tunnel.State.UP &&
- tunnel.name == uiState.vpnState.name) {
- showSnackbarMessage(Event.Message.TunnelOffAction.message)
- } else {
- viewModel.onDelete(tunnel)
- }
- }) {
- Icon(
- Icons.Rounded.Delete,
- stringResource(id = R.string.delete))
- }
- TunnelSwitch()
- }
} else {
- TunnelSwitch()
+ TunnelSwitch()
}
- }
- })
- }
+ }
+ },
+ )
}
- }
+ }
+ }
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainUiState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainUiState.kt
index 3246a252..775bbba0 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainUiState.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainUiState.kt
@@ -5,8 +5,8 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState
import com.zaneschepke.wireguardautotunnel.util.TunnelConfigs
data class MainUiState(
- val settings : Settings = Settings(),
- val tunnels : TunnelConfigs = emptyList(),
+ val settings: Settings = Settings(),
+ val tunnels: TunnelConfigs = emptyList(),
val vpnState: VpnState = VpnState(),
- val loading : Boolean = true
+ val loading: Boolean = true
)
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt
index 9965d120..5008ccd4 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt
@@ -14,11 +14,7 @@ import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
-import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceState
-import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardConnectivityWatcherService
-import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelService
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
-import com.zaneschepke.wireguardautotunnel.service.tunnel.WireGuardTunnel
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.Event
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
@@ -32,6 +28,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import timber.log.Timber
import java.io.InputStream
import java.util.zip.ZipInputStream
import javax.inject.Inject
@@ -46,216 +43,225 @@ constructor(
private val vpnService: VpnService
) : ViewModel() {
- val uiState =
- combine(
- settingsRepository.getSettingsFlow(),
- tunnelConfigRepository.getTunnelConfigsFlow(),
- vpnService.vpnState,
- ) { settings, tunnels, vpnState ->
- validateWatcherServiceState(settings)
- MainUiState(settings, tunnels, vpnState, false)
- }
- .stateIn(
- viewModelScope,
- SharingStarted.WhileSubscribed(Constants.SUBSCRIPTION_TIMEOUT),
- MainUiState())
+ val uiState =
+ combine(
+ settingsRepository.getSettingsFlow(),
+ tunnelConfigRepository.getTunnelConfigsFlow(),
+ vpnService.vpnState,
+ ) { settings, tunnels, vpnState ->
+ validateWatcherServiceState(settings)
+ MainUiState(settings, tunnels, vpnState, false)
+ }
+ .stateIn(
+ viewModelScope,
+ SharingStarted.WhileSubscribed(Constants.SUBSCRIPTION_TIMEOUT),
+ MainUiState(),
+ )
- private fun validateWatcherServiceState(settings: Settings) = viewModelScope.launch(Dispatchers.IO) {
- val watcherState =
- ServiceManager.getServiceState(
- application.applicationContext, WireGuardConnectivityWatcherService::class.java)
- if (settings.isAutoTunnelEnabled &&
- watcherState == ServiceState.STOPPED) {
- ServiceManager.startWatcherService(application.applicationContext)
- }
- }
-
- private fun stopWatcherService() = viewModelScope.launch(Dispatchers.IO) {
- ServiceManager.stopWatcherService(application.applicationContext)
- }
- fun onDelete(tunnel: TunnelConfig) {
- viewModelScope.launch(Dispatchers.IO) {
- if (tunnelConfigRepository.count() == 1) {
- stopWatcherService()
- val settings = settingsRepository.getSettings()
- settings.defaultTunnel = null
- settings.isAutoTunnelEnabled = false
- settings.isAlwaysOnVpnEnabled = false
- saveSettings(settings)
- }
- tunnelConfigRepository.delete(tunnel)
- WireGuardAutoTunnel.requestTileServiceStateUpdate()
- }
- }
-
- fun onTunnelStart(tunnelConfig: TunnelConfig) = viewModelScope.launch(Dispatchers.IO) {
- stopActiveTunnel().await()
- startTunnel(tunnelConfig)
- }
-
- private fun startTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch(Dispatchers.IO) {
- ServiceManager.startVpnService(application.applicationContext, tunnelConfig.toString())
- }
-
- private fun stopActiveTunnel() =
- viewModelScope.async(Dispatchers.IO) {
- if (ServiceManager.getServiceState(
- application.applicationContext, WireGuardTunnelService::class.java) ==
- ServiceState.STARTED) {
- onTunnelStop()
- delay(Constants.TOGGLE_TUNNEL_DELAY)
+ private fun validateWatcherServiceState(settings: Settings) =
+ viewModelScope.launch(Dispatchers.IO) {
+ if (settings.isAutoTunnelEnabled) {
+ ServiceManager.startWatcherService(application.applicationContext)
+ }
}
- }
- fun onTunnelStop() = viewModelScope.launch(Dispatchers.IO) {
- ServiceManager.stopVpnService(application.applicationContext)
- }
+ private fun stopWatcherService() =
+ viewModelScope.launch(Dispatchers.IO) {
+ ServiceManager.stopWatcherService(application.applicationContext)
+ }
- private fun validateConfigString(config: String) {
- TunnelConfig.configFromQuick(config)
- }
+ fun onDelete(tunnel: TunnelConfig) {
+ viewModelScope.launch(Dispatchers.IO) {
+ if (tunnelConfigRepository.count() == 1) {
+ stopWatcherService()
+ val settings = settingsRepository.getSettings()
+ settings.defaultTunnel = null
+ settings.isAutoTunnelEnabled = false
+ settings.isAlwaysOnVpnEnabled = false
+ saveSettings(settings)
+ }
+ tunnelConfigRepository.delete(tunnel)
+ WireGuardAutoTunnel.requestTileServiceStateUpdate()
+ }
+ }
- suspend fun onTunnelQrResult(result: String) : Result {
+ fun onTunnelStart(tunnelConfig: TunnelConfig) =
+ viewModelScope.launch(Dispatchers.IO) {
+ Timber.d("On start called!")
+ stopActiveTunnel().await()
+ startTunnel(tunnelConfig)
+ }
+
+ private fun startTunnel(tunnelConfig: TunnelConfig) =
+ viewModelScope.launch(Dispatchers.IO) {
+ Timber.d("Start tunnel via manager")
+ ServiceManager.startVpnService(application.applicationContext, tunnelConfig.toString())
+ }
+
+ private fun stopActiveTunnel() =
+ viewModelScope.async(Dispatchers.IO) {
+ onTunnelStop()
+ delay(Constants.TOGGLE_TUNNEL_DELAY)
+ }
+
+ fun onTunnelStop() =
+ viewModelScope.launch(Dispatchers.IO) {
+ Timber.d("Stopping active tunnel")
+ ServiceManager.stopVpnService(application.applicationContext)
+ }
+
+ private fun validateConfigString(config: String) {
+ TunnelConfig.configFromQuick(config)
+ }
+
+ suspend fun onTunnelQrResult(result: String): Result {
return try {
- validateConfigString(result)
- val tunnelConfig =
- TunnelConfig(name = NumberUtils.generateRandomTunnelName(), wgQuick = result)
- addTunnel(tunnelConfig)
+ validateConfigString(result)
+ val tunnelConfig =
+ TunnelConfig(name = NumberUtils.generateRandomTunnelName(), wgQuick = result)
+ addTunnel(tunnelConfig)
Result.Success(Unit)
} catch (e: Exception) {
Result.Error(Event.Error.InvalidQrCode)
}
- }
+ }
- private suspend fun saveTunnelConfigFromStream(stream: InputStream, fileName: String) {
- val bufferReader = stream.bufferedReader(charset = Charsets.UTF_8)
- val config = Config.parse(bufferReader)
- val tunnelName = getNameFromFileName(fileName)
- addTunnel(TunnelConfig(name = tunnelName, wgQuick = config.toWgQuickString()))
- withContext(Dispatchers.IO) { stream.close() }
- }
+ private suspend fun saveTunnelConfigFromStream(stream: InputStream, fileName: String) {
+ val bufferReader = stream.bufferedReader(charset = Charsets.UTF_8)
+ val config = Config.parse(bufferReader)
+ val tunnelName = getNameFromFileName(fileName)
+ addTunnel(TunnelConfig(name = tunnelName, wgQuick = config.toWgQuickString()))
+ withContext(Dispatchers.IO) { stream.close() }
+ }
- private fun getInputStreamFromUri(uri: Uri): InputStream? {
- return application.applicationContext.contentResolver.openInputStream(uri)
- }
+ private fun getInputStreamFromUri(uri: Uri): InputStream? {
+ return application.applicationContext.contentResolver.openInputStream(uri)
+ }
- suspend fun onTunnelFileSelected(uri: Uri) : Result {
+ suspend fun onTunnelFileSelected(uri: Uri): Result {
try {
- if(isValidUriContentScheme(uri)){
+ if (isValidUriContentScheme(uri)) {
val fileName = getFileName(application.applicationContext, uri)
when (getFileExtensionFromFileName(fileName)) {
- Constants.CONF_FILE_EXTENSION -> saveTunnelFromConfUri(fileName, uri).let {
- when(it) {
- is Result.Error -> return Result.Error(Event.Error.FileReadFailed)
- is Result.Success -> return it
+ Constants.CONF_FILE_EXTENSION ->
+ saveTunnelFromConfUri(fileName, uri).let {
+ when (it) {
+ is Result.Error -> return Result.Error(Event.Error.FileReadFailed)
+ is Result.Success -> return it
+ }
}
- }
Constants.ZIP_FILE_EXTENSION -> saveTunnelsFromZipUri(uri)
else -> return Result.Error(Event.Error.InvalidFileExtension)
}
return Result.Success(Unit)
- } else {
- return Result.Error(Event.Error.InvalidFileExtension)
+ } else {
+ return Result.Error(Event.Error.InvalidFileExtension)
}
} catch (e: Exception) {
return Result.Error(Event.Error.FileReadFailed)
}
- }
-
- private suspend fun saveTunnelsFromZipUri(uri: Uri) {
- ZipInputStream(getInputStreamFromUri(uri)).use { zip ->
- generateSequence { zip.nextEntry }
- .filterNot {
- it.isDirectory || getFileExtensionFromFileName(it.name) != Constants.CONF_FILE_EXTENSION
- }
- .forEach {
- val name = getNameFromFileName(it.name)
- val config = Config.parse(zip)
- viewModelScope.launch(Dispatchers.IO) {
- addTunnel(TunnelConfig(name = name, wgQuick = config.toWgQuickString()))
- }
- }
- }
- }
-
- private suspend fun saveTunnelFromConfUri(name: String, uri: Uri) : Result {
- val stream = getInputStreamFromUri(uri)
- return if(stream != null) {
- saveTunnelConfigFromStream(stream, name)
- Result.Success(Unit)
- } else {
- Result.Error(Event.Error.FileReadFailed)
- }
- }
-
- private suspend fun addTunnel(tunnelConfig: TunnelConfig) {
- saveTunnel(tunnelConfig)
- WireGuardAutoTunnel.requestTileServiceStateUpdate()
- }
-
- fun pauseAutoTunneling() = viewModelScope.launch {
- settingsRepository.save(uiState.value.settings.copy(isAutoTunnelPaused = true))
}
- fun resumeAutoTunneling() = viewModelScope.launch {
- settingsRepository.save(uiState.value.settings.copy(isAutoTunnelPaused = false))
+ private suspend fun saveTunnelsFromZipUri(uri: Uri) {
+ ZipInputStream(getInputStreamFromUri(uri)).use { zip ->
+ generateSequence { zip.nextEntry }
+ .filterNot {
+ it.isDirectory ||
+ getFileExtensionFromFileName(it.name) != Constants.CONF_FILE_EXTENSION
+ }
+ .forEach {
+ val name = getNameFromFileName(it.name)
+ val config = Config.parse(zip)
+ viewModelScope.launch(Dispatchers.IO) {
+ addTunnel(TunnelConfig(name = name, wgQuick = config.toWgQuickString()))
+ }
+ }
+ }
}
- private suspend fun saveTunnel(tunnelConfig: TunnelConfig) {
- tunnelConfigRepository.save(tunnelConfig)
- }
-
- private fun getFileNameByCursor(context: Context, uri: Uri): String? {
- context.contentResolver.query(uri, null, null, null, null)?.use {
- return getDisplayNameByCursor(it)
+ private suspend fun saveTunnelFromConfUri(name: String, uri: Uri): Result {
+ val stream = getInputStreamFromUri(uri)
+ return if (stream != null) {
+ saveTunnelConfigFromStream(stream, name)
+ Result.Success(Unit)
+ } else {
+ Result.Error(Event.Error.FileReadFailed)
+ }
}
- return null
- }
- private fun getDisplayNameColumnIndex(cursor: Cursor): Int? {
- val columnIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
- return if (columnIndex != -1) {
- return columnIndex
- } else {
- null
+ private suspend fun addTunnel(tunnelConfig: TunnelConfig) {
+ saveTunnel(tunnelConfig)
+ WireGuardAutoTunnel.requestTileServiceStateUpdate()
}
- }
- private fun getDisplayNameByCursor(cursor: Cursor): String? {
- return if (cursor.moveToFirst()) {
- val index = getDisplayNameColumnIndex(cursor)
- if (index != null) {
- cursor.getString(index)
- } else null
- } else null
- }
+ fun pauseAutoTunneling() =
+ viewModelScope.launch {
+ settingsRepository.save(uiState.value.settings.copy(isAutoTunnelPaused = true))
+ }
- private fun isValidUriContentScheme(uri: Uri): Boolean {
- return uri.scheme == Constants.URI_CONTENT_SCHEME
- }
- private fun getFileName(context: Context, uri: Uri): String {
+ fun resumeAutoTunneling() =
+ viewModelScope.launch {
+ settingsRepository.save(uiState.value.settings.copy(isAutoTunnelPaused = false))
+ }
+
+ private suspend fun saveTunnel(tunnelConfig: TunnelConfig) {
+ tunnelConfigRepository.save(tunnelConfig)
+ }
+
+ private fun getFileNameByCursor(context: Context, uri: Uri): String? {
+ context.contentResolver.query(uri, null, null, null, null)?.use {
+ return getDisplayNameByCursor(it)
+ }
+ return null
+ }
+
+ private fun getDisplayNameColumnIndex(cursor: Cursor): Int? {
+ val columnIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+ return if (columnIndex != -1) {
+ return columnIndex
+ } else {
+ null
+ }
+ }
+
+ private fun getDisplayNameByCursor(cursor: Cursor): String? {
+ return if (cursor.moveToFirst()) {
+ val index = getDisplayNameColumnIndex(cursor)
+ if (index != null) {
+ cursor.getString(index)
+ } else null
+ } else null
+ }
+
+ private fun isValidUriContentScheme(uri: Uri): Boolean {
+ return uri.scheme == Constants.URI_CONTENT_SCHEME
+ }
+
+ private fun getFileName(context: Context, uri: Uri): String {
return getFileNameByCursor(context, uri) ?: NumberUtils.generateRandomTunnelName()
- }
-
- private fun getNameFromFileName(fileName: String): String {
- return fileName.substring(0, fileName.lastIndexOf('.'))
- }
-
- private fun getFileExtensionFromFileName(fileName: String): String {
- return try {
- fileName.substring(fileName.lastIndexOf('.'))
- } catch (e: Exception) {
- ""
}
- }
- private fun saveSettings(settings: Settings) =
- viewModelScope.launch(Dispatchers.IO) { settingsRepository.save(settings) }
-
- fun onDefaultTunnelChange(selectedTunnel: TunnelConfig?) = viewModelScope.launch {
- if (selectedTunnel != null) {
- saveSettings(uiState.value.settings.copy(defaultTunnel = selectedTunnel.toString())).join()
- WireGuardAutoTunnel.requestTileServiceStateUpdate()
+ private fun getNameFromFileName(fileName: String): String {
+ return fileName.substring(0, fileName.lastIndexOf('.'))
}
- }
+
+ private fun getFileExtensionFromFileName(fileName: String): String {
+ return try {
+ fileName.substring(fileName.lastIndexOf('.'))
+ } catch (e: Exception) {
+ ""
+ }
+ }
+
+ private fun saveSettings(settings: Settings) =
+ viewModelScope.launch(Dispatchers.IO) { settingsRepository.save(settings) }
+
+ fun onDefaultTunnelChange(selectedTunnel: TunnelConfig?) =
+ viewModelScope.launch {
+ if (selectedTunnel != null) {
+ saveSettings(uiState.value.settings.copy(defaultTunnel = selectedTunnel.toString()))
+ .join()
+ WireGuardAutoTunnel.requestTileServiceStateUpdate()
+ }
+ }
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt
index f5d053dd..6ad73939 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt
@@ -1,10 +1,17 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.settings
import android.Manifest
+import android.app.Activity
+import android.content.Context.POWER_SERVICE
import android.content.Intent
import android.net.Uri
import android.os.Build
+import android.os.PowerManager
+import android.provider.Settings
import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -83,7 +90,8 @@ import java.io.File
@OptIn(
ExperimentalPermissionsApi::class,
- ExperimentalLayoutApi::class)
+ ExperimentalLayoutApi::class,
+)
@Composable
fun SettingsScreen(
viewModel: SettingsViewModel = hiltViewModel(),
@@ -91,92 +99,127 @@ fun SettingsScreen(
showSnackbarMessage: (String) -> Unit,
focusRequester: FocusRequester
) {
- val scope = rememberCoroutineScope { Dispatchers.IO }
- val context = LocalContext.current
- val focusManager = LocalFocusManager.current
- val scrollState = rememberScrollState()
- val interactionSource = remember { MutableInteractionSource() }
+ val scope = rememberCoroutineScope { Dispatchers.IO }
+ val context = LocalContext.current
+ val focusManager = LocalFocusManager.current
+ val scrollState = rememberScrollState()
+ val interactionSource = remember { MutableInteractionSource() }
- val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
- val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
- var currentText by remember { mutableStateOf("") }
- var isBackgroundLocationGranted by remember { mutableStateOf(true) }
+ val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
+ var currentText by remember { mutableStateOf("") }
+ var isBackgroundLocationGranted by remember { mutableStateOf(true) }
var showLocationServicesAlertDialog by remember { mutableStateOf(false) }
- var didExportFiles by remember { mutableStateOf(false) }
- var showAuthPrompt by remember { mutableStateOf(false) }
+ var didExportFiles by remember { mutableStateOf(false) }
+ var showAuthPrompt by remember { mutableStateOf(false) }
val focusRequester2 = remember { FocusRequester() }
- val screenPadding = 5.dp
- val fillMaxWidth = .85f
+ val screenPadding = 5.dp
+ val fillMaxWidth = .85f
- if (uiState.loading) {
- LoadingScreen()
- return
- }
-
- fun exportAllConfigs() {
- try {
- val files = uiState.tunnels.map { File(context.cacheDir, "${it.name}.conf") }
- files.forEachIndexed { index, file ->
- file.outputStream().use { it.write(uiState.tunnels[index].wgQuick.toByteArray()) }
- }
- FileUtils.saveFilesToZip(context, files)
- didExportFiles = true
- showSnackbarMessage(Event.Message.ConfigsExported.message)
- } catch (e: Exception) {
- showSnackbarMessage(Event.Error.Exception(e).message)
+ if (uiState.loading) {
+ LoadingScreen()
+ return
}
- }
- fun saveTrustedSSID() {
- if (currentText.isNotEmpty()) {
- viewModel.onSaveTrustedSSID(currentText).let {
- when(it) {
- is Result.Success -> currentText = ""
- is Result.Error -> showSnackbarMessage(it.error.message)
- }
- }
- }
- }
-
- fun openSettings() {
- scope.launch {
- val intentSettings = Intent(ACTION_APPLICATION_DETAILS_SETTINGS)
- intentSettings.data = Uri.fromParts("package", context.packageName, null)
- context.startActivity(intentSettings)
- }
- }
-
- fun checkFineLocationGranted() {
- isBackgroundLocationGranted =
- if (!fineLocationState.status.isGranted) {
- false
- } else {
- viewModel.setLocationDisclosureShown()
- true
+ val startForResult =
+ rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ result: ActivityResult ->
+ if (result.resultCode == Activity.RESULT_OK) {
+ val intent = result.data
+ // Handle the Intent
+ }
+ viewModel.setBatteryOptimizeDisableShown()
}
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- if(WireGuardAutoTunnel.isRunningOnAndroidTv() && 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
- }
- }
- }
+ fun exportAllConfigs() {
+ try {
+ val files = uiState.tunnels.map { File(context.cacheDir, "${it.name}.conf") }
+ files.forEachIndexed { index, file ->
+ file.outputStream().use { it.write(uiState.tunnels[index].wgQuick.toByteArray()) }
+ }
+ FileUtils.saveFilesToZip(context, files)
+ didExportFiles = true
+ showSnackbarMessage(Event.Message.ConfigsExported.message)
+ } catch (e: Exception) {
+ showSnackbarMessage(Event.Error.Exception(e).message)
+ }
+ }
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
- checkFineLocationGranted()
- }
+ fun isBatteryOptimizationsDisabled(): Boolean {
+ val pm = context.getSystemService(POWER_SERVICE) as PowerManager
+ return pm.isIgnoringBatteryOptimizations(context.packageName)
+ }
+
+ fun requestBatteryOptimizationsDisabled() {
+ val intent =
+ Intent().apply {
+ this.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
+ data = Uri.fromParts("package", context.packageName, null)
+ }
+ startForResult.launch(intent)
+ }
+
+ fun handleAutoTunnelToggle() {
+ if (uiState.isBatteryOptimizeDisableShown || isBatteryOptimizationsDisabled()) {
+ viewModel.toggleAutoTunnel()
+ } else {
+ requestBatteryOptimizationsDisabled()
+ }
+ }
+
+ fun saveTrustedSSID() {
+ if (currentText.isNotEmpty()) {
+ viewModel.onSaveTrustedSSID(currentText).let {
+ when (it) {
+ is Result.Success -> currentText = ""
+ is Result.Error -> showSnackbarMessage(it.error.message)
+ }
+ }
+ }
+ }
+
+ fun openSettings() {
+ scope.launch {
+ val intentSettings = Intent(ACTION_APPLICATION_DETAILS_SETTINGS)
+ intentSettings.data = Uri.fromParts("package", context.packageName, null)
+ context.startActivity(intentSettings)
+ }
+ }
+
+ fun checkFineLocationGranted() {
+ isBackgroundLocationGranted =
+ if (!fineLocationState.status.isGranted) {
+ false
+ } else {
+ viewModel.setLocationDisclosureShown()
+ true
+ }
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ if (
+ WireGuardAutoTunnel.isRunningOnAndroidTv() &&
+ 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()
+ }
AnimatedVisibility(showLocationServicesAlertDialog) {
AlertDialog(
@@ -185,8 +228,9 @@ fun SettingsScreen(
TextButton(
onClick = {
showLocationServicesAlertDialog = false
- viewModel.toggleAutoTunnel()
- }) {
+ handleAutoTunnelToggle()
+ },
+ ) {
Text(text = stringResource(R.string.okay))
}
},
@@ -196,253 +240,313 @@ fun SettingsScreen(
}
},
title = { Text(text = stringResource(R.string.location_services_not_detected)) },
- text = { Text(text = stringResource(R.string.location_services_missing_message)) })
+ text = { Text(text = stringResource(R.string.location_services_missing_message)) },
+ )
}
- if (!uiState.isLocationDisclosureShown) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Top,
- modifier = Modifier.fillMaxSize().verticalScroll(scrollState).padding(padding)) {
- Icon(
- Icons.Rounded.LocationOff,
- contentDescription = stringResource(id = R.string.map),
- modifier = Modifier.padding(30.dp).size(128.dp))
- Text(
- stringResource(R.string.prominent_background_location_title),
- textAlign = TextAlign.Center,
- modifier = Modifier.padding(30.dp),
- fontSize = 20.sp)
- Text(
- stringResource(R.string.prominent_background_location_message),
- textAlign = TextAlign.Center,
- modifier = Modifier.padding(30.dp),
- fontSize = 15.sp)
- Row(
- modifier =
- if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
- Modifier.fillMaxWidth().padding(10.dp)
- } else {
- Modifier.fillMaxWidth().padding(30.dp)
- },
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceEvenly) {
+ if (!uiState.isLocationDisclosureShown) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Top,
+ modifier = Modifier.fillMaxSize().verticalScroll(scrollState).padding(padding),
+ ) {
+ Icon(
+ Icons.Rounded.LocationOff,
+ contentDescription = stringResource(id = R.string.map),
+ modifier = Modifier.padding(30.dp).size(128.dp),
+ )
+ Text(
+ stringResource(R.string.prominent_background_location_title),
+ textAlign = TextAlign.Center,
+ modifier = Modifier.padding(30.dp),
+ fontSize = 20.sp,
+ )
+ Text(
+ stringResource(R.string.prominent_background_location_message),
+ textAlign = TextAlign.Center,
+ modifier = Modifier.padding(30.dp),
+ fontSize = 15.sp,
+ )
+ Row(
+ modifier =
+ if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
+ Modifier.fillMaxWidth().padding(10.dp)
+ } else {
+ Modifier.fillMaxWidth().padding(30.dp)
+ },
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceEvenly,
+ ) {
TextButton(onClick = { viewModel.setLocationDisclosureShown() }) {
- Text(stringResource(id = R.string.no_thanks))
+ Text(stringResource(id = R.string.no_thanks))
}
TextButton(
modifier = Modifier.focusRequester(focusRequester),
onClick = {
- openSettings()
- viewModel.setLocationDisclosureShown()
- }) {
- Text(stringResource(id = R.string.turn_on))
- }
- }
+ openSettings()
+ viewModel.setLocationDisclosureShown()
+ },
+ ) {
+ Text(stringResource(id = R.string.turn_on))
+ }
+ }
}
- }
+ }
- if(showAuthPrompt) {
- AuthorizationPrompt(
- onSuccess = {
- showAuthPrompt = false
- exportAllConfigs()
- },
- onError = { _ ->
- showAuthPrompt = false
- showSnackbarMessage(Event.Error.AuthenticationFailed.message)
- },
- onFailure = {
- showAuthPrompt = false
- showSnackbarMessage(Event.Error.AuthorizationFailed.message)
- })
- }
+ if (showAuthPrompt) {
+ AuthorizationPrompt(
+ onSuccess = {
+ showAuthPrompt = false
+ exportAllConfigs()
+ },
+ onError = { _ ->
+ showAuthPrompt = false
+ showSnackbarMessage(Event.Error.AuthenticationFailed.message)
+ },
+ onFailure = {
+ showAuthPrompt = false
+ showSnackbarMessage(Event.Error.AuthorizationFailed.message)
+ },
+ )
+ }
- if (uiState.tunnels.isEmpty() && uiState.isLocationDisclosureShown) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center,
- modifier = Modifier.fillMaxSize().padding(padding)) {
- Text(
- stringResource(R.string.one_tunnel_required),
- textAlign = TextAlign.Center,
- modifier = Modifier.padding(15.dp),
- fontStyle = FontStyle.Italic)
+ if (uiState.tunnels.isEmpty() && uiState.isLocationDisclosureShown) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ modifier = Modifier.fillMaxSize().padding(padding),
+ ) {
+ Text(
+ stringResource(R.string.one_tunnel_required),
+ textAlign = TextAlign.Center,
+ modifier = Modifier.padding(15.dp),
+ fontStyle = FontStyle.Italic,
+ )
}
- }
- if (uiState.isLocationDisclosureShown && uiState.tunnels.isNotEmpty()) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Top,
- modifier =
- Modifier.fillMaxSize().verticalScroll(scrollState).clickable(
- indication = null, interactionSource = interactionSource) {
- focusManager.clearFocus()
- }) {
- Surface(
- tonalElevation = 2.dp,
- shadowElevation = 2.dp,
- shape = RoundedCornerShape(12.dp),
- color = MaterialTheme.colorScheme.surface,
- modifier =
- (if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
- Modifier.height(IntrinsicSize.Min)
- .fillMaxWidth(fillMaxWidth)
- .padding(top = 10.dp)
- } else {
- Modifier.fillMaxWidth(fillMaxWidth).padding(top = 60.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.isAutoTunnelEnabled ||
- uiState.settings.isAlwaysOnVpnEnabled),
- checked = uiState.settings.isTunnelOnWifiEnabled,
- padding = screenPadding,
- onCheckChanged = { viewModel.onToggleTunnelOnWifi() },
- modifier = if(uiState.settings.isAutoTunnelEnabled) Modifier else Modifier.focusRequester(focusRequester).focusProperties { down = focusRequester2 })
- AnimatedVisibility(visible = uiState.settings.isTunnelOnWifiEnabled) {
- Column {
- FlowRow(
- modifier = Modifier.padding(screenPadding).fillMaxWidth(),
- horizontalArrangement = Arrangement.spacedBy(5.dp)) {
- uiState.settings.trustedNetworkSSIDs.forEach { ssid ->
- ClickableIconButton(
- onClick = { if(WireGuardAutoTunnel.isRunningOnAndroidTv()) {
- viewModel.onDeleteTrustedSSID(ssid)
- focusRequester2.requestFocus()
- }},
- onIconClick = { viewModel.onDeleteTrustedSSID(ssid) },
- text = ssid,
- icon = Icons.Filled.Close,
- enabled =
- !(uiState.settings.isAutoTunnelEnabled ||
- uiState.settings.isAlwaysOnVpnEnabled))
- }
- if (uiState.settings.trustedNetworkSSIDs.isEmpty()) {
- Text(
- stringResource(R.string.none),
- fontStyle = FontStyle.Italic,
- color = Color.Gray)
- }
- }
- OutlinedTextField(
- enabled =
- !(uiState.settings.isAutoTunnelEnabled ||
- uiState.settings.isAlwaysOnVpnEnabled),
- value = currentText,
- onValueChange = { currentText = it },
- label = { Text(stringResource(R.string.add_trusted_ssid)) },
- modifier =
- Modifier.padding(
- start = screenPadding, top = 5.dp, bottom = 10.dp)
- .focusRequester(focusRequester2)
- ,
- 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.isAutoTunnelEnabled ||
- uiState.settings.isAlwaysOnVpnEnabled),
- checked = uiState.settings.isTunnelOnMobileDataEnabled,
- padding = screenPadding,
- onCheckChanged = { viewModel.onToggleTunnelOnMobileData() })
- ConfigurationToggle(
- stringResource(id = R.string.tunnel_on_ethernet),
- enabled =
- !(uiState.settings.isAutoTunnelEnabled ||
- uiState.settings.isAlwaysOnVpnEnabled),
- checked = uiState.settings.isTunnelOnEthernetEnabled,
- padding = screenPadding,
- onCheckChanged = { viewModel.onToggleTunnelOnEthernet() })
- ConfigurationToggle(
- stringResource(R.string.battery_saver),
- enabled =
- !(uiState.settings.isAutoTunnelEnabled ||
- uiState.settings.isAlwaysOnVpnEnabled),
- checked = uiState.settings.isBatterySaverEnabled,
- padding = screenPadding,
- onCheckChanged = { viewModel.onToggleBatterySaver() })
- Row(
- verticalAlignment = Alignment.CenterVertically,
- modifier = (if(!uiState.settings.isAutoTunnelEnabled) Modifier else Modifier.focusRequester(focusRequester))
- .fillMaxSize().padding(top = 5.dp),
- horizontalArrangement = Arrangement.Center) {
- TextButton(
- enabled = !uiState.settings.isAlwaysOnVpnEnabled,
- onClick = {
- if (uiState.settings.isTunnelOnWifiEnabled && !uiState.settings.isAutoTunnelEnabled) {
- when(false) {
- isBackgroundLocationGranted ->
- showSnackbarMessage(Event.Error.BackgroundLocationRequired.message)
- fineLocationState.status.isGranted ->
- showSnackbarMessage(Event.Error.PreciseLocationRequired.message)
- viewModel.isLocationEnabled(context) ->
- showLocationServicesAlertDialog = true
- else -> {
- viewModel.toggleAutoTunnel()
- }
- }
- } else {
- viewModel.toggleAutoTunnel()
- }
- }) {
- val autoTunnelButtonText =
- if (uiState.settings.isAutoTunnelEnabled) {
- stringResource(R.string.disable_auto_tunnel)
- } else {
- stringResource(id = R.string.enable_auto_tunnel)
- }
- Text(autoTunnelButtonText)
- }
- }
- }
- }
- if (WgQuickBackend.hasKernelSupport()) {
+ }
+ if (uiState.isLocationDisclosureShown && uiState.tunnels.isNotEmpty()) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Top,
+ modifier =
+ Modifier.fillMaxSize().verticalScroll(scrollState).clickable(
+ indication = null,
+ interactionSource = interactionSource,
+ ) {
+ focusManager.clearFocus()
+ },
+ ) {
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)) {
+ modifier =
+ (if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
+ Modifier.height(IntrinsicSize.Min)
+ .fillMaxWidth(fillMaxWidth)
+ .padding(top = 10.dp)
+ } else {
+ Modifier.fillMaxWidth(fillMaxWidth).padding(top = 60.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.isAutoTunnelEnabled ||
+ uiState.settings.isAlwaysOnVpnEnabled),
+ checked = uiState.settings.isTunnelOnWifiEnabled,
+ padding = screenPadding,
+ onCheckChanged = { viewModel.onToggleTunnelOnWifi() },
+ modifier =
+ if (uiState.settings.isAutoTunnelEnabled) Modifier
+ else
+ Modifier.focusRequester(focusRequester).focusProperties {
+ down = focusRequester2
+ },
+ )
+ AnimatedVisibility(visible = uiState.settings.isTunnelOnWifiEnabled) {
+ Column {
+ FlowRow(
+ modifier = Modifier.padding(screenPadding).fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(5.dp),
+ ) {
+ uiState.settings.trustedNetworkSSIDs.forEach { ssid ->
+ ClickableIconButton(
+ onClick = {
+ if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
+ viewModel.onDeleteTrustedSSID(ssid)
+ focusRequester2.requestFocus()
+ }
+ },
+ onIconClick = { viewModel.onDeleteTrustedSSID(ssid) },
+ text = ssid,
+ icon = Icons.Filled.Close,
+ enabled =
+ !(uiState.settings.isAutoTunnelEnabled ||
+ uiState.settings.isAlwaysOnVpnEnabled),
+ )
+ }
+ if (uiState.settings.trustedNetworkSSIDs.isEmpty()) {
+ Text(
+ stringResource(R.string.none),
+ fontStyle = FontStyle.Italic,
+ color = Color.Gray,
+ )
+ }
+ }
+ OutlinedTextField(
+ enabled =
+ !(uiState.settings.isAutoTunnelEnabled ||
+ uiState.settings.isAlwaysOnVpnEnabled),
+ value = currentText,
+ onValueChange = { currentText = it },
+ label = { Text(stringResource(R.string.add_trusted_ssid)) },
+ modifier =
+ Modifier.padding(
+ start = screenPadding,
+ top = 5.dp,
+ bottom = 10.dp,
+ )
+ .focusRequester(focusRequester2),
+ 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.isAutoTunnelEnabled ||
+ uiState.settings.isAlwaysOnVpnEnabled),
+ checked = uiState.settings.isTunnelOnMobileDataEnabled,
+ padding = screenPadding,
+ onCheckChanged = { viewModel.onToggleTunnelOnMobileData() },
+ )
+ ConfigurationToggle(
+ stringResource(id = R.string.tunnel_on_ethernet),
+ enabled =
+ !(uiState.settings.isAutoTunnelEnabled ||
+ uiState.settings.isAlwaysOnVpnEnabled),
+ checked = uiState.settings.isTunnelOnEthernetEnabled,
+ padding = screenPadding,
+ onCheckChanged = { viewModel.onToggleTunnelOnEthernet() },
+ )
+ ConfigurationToggle(
+ stringResource(R.string.battery_saver),
+ enabled =
+ !(uiState.settings.isAutoTunnelEnabled ||
+ uiState.settings.isAlwaysOnVpnEnabled),
+ checked = uiState.settings.isBatterySaverEnabled,
+ padding = screenPadding,
+ onCheckChanged = { viewModel.onToggleBatterySaver() },
+ )
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ (if (!uiState.settings.isAutoTunnelEnabled) Modifier
+ else
+ Modifier.focusRequester(
+ focusRequester,
+ ))
+ .fillMaxSize()
+ .padding(top = 5.dp),
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ TextButton(
+ enabled = !uiState.settings.isAlwaysOnVpnEnabled,
+ onClick = {
+ if (
+ uiState.settings.isTunnelOnWifiEnabled &&
+ !uiState.settings.isAutoTunnelEnabled
+ ) {
+ when (false) {
+ isBackgroundLocationGranted ->
+ showSnackbarMessage(
+ Event.Error.BackgroundLocationRequired.message
+ )
+ fineLocationState.status.isGranted ->
+ showSnackbarMessage(
+ Event.Error.PreciseLocationRequired.message
+ )
+ viewModel.isLocationEnabled(context) ->
+ showLocationServicesAlertDialog = true
+ else -> {
+ handleAutoTunnelToggle()
+ }
+ }
+ } else {
+ handleAutoTunnelToggle()
+ }
+ },
+ ) {
+ val autoTunnelButtonText =
+ if (uiState.settings.isAutoTunnelEnabled) {
+ stringResource(R.string.disable_auto_tunnel)
+ } else {
+ stringResource(id = R.string.enable_auto_tunnel)
+ }
+ Text(autoTunnelButtonText)
+ }
+ }
+ }
+ }
+ if (WgQuickBackend.hasKernelSupport()) {
+ 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.kernel), padding = screenPadding)
+ title = stringResource(id = R.string.kernel),
+ padding = screenPadding,
+ )
ConfigurationToggle(
stringResource(R.string.use_kernel),
enabled =
@@ -451,58 +555,70 @@ fun SettingsScreen(
(uiState.vpnState.status == Tunnel.State.UP)),
checked = uiState.settings.isKernelEnabled,
padding = screenPadding,
- onCheckChanged = { viewModel.onToggleKernelMode().let {
- when(it) {
- is Result.Error -> showSnackbarMessage(it.error.message)
- is Result.Success -> {}
+ onCheckChanged = {
+ viewModel.onToggleKernelMode().let {
+ when (it) {
+ is Result.Error -> showSnackbarMessage(it.error.message)
+ is Result.Success -> {}
+ }
}
- } })
- }
+ },
+ )
+ }
}
- }
- if (!WireGuardAutoTunnel.isRunningOnAndroidTv()) {
- 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 = 140.dp)) {
- Column(
- horizontalAlignment = Alignment.Start,
- verticalArrangement = Arrangement.Top,
- modifier = Modifier.padding(15.dp)) {
+ }
+ if (!WireGuardAutoTunnel.isRunningOnAndroidTv()) {
+ 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 = 140.dp),
+ ) {
+ Column(
+ horizontalAlignment = Alignment.Start,
+ verticalArrangement = Arrangement.Top,
+ modifier = Modifier.padding(15.dp),
+ ) {
SectionTitle(
- title = stringResource(id = R.string.other), padding = screenPadding)
+ title = stringResource(id = R.string.other),
+ padding = screenPadding,
+ )
ConfigurationToggle(
stringResource(R.string.always_on_vpn_support),
enabled = !uiState.settings.isAutoTunnelEnabled,
checked = uiState.settings.isAlwaysOnVpnEnabled,
padding = screenPadding,
- onCheckChanged = { viewModel.onToggleAlwaysOnVPN() })
+ onCheckChanged = { viewModel.onToggleAlwaysOnVPN() },
+ )
ConfigurationToggle(
stringResource(R.string.enabled_app_shortcuts),
enabled = true,
checked = uiState.settings.isShortcutsEnabled,
padding = screenPadding,
- onCheckChanged = { viewModel.onToggleShortcutsEnabled() })
+ onCheckChanged = { viewModel.onToggleShortcutsEnabled() },
+ )
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxSize().padding(top = 5.dp),
- horizontalArrangement = Arrangement.Center) {
- TextButton(
- enabled = !didExportFiles, onClick = { showAuthPrompt = true }) {
- Text(stringResource(R.string.export_configs))
- }
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ TextButton(
+ enabled = !didExportFiles,
+ onClick = { showAuthPrompt = true },
+ ) {
+ Text(stringResource(R.string.export_configs))
}
- }
+ }
+ }
}
- }
- if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
- Spacer(modifier = Modifier.weight(.17f))
- }
+ }
+ if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
+ Spacer(modifier = Modifier.weight(.17f))
+ }
}
- }
+ }
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsUiState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsUiState.kt
index 21f150e2..01f4fe84 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsUiState.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsUiState.kt
@@ -5,9 +5,10 @@ import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState
data class SettingsUiState(
- val settings : Settings = Settings(),
- val tunnels : List = emptyList(),
+ val settings: Settings = Settings(),
+ val tunnels: List = emptyList(),
val vpnState: VpnState = VpnState(),
- val isLocationDisclosureShown : Boolean = true,
- val loading : Boolean = true
+ val isLocationDisclosureShown: Boolean = true,
+ val isBatteryOptimizeDisableShown: Boolean = false,
+ val loading: Boolean = true
)
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsViewModel.kt
index 6e91616d..5eff5767 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsViewModel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsViewModel.kt
@@ -36,18 +36,29 @@ constructor(
private val vpnService: VpnService
) : ViewModel() {
- val uiState = combine(
- settingsRepository.getSettingsFlow(),
- tunnelConfigRepository.getTunnelConfigsFlow(),
- vpnService.vpnState,
- dataStoreManager.locationDisclosureFlow,
- ){ settings, tunnels, tunnelState, locationDisclosure ->
- SettingsUiState(settings, tunnels, tunnelState, locationDisclosure
- ?: false, false)
- }.stateIn(viewModelScope,
- SharingStarted.WhileSubscribed(Constants.SUBSCRIPTION_TIMEOUT), SettingsUiState())
+ val uiState =
+ combine(
+ settingsRepository.getSettingsFlow(),
+ tunnelConfigRepository.getTunnelConfigsFlow(),
+ vpnService.vpnState,
+ dataStoreManager.preferencesFlow,
+ ) { settings, tunnels, tunnelState, preferences ->
+ SettingsUiState(
+ settings,
+ tunnels,
+ tunnelState,
+ preferences?.get(DataStoreManager.LOCATION_DISCLOSURE_SHOWN) ?: false,
+ preferences?.get(DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN) ?: false,
+ false
+ )
+ }
+ .stateIn(
+ viewModelScope,
+ SharingStarted.WhileSubscribed(Constants.SUBSCRIPTION_TIMEOUT),
+ SettingsUiState(),
+ )
- fun onSaveTrustedSSID(ssid: String) : Result{
+ fun onSaveTrustedSSID(ssid: String): Result {
val trimmed = ssid.trim()
return if (!uiState.value.settings.trustedNetworkSSIDs.contains(trimmed)) {
uiState.value.settings.trustedNetworkSSIDs.add(trimmed)
@@ -58,64 +69,77 @@ constructor(
}
}
- fun setLocationDisclosureShown() = viewModelScope.launch {
+ fun setLocationDisclosureShown() =
+ viewModelScope.launch {
dataStoreManager.saveToDataStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN, true)
- }
+ }
+
+ fun setBatteryOptimizeDisableShown() =
+ viewModelScope.launch {
+ dataStoreManager.saveToDataStore(DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN, true)
+ }
fun onToggleTunnelOnMobileData() {
saveSettings(
uiState.value.settings.copy(
- isTunnelOnMobileDataEnabled = !uiState.value.settings.isTunnelOnMobileDataEnabled
- )
+ isTunnelOnMobileDataEnabled = !uiState.value.settings.isTunnelOnMobileDataEnabled,
+ ),
)
}
fun onDeleteTrustedSSID(ssid: String) {
- saveSettings(uiState.value.settings.copy(
- trustedNetworkSSIDs = (uiState.value.settings.trustedNetworkSSIDs - ssid).toMutableList()
- ))
- }
-
- private suspend fun getDefaultTunnelOrFirst() : String {
- return uiState.value.settings.defaultTunnel ?: tunnelConfigRepository.getAll().first().toString()
- }
-
- fun toggleAutoTunnel() = viewModelScope.launch {
- val isAutoTunnelEnabled = uiState.value.settings.isAutoTunnelEnabled
- var isAutoTunnelPaused = uiState.value.settings.isAutoTunnelPaused
-
- if (isAutoTunnelEnabled) {
- ServiceManager.stopWatcherService(application)
- } else {
- ServiceManager.startWatcherService(application)
- isAutoTunnelPaused = false
- }
saveSettings(
uiState.value.settings.copy(
- isAutoTunnelEnabled = !isAutoTunnelEnabled,
- isAutoTunnelPaused = isAutoTunnelPaused,
- defaultTunnel = getDefaultTunnelOrFirst()
- )
+ trustedNetworkSSIDs =
+ (uiState.value.settings.trustedNetworkSSIDs - ssid).toMutableList(),
+ ),
)
}
+ private suspend fun getDefaultTunnelOrFirst(): String {
+ return uiState.value.settings.defaultTunnel
+ ?: tunnelConfigRepository.getAll().first().toString()
+ }
- fun onToggleAlwaysOnVPN() = viewModelScope.launch {
- val updatedSettings = uiState.value.settings.copy(
- isAlwaysOnVpnEnabled = !uiState.value.settings.isAlwaysOnVpnEnabled,
- defaultTunnel = getDefaultTunnelOrFirst()
+ fun toggleAutoTunnel() =
+ viewModelScope.launch {
+ val isAutoTunnelEnabled = uiState.value.settings.isAutoTunnelEnabled
+ var isAutoTunnelPaused = uiState.value.settings.isAutoTunnelPaused
+
+ if (isAutoTunnelEnabled) {
+ ServiceManager.stopWatcherService(application)
+ } else {
+ ServiceManager.startWatcherService(application)
+ isAutoTunnelPaused = false
+ }
+ saveSettings(
+ uiState.value.settings.copy(
+ isAutoTunnelEnabled = !isAutoTunnelEnabled,
+ isAutoTunnelPaused = isAutoTunnelPaused,
+ defaultTunnel = getDefaultTunnelOrFirst(),
+ ),
)
- saveSettings(updatedSettings)
- }
+ }
- private fun saveSettings(settings: Settings) = viewModelScope.launch {
- settingsRepository.save(settings)
- }
+ fun onToggleAlwaysOnVPN() =
+ viewModelScope.launch {
+ val updatedSettings =
+ uiState.value.settings.copy(
+ isAlwaysOnVpnEnabled = !uiState.value.settings.isAlwaysOnVpnEnabled,
+ defaultTunnel = getDefaultTunnelOrFirst(),
+ )
+ saveSettings(updatedSettings)
+ }
+
+ private fun saveSettings(settings: Settings) =
+ viewModelScope.launch { settingsRepository.save(settings) }
fun onToggleTunnelOnEthernet() {
- saveSettings(uiState.value.settings.copy(
- isTunnelOnEthernetEnabled = !uiState.value.settings.isTunnelOnEthernetEnabled
- ))
+ saveSettings(
+ uiState.value.settings.copy(
+ isTunnelOnEthernetEnabled = !uiState.value.settings.isTunnelOnEthernetEnabled,
+ ),
+ )
}
fun isLocationEnabled(context: Context): Boolean {
@@ -126,36 +150,36 @@ constructor(
fun onToggleShortcutsEnabled() {
saveSettings(
uiState.value.settings.copy(
- isShortcutsEnabled = !uiState.value.settings.isShortcutsEnabled
- )
+ isShortcutsEnabled = !uiState.value.settings.isShortcutsEnabled,
+ ),
)
}
fun onToggleBatterySaver() {
saveSettings(
uiState.value.settings.copy(
- isBatterySaverEnabled = !uiState.value.settings.isBatterySaverEnabled
- )
+ isBatterySaverEnabled = !uiState.value.settings.isBatterySaverEnabled,
+ ),
)
}
private fun saveKernelMode(on: Boolean) {
saveSettings(
uiState.value.settings.copy(
- isKernelEnabled = on
- )
+ isKernelEnabled = on,
+ ),
)
}
fun onToggleTunnelOnWifi() {
saveSettings(
uiState.value.settings.copy(
- isTunnelOnWifiEnabled = !uiState.value.settings.isTunnelOnWifiEnabled
- )
+ isTunnelOnWifiEnabled = !uiState.value.settings.isTunnelOnWifiEnabled,
+ ),
)
}
- fun onToggleKernelMode() : Result {
+ fun onToggleKernelMode(): Result {
if (!uiState.value.settings.isKernelEnabled) {
try {
rootShell.start()
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt
index 518211d2..8be48c73 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt
@@ -62,39 +62,43 @@ fun SupportScreen(
showSnackbarMessage: (String) -> Unit,
focusRequester: FocusRequester
) {
- val context = LocalContext.current
- val fillMaxWidth = .85f
+ val context = LocalContext.current
+ val fillMaxWidth = .85f
- val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
- fun openWebPage(url: String) {
- try {
- val webpage: Uri = Uri.parse(url)
- val intent = Intent(Intent.ACTION_VIEW, webpage)
- context.startActivity(intent)
- } catch (e : Exception) {
- showSnackbarMessage(Event.Error.Exception(e).message)
- }
- }
+ fun openWebPage(url: String) {
+ try {
+ val webpage: Uri = Uri.parse(url)
+ val intent = Intent(Intent.ACTION_VIEW, webpage)
+ context.startActivity(intent)
+ } catch (e: Exception) {
+ showSnackbarMessage(Event.Error.Exception(e).message)
+ }
+ }
- fun launchEmail() {
- try {
- val intent =
- Intent(Intent.ACTION_SEND).apply {
- type = Constants.EMAIL_MIME_TYPE
- putExtra(Intent.EXTRA_EMAIL, context.getString(R.string.my_email))
- putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.email_subject))
- }
- startActivity(context, createChooser(intent, context.getString(R.string.email_chooser)), null)
- } catch (e : Exception) {
- showSnackbarMessage(Event.Error.Exception(e).message)
- }
- }
+ fun launchEmail() {
+ try {
+ val intent =
+ Intent(Intent.ACTION_SENDTO).apply {
+ type = Constants.EMAIL_MIME_TYPE
+ putExtra(Intent.EXTRA_EMAIL, arrayOf(context.getString(R.string.my_email)))
+ putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.email_subject))
+ }
+ startActivity(
+ context,
+ createChooser(intent, context.getString(R.string.email_chooser)),
+ null,
+ )
+ } catch (e: Exception) {
+ showSnackbarMessage(Event.Error.Exception(e).message)
+ }
+ }
- if (uiState.loading) {
- LoadingScreen()
- return
- }
+ if (uiState.loading) {
+ LoadingScreen()
+ return
+ }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
@@ -103,126 +107,147 @@ fun SupportScreen(
Modifier.fillMaxSize()
.verticalScroll(rememberScrollState())
.focusable()
- .padding(padding)) {
- Surface(
- tonalElevation = 2.dp,
- shadowElevation = 2.dp,
- shape = RoundedCornerShape(12.dp),
- color = MaterialTheme.colorScheme.surface,
- modifier =
- (if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
+ .padding(padding),
+ ) {
+ Surface(
+ tonalElevation = 2.dp,
+ shadowElevation = 2.dp,
+ shape = RoundedCornerShape(12.dp),
+ color = MaterialTheme.colorScheme.surface,
+ modifier =
+ (if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
Modifier.height(IntrinsicSize.Min)
.fillMaxWidth(fillMaxWidth)
.padding(top = 10.dp)
- } else {
+ } else {
Modifier.fillMaxWidth(fillMaxWidth).padding(top = 20.dp)
- })
- .padding(bottom = 25.dp)) {
- Column(modifier = Modifier.padding(20.dp)) {
- Text(
- stringResource(R.string.thank_you),
- textAlign = TextAlign.Start,
- fontWeight = FontWeight.Bold,
- modifier = Modifier.padding(bottom = 20.dp),
- fontSize = 16.sp)
- Text(
- stringResource(id = R.string.support_help_text),
- textAlign = TextAlign.Start,
- fontSize = 16.sp,
- modifier = Modifier.padding(bottom = 20.dp))
- TextButton(
- onClick = { openWebPage(context.resources.getString(R.string.docs_url)) },
- modifier = Modifier.padding(vertical = 5.dp).focusRequester(focusRequester)) {
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.fillMaxWidth()) {
- Row {
- Icon(Icons.Rounded.Book, stringResource(id = R.string.docs))
- Text(
- stringResource(id = R.string.docs_description),
- textAlign = TextAlign.Justify,
- modifier = Modifier.padding(start = 10.dp))
- }
- Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
- }
- }
- Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp)
- TextButton(
- onClick = { openWebPage(context.resources.getString(R.string.discord_url)) },
- modifier = Modifier.padding(vertical = 5.dp)) {
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.fillMaxWidth()) {
- Row {
- Icon(
- imageVector = ImageVector.vectorResource(R.drawable.discord),
- stringResource(id = R.string.discord),
- Modifier.size(25.dp))
- Text(
- stringResource(id = R.string.discord_description),
- textAlign = TextAlign.Justify,
- modifier = Modifier.padding(start = 10.dp))
- }
- Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
- }
- }
- Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp)
- TextButton(
- onClick = { openWebPage(context.resources.getString(R.string.github_url)) },
- modifier = Modifier.padding(vertical = 5.dp)) {
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.fillMaxWidth()) {
- Row {
- Icon(
- imageVector = ImageVector.vectorResource(R.drawable.github),
- stringResource(id = R.string.github),
- Modifier.size(25.dp))
- Text(
- "Open an issue",
- textAlign = TextAlign.Justify,
- modifier = Modifier.padding(start = 10.dp))
- }
- Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
- }
- }
- Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp)
- TextButton(
- onClick = { launchEmail() }, modifier = Modifier.padding(vertical = 5.dp)) {
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.fillMaxWidth()) {
- Row {
- Icon(Icons.Rounded.Mail, stringResource(id = R.string.email))
- Text(
- stringResource(id = R.string.email_description),
- textAlign = TextAlign.Justify,
- modifier = Modifier.padding(start = 10.dp))
- }
- Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
- }
- }
+ })
+ .padding(bottom = 25.dp),
+ ) {
+ Column(modifier = Modifier.padding(20.dp)) {
+ Text(
+ stringResource(R.string.thank_you),
+ textAlign = TextAlign.Start,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier.padding(bottom = 20.dp),
+ fontSize = 16.sp,
+ )
+ Text(
+ stringResource(id = R.string.support_help_text),
+ textAlign = TextAlign.Start,
+ fontSize = 16.sp,
+ modifier = Modifier.padding(bottom = 20.dp),
+ )
+ TextButton(
+ onClick = { openWebPage(context.resources.getString(R.string.docs_url)) },
+ modifier = Modifier.padding(vertical = 5.dp).focusRequester(focusRequester),
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ Row {
+ Icon(Icons.Rounded.Book, stringResource(id = R.string.docs))
+ Text(
+ stringResource(id = R.string.docs_description),
+ textAlign = TextAlign.Justify,
+ modifier = Modifier.padding(start = 10.dp),
+ )
+ }
+ Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
+ }
}
- }
- Spacer(modifier = Modifier.weight(1f))
- Text(
- stringResource(id = R.string.privacy_policy),
- style = TextStyle(textDecoration = TextDecoration.Underline),
- fontSize = 16.sp,
- modifier =
- Modifier.clickable {
- openWebPage(context.resources.getString(R.string.privacy_policy_url))
- })
- Row(
- horizontalArrangement = Arrangement.spacedBy(25.dp),
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.padding(25.dp)) {
- Text("Version: ${BuildConfig.VERSION_NAME}", modifier = Modifier.focusable())
- Text("Mode: ${if (uiState.settings.isKernelEnabled) "Kernel" else "Userspace" }")
- }
+ Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp)
+ TextButton(
+ onClick = { openWebPage(context.resources.getString(R.string.discord_url)) },
+ modifier = Modifier.padding(vertical = 5.dp),
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ Row {
+ Icon(
+ imageVector = ImageVector.vectorResource(R.drawable.discord),
+ stringResource(id = R.string.discord),
+ Modifier.size(25.dp),
+ )
+ Text(
+ stringResource(id = R.string.discord_description),
+ textAlign = TextAlign.Justify,
+ modifier = Modifier.padding(start = 10.dp),
+ )
+ }
+ Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
+ }
+ }
+ Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp)
+ TextButton(
+ onClick = { openWebPage(context.resources.getString(R.string.github_url)) },
+ modifier = Modifier.padding(vertical = 5.dp),
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ Row {
+ Icon(
+ imageVector = ImageVector.vectorResource(R.drawable.github),
+ stringResource(id = R.string.github),
+ Modifier.size(25.dp),
+ )
+ Text(
+ "Open an issue",
+ textAlign = TextAlign.Justify,
+ modifier = Modifier.padding(start = 10.dp),
+ )
+ }
+ Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
+ }
+ }
+ Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp)
+ TextButton(
+ onClick = { launchEmail() },
+ modifier = Modifier.padding(vertical = 5.dp),
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ Row {
+ Icon(Icons.Rounded.Mail, stringResource(id = R.string.email))
+ Text(
+ stringResource(id = R.string.email_description),
+ textAlign = TextAlign.Justify,
+ modifier = Modifier.padding(start = 10.dp),
+ )
+ }
+ Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
+ }
+ }
+ }
}
- }
+ Spacer(modifier = Modifier.weight(1f))
+ Text(
+ stringResource(id = R.string.privacy_policy),
+ style = TextStyle(textDecoration = TextDecoration.Underline),
+ fontSize = 16.sp,
+ modifier =
+ Modifier.clickable {
+ openWebPage(context.resources.getString(R.string.privacy_policy_url))
+ },
+ )
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(25.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.padding(25.dp),
+ ) {
+ Text("Version: ${BuildConfig.VERSION_NAME}", modifier = Modifier.focusable())
+ Text("Mode: ${if (uiState.settings.isKernelEnabled) "Kernel" else "Userspace"}")
+ }
+ }
+}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportUiState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportUiState.kt
index ef5dcc89..e874416c 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportUiState.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportUiState.kt
@@ -2,7 +2,4 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.support
import com.zaneschepke.wireguardautotunnel.data.model.Settings
-data class SupportUiState(
- val settings : Settings = Settings(),
- val loading : Boolean = true
-)
\ No newline at end of file
+data class SupportUiState(val settings: Settings = Settings(), val loading: Boolean = true)
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportViewModel.kt
index f023ad90..dc9e638d 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportViewModel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportViewModel.kt
@@ -11,15 +11,16 @@ import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
@HiltViewModel
-class SupportViewModel @Inject constructor(
- private val settingsRepository: SettingsRepository
-) : ViewModel() {
+class SupportViewModel @Inject constructor(private val settingsRepository: SettingsRepository) :
+ ViewModel() {
- val uiState = settingsRepository.getSettingsFlow().map {
- SupportUiState(it, false)
- }.stateIn(
- viewModelScope,
- SharingStarted.WhileSubscribed(Constants.SUBSCRIPTION_TIMEOUT),
- SupportUiState()
- )
+ val uiState =
+ settingsRepository
+ .getSettingsFlow()
+ .map { SupportUiState(it, false) }
+ .stateIn(
+ viewModelScope,
+ SharingStarted.WhileSubscribed(Constants.SUBSCRIPTION_TIMEOUT),
+ SupportUiState(),
+ )
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Theme.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Theme.kt
index d4d529bc..0c4770c9 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Theme.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Theme.kt
@@ -21,7 +21,7 @@ private val DarkColorScheme =
primary = virdigris,
secondary = virdigris,
// secondary = PurpleGrey80,
- tertiary = virdigris
+ tertiary = virdigris,
// tertiary = Pink80
)
@@ -29,7 +29,7 @@ private val LightColorScheme =
lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
- tertiary = Pink40
+ tertiary = Pink40,
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
@@ -57,7 +57,6 @@ fun WireguardAutoTunnelTheme(
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
-
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
@@ -68,14 +67,19 @@ fun WireguardAutoTunnelTheme(
WindowCompat.setDecorFitsSystemWindows(window, false)
window.statusBarColor = Color.Transparent.toArgb()
window.navigationBarColor = Color.Transparent.toArgb()
- WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars = !darkTheme
- WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightNavigationBars = !darkTheme
+ WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars =
+ !darkTheme
+ WindowCompat.getInsetsController(
+ window,
+ window.decorView,
+ )
+ .isAppearanceLightNavigationBars = !darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
- content = content
+ content = content,
)
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/TransparentSystemBars.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/TransparentSystemBars.kt
index 62ee01f5..9d353baf 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/TransparentSystemBars.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/TransparentSystemBars.kt
@@ -14,7 +14,7 @@ fun TransparentSystemBars() {
DisposableEffect(systemUiController, useDarkIcons) {
systemUiController.setSystemBarsColor(
color = Color.Transparent,
- darkIcons = useDarkIcons
+ darkIcons = useDarkIcons,
)
onDispose {}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Type.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Type.kt
index 9f0e5797..9eac359c 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Type.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Type.kt
@@ -10,13 +10,13 @@ import androidx.compose.ui.unit.sp
val Typography =
Typography(
bodyLarge =
- TextStyle(
- fontFamily = FontFamily.Default,
- fontWeight = FontWeight.Normal,
- fontSize = 16.sp,
- lineHeight = 24.sp,
- letterSpacing = 0.5.sp
- )
+ TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.5.sp,
+ ),
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Event.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Event.kt
index a7da4b02..d95a8836 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Event.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Event.kt
@@ -12,79 +12,100 @@ sealed class Event {
override val message: String
get() = WireGuardAutoTunnel.instance.getString(R.string.error_none)
}
+
data object SsidConflict : Error() {
override val message: String
get() = WireGuardAutoTunnel.instance.getString(R.string.error_ssid_exists)
}
+
data object RootDenied : Error() {
override val message: String
get() = WireGuardAutoTunnel.instance.getString(R.string.error_root_denied)
}
+
data class General(val customMessage: String) : Error() {
override val message: String
get() = customMessage
}
- data class Exception(val exception : kotlin.Exception) : Error() {
+
+ data class Exception(val exception: kotlin.Exception) : Error() {
override val message: String
- get() = exception.message ?: WireGuardAutoTunnel.instance.getString(R.string.unknown_error)
+ get() =
+ exception.message
+ ?: WireGuardAutoTunnel.instance.getString(R.string.unknown_error)
}
+
data object InvalidQrCode : Error() {
override val message: String
get() = WireGuardAutoTunnel.instance.getString(R.string.error_invalid_code)
}
+
data object InvalidFileExtension : Error() {
override val message: String
get() = WireGuardAutoTunnel.instance.getString(R.string.error_file_extension)
}
+
data object FileReadFailed : Error() {
override val message: String
get() = WireGuardAutoTunnel.instance.getString(R.string.error_file_extension)
}
+
data object AuthenticationFailed : Error() {
override val message: String
get() = WireGuardAutoTunnel.instance.getString(R.string.error_authentication_failed)
}
+
data object AuthorizationFailed : Error() {
override val message: String
get() = WireGuardAutoTunnel.instance.getString(R.string.error_authorization_failed)
}
+
data object BackgroundLocationRequired : Error() {
override val message: String
- get() = WireGuardAutoTunnel.instance.getString(R.string.background_location_required)
+ get() =
+ WireGuardAutoTunnel.instance.getString(R.string.background_location_required)
}
+
data object LocationServicesRequired : Error() {
override val message: String
get() = WireGuardAutoTunnel.instance.getString(R.string.location_services_required)
}
+
data object PreciseLocationRequired : Error() {
override val message: String
get() = WireGuardAutoTunnel.instance.getString(R.string.precise_location_required)
}
+
data object FileExplorerRequired : Error() {
override val message: String
get() = WireGuardAutoTunnel.instance.getString(R.string.error_no_file_explorer)
}
}
+
sealed class Message : Event() {
- data object ConfigSaved: Message() {
+ data object ConfigSaved : Message() {
override val message: String
get() = WireGuardAutoTunnel.instance.getString(R.string.config_changes_saved)
}
- data object ConfigsExported: Message() {
+
+ data object ConfigsExported : Message() {
override val message: String
get() = WireGuardAutoTunnel.instance.getString(R.string.exported_configs_message)
}
- data object TunnelOffAction: Message() {
+
+ data object TunnelOffAction : Message() {
override val message: String
get() = WireGuardAutoTunnel.instance.getString(R.string.turn_off_tunnel)
}
- data object TunnelOnAction: Message() {
+
+ data object TunnelOnAction : Message() {
override val message: String
get() = WireGuardAutoTunnel.instance.getString(R.string.turn_on_tunnel)
}
- data object AutoTunnelOffAction: Message() {
+
+ data object AutoTunnelOffAction : Message() {
override val message: String
get() = WireGuardAutoTunnel.instance.getString(R.string.turn_off_auto)
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Extensions.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Extensions.kt
index 7eb05864..acc19ee1 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Extensions.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Extensions.kt
@@ -37,23 +37,23 @@ fun BigDecimal.toThreeDecimalPlaceString(): String {
}
fun List.update(index: Int, item: T): List = toMutableList().apply { this[index] = item }
+
fun List.removeAt(index: Int): List = toMutableList().apply { this.removeAt(index) }
typealias TunnelConfigs = List
+
typealias Packages = List
fun Statistics.mapPeerStats(): Map {
- return this.peers().associateWith { key ->
- (this.peer(key))
- }
+ return this.peers().associateWith { key -> (this.peer(key)) }
}
-fun PeerStats.latestHandshakeSeconds() : Long? {
+fun PeerStats.latestHandshakeSeconds(): Long? {
return NumberUtils.getSecondsBetweenTimestampAndNow(this.latestHandshakeEpochMillis)
}
-fun PeerStats.handshakeStatus() : HandshakeStatus {
- //TODO add never connected status after duration
+fun PeerStats.handshakeStatus(): HandshakeStatus {
+ // TODO add never connected status after duration
return this.latestHandshakeSeconds().let {
when {
it == null -> HandshakeStatus.NOT_STARTED
@@ -65,4 +65,3 @@ fun PeerStats.handshakeStatus() : HandshakeStatus {
}
}
}
-
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt
index 2651dabb..2c895cde 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt
@@ -36,22 +36,20 @@ object FileUtils {
val target =
File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
- fileName
+ fileName,
)
return target.outputStream()
}
return null
}
- fun saveFilesToZip(
- context: Context,
- files: List
- ) {
- val zipOutputStream = createDownloadsFileOutputStream(
- context,
- "wg-export_${Instant.now().epochSecond}.zip",
- ZIP_FILE_MIME_TYPE
- )
+ fun saveFilesToZip(context: Context, files: List) {
+ val zipOutputStream =
+ createDownloadsFileOutputStream(
+ context,
+ "wg-export_${Instant.now().epochSecond}.zip",
+ ZIP_FILE_MIME_TYPE,
+ )
ZipOutputStream(zipOutputStream).use { zos ->
files.forEach { file ->
val entry = ZipEntry(file.name)
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Result.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Result.kt
index c5d59136..b0515cb1 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Result.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Result.kt
@@ -2,15 +2,15 @@ package com.zaneschepke.wireguardautotunnel.util
import timber.log.Timber
-
sealed class Result {
- class Success(val data: T): Result()
- class Error(val error : Event.Error): Result() {
+ class Success(val data: T) : Result()
+
+ class Error(val error: Event.Error) : Result() {
init {
- when(this.error) {
+ when (this.error) {
is Event.Error.Exception -> Timber.e(this.error.exception)
else -> Timber.e(this.error.message)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/res/drawable/discord.xml b/app/src/main/res/drawable/discord.xml
index df433393..65329151 100644
--- a/app/src/main/res/drawable/discord.xml
+++ b/app/src/main/res/drawable/discord.xml
@@ -3,8 +3,8 @@
android:height="800dp"
android:viewportWidth="256"
android:viewportHeight="256">
-
+
diff --git a/app/src/main/res/drawable/github.xml b/app/src/main/res/drawable/github.xml
index 32f8254e..1f23a47b 100644
--- a/app/src/main/res/drawable/github.xml
+++ b/app/src/main/res/drawable/github.xml
@@ -3,10 +3,10 @@
android:height="800dp"
android:viewportWidth="20"
android:viewportHeight="20">
-
+
diff --git a/app/src/main/res/drawable/shield.xml b/app/src/main/res/drawable/shield.xml
index e49a1f26..ac7ce057 100644
--- a/app/src/main/res/drawable/shield.xml
+++ b/app/src/main/res/drawable/shield.xml
@@ -1,5 +1,10 @@
-
-
+
+
diff --git a/app/src/main/res/drawable/vpn_off.xml b/app/src/main/res/drawable/vpn_off.xml
index d33949b7..08aa68eb 100644
--- a/app/src/main/res/drawable/vpn_off.xml
+++ b/app/src/main/res/drawable/vpn_off.xml
@@ -1,5 +1,10 @@
-
-
+
+
diff --git a/app/src/main/res/drawable/vpn_on.xml b/app/src/main/res/drawable/vpn_on.xml
index 1339fb3b..a35e5358 100644
--- a/app/src/main/res/drawable/vpn_on.xml
+++ b/app/src/main/res/drawable/vpn_on.xml
@@ -1,5 +1,10 @@
-
-
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml b/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml
index cf3108b3..3b3505db 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index 036d09bc..c9ad5f98 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index 036d09bc..c9ad5f98 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7a8b6e16..2a9a7c0f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -164,4 +164,7 @@
Location Services Not Detected
The app is not detecting any location services enabled on your device. Depending on the device, this could cause the untrusted wifi feature to fail to read the wifi name. Would you like to continue anyways?
Auto-tunnel Service
+ Delete tunnel
+ Are you sure you would like to delete this tunnel?
+ Yes
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 9414a536..b703e9da 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,5 +1,6 @@
+
diff --git a/app/src/main/res/xml/shortcuts.xml b/app/src/main/res/xml/shortcuts.xml
index 0fbe1c3d..4d47265f 100644
--- a/app/src/main/res/xml/shortcuts.xml
+++ b/app/src/main/res/xml/shortcuts.xml
@@ -1,31 +1,35 @@
+ android:shortcutShortLabel="@string/vpn_on">
-
+ android:targetClass="com.zaneschepke.wireguardautotunnel.service.shortcut.ShortcutsActivity"
+ android:targetPackage="com.zaneschepke.wireguardautotunnel">
+
+ android:shortcutShortLabel="@string/vpn_off">
-
+ android:targetClass="com.zaneschepke.wireguardautotunnel.service.shortcut.ShortcutsActivity"
+ android:targetPackage="com.zaneschepke.wireguardautotunnel">
+
diff --git a/buildSrc/src/main/kotlin/BuildHelper.kt b/buildSrc/src/main/kotlin/BuildHelper.kt
index 8d413c54..82a5d7ed 100644
--- a/buildSrc/src/main/kotlin/BuildHelper.kt
+++ b/buildSrc/src/main/kotlin/BuildHelper.kt
@@ -26,11 +26,9 @@ object BuildHelper {
}
fun isReleaseBuild(gradle: Gradle): Boolean {
- return (
- gradle.startParameter.taskNames.size > 0 &&
- gradle.startParameter.taskNames[0].contains(
- "Release",
- )
- )
+ return (gradle.startParameter.taskNames.size > 0 &&
+ gradle.startParameter.taskNames[0].contains(
+ "Release",
+ ))
}
}
diff --git a/buildSrc/src/main/kotlin/Constants.kt b/buildSrc/src/main/kotlin/Constants.kt
index 398bc78d..8e33e4e9 100644
--- a/buildSrc/src/main/kotlin/Constants.kt
+++ b/buildSrc/src/main/kotlin/Constants.kt
@@ -1,11 +1,13 @@
object Constants {
- const val VERSION_NAME = "3.3.2"
+ const val VERSION_NAME = "3.3.3"
const val JVM_TARGET = "17"
- const val VERSION_CODE = 33200
+ const val VERSION_CODE = 33300
const val TARGET_SDK = 34
const val MIN_SDK = 26
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
const val APP_NAME = "wgtunnel"
+ const val COMPOSE_COMPILER_EXTENSION_VERSION = "1.5.7"
+
const val STORE_PASS_VAR = "SIGNING_STORE_PASSWORD"
const val KEY_ALIAS_VAR = "SIGNING_KEY_ALIAS"
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index a2dec451..7ab1810e 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -5,13 +5,13 @@ platform :android do
desc "Deploy a beta version to the Google Play"
lane :beta do
gradle(task: "clean bundleGeneralRelease")
- upload_to_play_store(track: 'beta')
+ upload_to_play_store(track: 'beta', skip_upload_apk: true)
end
desc "Deploy a new version to the Google Play"
lane :production do
gradle(task: "clean bundleGeneralRelease")
- upload_to_play_store
+ upload_to_play_store(skip_upload_apk: true)
end
end
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/33300.txt b/fastlane/metadata/android/en-US/changelogs/33300.txt
new file mode 100644
index 00000000..9278b40b
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/33300.txt
@@ -0,0 +1,7 @@
+Enhancements:
+- Added delete tunnel confirmation
+- Added battery background permission
+Fixes:
+- Tunnel disable frozen bug
+- Email to field bug
+- Config edit empty DNS
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index c21f0b38..990e8f13 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -11,7 +11,7 @@ desugar_jdk_libs = "2.0.4"
espressoCore = "3.5.1"
firebase-crashlytics-gradle = "2.9.9"
google-services = "4.4.0"
-hiltAndroid = "2.49"
+hiltAndroid = "2.50"
hiltNavigationCompose = "1.1.0"
junit = "4.13.2"
kotlinx-serialization-json = "1.6.2"
@@ -22,15 +22,14 @@ navigationCompose = "2.7.6"
roomVersion = "2.6.1"
timber = "5.0.1"
tunnel = "1.0.20230706"
-androidGradlePlugin = "8.2.0"
-kotlin="1.9.10"
-ksp="1.9.10-1.0.13"
-composeBom="2023.10.01"
-firebaseBom= "32.7.0"
-compose="1.5.4"
-crashlytics= "18.6.0"
-analytics="21.5.0"
-composeCompiler="1.5.3"
+androidGradlePlugin = "8.2.1"
+kotlin = "1.9.21"
+ksp = "1.9.21-1.0.16"
+composeBom = "2023.10.01"
+firebaseBom = "32.7.0"
+compose = "1.5.4"
+crashlytics = "18.6.0"
+analytics = "21.5.0"
zxingAndroidEmbedded = "4.3.0"
zxingCore = "3.5.2"
@@ -54,13 +53,13 @@ androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomVers
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomVersion" }
#compose
-androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref="composeBom" }
-androidx-compose-ui-test = { module="androidx.compose.ui:ui-test-junit4", version.ref="compose" }
-androidx-compose-ui-tooling = { module="androidx.compose.ui:ui-tooling", version.ref="compose" }
-androidx-compose-manifest = { module="androidx.compose.ui:ui-test-manifest", version.ref="compose" }
-androidx-compose-ui-graphics = { module="androidx.compose.ui:ui-graphics", version.ref="compose" }
-androidx-compose-ui-tooling-preview = { module="androidx.compose.ui:ui-tooling-preview", version.ref="compose" }
-androidx-compose-ui = { module="androidx.compose.ui:ui", version.ref="compose" }
+androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
+androidx-compose-ui-test = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose" }
+androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
+androidx-compose-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose" }
+androidx-compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics", version.ref = "compose" }
+androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
+androidx-compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }
#hilt
androidx-room-testing = { module = "androidx.room:room-testing", version.ref = "roomVersion" }
@@ -84,14 +83,13 @@ lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-com
material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "material-icons-extended" }
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
-
tunnel = { module = "com.wireguard.android:tunnel", version.ref = "tunnel" }
#firebase
google-firebase-crashlytics-ktx = { module = "com.google.firebase:firebase-crashlytics-ktx", version.ref = "crashlytics" }
google-firebase-analytics-ktx = { module = "com.google.firebase:firebase-analytics-ktx", version.ref = "analytics" }
firebase-crashlytics-gradle = { module = "com.google.firebase:firebase-crashlytics-gradle", version.ref = "firebase-crashlytics-gradle" }
-firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom"}
+firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
google-services = { module = "com.google.gms:google-services", version.ref = "google-services" }
zxing-core = { module = "com.google.zxing:core", version.ref = "zxingCore" }
@@ -101,4 +99,4 @@ zxing-android-embedded = { module = "com.journeyapps:zxing-android-embedded", ve
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hiltAndroid" }
-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
+ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 308d6d9f..9daeafac 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -5,6 +5,7 @@ pluginManagement {
gradlePluginPortal()
}
}
+
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
@@ -14,4 +15,5 @@ dependencyResolutionManagement {
}
rootProject.name = "WG Tunnel"
+
include(":app")