ui: move to Jetpack DataStore instead of SharedPrefs

Hopefully PreferencesPreferenceDataStore gets to go away sometime down
the line.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld 2020-09-18 14:03:48 +02:00
parent 3ffe7a5e68
commit d200437813
10 changed files with 304 additions and 101 deletions

View File

@ -11,6 +11,7 @@ buildscript {
coordinatorLayoutVersion = '1.1.0'
coreKtxVersion = '1.3.1'
coroutinesVersion = '1.3.9'
datastoreVersion = '1.0.0-alpha01'
desugarVersion = '1.0.10'
fragmentVersion = '1.3.0-alpha07'
jsr305Version = '3.0.2'

View File

@ -68,6 +68,7 @@ dependencies {
implementation "androidx.fragment:fragment-ktx:$fragmentVersion"
implementation "androidx.preference:preference-ktx:$preferenceVersion"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleRuntimeKtxVersion"
implementation "androidx.datastore:datastore-preferences:$datastoreVersion"
implementation "com.github.material-components:material-components-android:$materialComponentsVersion"
implementation "com.journeyapps:zxing-android-embedded:$zxingEmbeddedVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"

View File

@ -6,15 +6,15 @@ package com.wireguard.android
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.os.Build
import android.os.StrictMode
import android.os.StrictMode.ThreadPolicy
import android.os.StrictMode.VmPolicy
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.PreferenceManager
import androidx.datastore.DataStore
import androidx.datastore.preferences.Preferences
import androidx.datastore.preferences.createDataStore
import com.wireguard.android.backend.Backend
import com.wireguard.android.backend.GoBackend
import com.wireguard.android.backend.WgQuickBackend
@ -23,22 +23,27 @@ import com.wireguard.android.model.TunnelManager
import com.wireguard.android.util.ModuleLoader
import com.wireguard.android.util.RootShell
import com.wireguard.android.util.ToolsInstaller
import com.wireguard.android.util.UserKnobs
import com.wireguard.android.util.applicationScope
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import java.lang.ref.WeakReference
import java.util.Locale
class Application : android.app.Application(), OnSharedPreferenceChangeListener {
class Application : android.app.Application() {
private val futureBackend = CompletableDeferred<Backend>()
private val coroutineScope = CoroutineScope(Job() + Dispatchers.Main.immediate)
private var backend: Backend? = null
private lateinit var moduleLoader: ModuleLoader
private lateinit var rootShell: RootShell
private lateinit var sharedPreferences: SharedPreferences
private lateinit var preferencesDataStore: DataStore<Preferences>
private lateinit var toolsInstaller: ToolsInstaller
private lateinit var tunnelManager: TunnelManager
@ -58,7 +63,7 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener
}
}
private fun determineBackend(): Backend {
private suspend fun determineBackend(): Backend {
var backend: Backend? = null
var didStartRootShell = false
if (!ModuleLoader.isModuleLoaded() && moduleLoader.moduleMightExist()) {
@ -69,19 +74,22 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener
} catch (ignored: Exception) {
}
}
if (!sharedPreferences.getBoolean("disable_kernel_module", false) && ModuleLoader.isModuleLoaded()) {
if (!UserKnobs.disableKernelModule.first() && ModuleLoader.isModuleLoaded()) {
try {
if (!didStartRootShell)
rootShell.start()
val wgQuickBackend = WgQuickBackend(applicationContext, rootShell, toolsInstaller)
wgQuickBackend.setMultipleTunnels(sharedPreferences.getBoolean("multiple_tunnels", false))
wgQuickBackend.setMultipleTunnels(UserKnobs.multipleTunnels.first())
backend = wgQuickBackend
UserKnobs.multipleTunnels.onEach {
wgQuickBackend.setMultipleTunnels(it)
}.launchIn(coroutineScope)
} catch (ignored: Exception) {
}
}
if (backend == null) {
backend = GoBackend(applicationContext)
GoBackend.setAlwaysOnCallback { get().tunnelManager.restoreState(true) }
GoBackend.setAlwaysOnCallback { get().applicationScope.launch { get().tunnelManager.restoreState(true) } }
}
return backend
}
@ -92,10 +100,12 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener
rootShell = RootShell(applicationContext)
toolsInstaller = ToolsInstaller(applicationContext, rootShell)
moduleLoader = ModuleLoader(applicationContext, rootShell, USER_AGENT)
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
preferencesDataStore = applicationContext.createDataStore(name = "settings")
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
AppCompatDelegate.setDefaultNightMode(
if (sharedPreferences.getBoolean("dark_theme", false)) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO)
coroutineScope.launch {
AppCompatDelegate.setDefaultNightMode(
if (UserKnobs.darkTheme.first()) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO)
}
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
@ -109,16 +119,9 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener
Log.e(TAG, Log.getStackTraceString(e))
}
}
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
if ("multiple_tunnels" == key && backend != null && backend is WgQuickBackend)
(backend as WgQuickBackend).setMultipleTunnels(sharedPreferences.getBoolean(key, false))
}
override fun onTerminate() {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
coroutineScope.cancel()
super.onTerminate()
}
@ -143,7 +146,7 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener
fun getRootShell() = get().rootShell
@JvmStatic
fun getSharedPreferences() = get().sharedPreferences
fun getPreferencesDataStore() = get().preferencesDataStore
@JvmStatic
fun getToolsInstaller() = get().toolsInstaller

View File

@ -14,6 +14,7 @@ import androidx.preference.PreferenceFragmentCompat
import com.wireguard.android.Application
import com.wireguard.android.R
import com.wireguard.android.backend.WgQuickBackend
import com.wireguard.android.preference.PreferencesPreferenceDataStore
import com.wireguard.android.util.AdminKnobs
import com.wireguard.android.util.ModuleLoader
import kotlinx.coroutines.Dispatchers
@ -43,6 +44,7 @@ class SettingsActivity : ThemeChangeAwareActivity() {
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, key: String?) {
preferenceManager.preferenceDataStore = PreferencesPreferenceDataStore(lifecycleScope, Application.getPreferencesDataStore())
addPreferencesFromResource(R.xml.preferences)
preferenceScreen.initialExpandedChildrenCount = 4
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {

View File

@ -4,39 +4,30 @@
*/
package com.wireguard.android.activity
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import com.wireguard.android.Application
import androidx.lifecycle.lifecycleScope
import com.wireguard.android.util.UserKnobs
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
abstract class ThemeChangeAwareActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
abstract class ThemeChangeAwareActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
Application.getSharedPreferences().registerOnSharedPreferenceChangeListener(this)
}
}
override fun onDestroy() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
Application.getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this)
}
super.onDestroy()
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
when (key) {
"dark_theme" -> {
AppCompatDelegate.setDefaultNightMode(if (sharedPreferences.getBoolean(key, false)) {
UserKnobs.darkTheme.onEach {
val newMode = if (it) {
AppCompatDelegate.MODE_NIGHT_YES
} else {
AppCompatDelegate.MODE_NIGHT_NO
})
recreate()
}
}
if (AppCompatDelegate.getDefaultNightMode() != newMode) {
AppCompatDelegate.setDefaultNightMode(newMode)
recreate()
}
}.launchIn(lifecycleScope)
}
}
}

View File

@ -4,7 +4,6 @@
*/
package com.wireguard.android.model
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@ -14,7 +13,6 @@ import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import com.wireguard.android.Application.Companion.get
import com.wireguard.android.Application.Companion.getBackend
import com.wireguard.android.Application.Companion.getSharedPreferences
import com.wireguard.android.Application.Companion.getTunnelManager
import com.wireguard.android.BR
import com.wireguard.android.R
@ -22,6 +20,7 @@ import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Tunnel
import com.wireguard.android.configStore.ConfigStore
import com.wireguard.android.databinding.ObservableSortedKeyedArrayList
import com.wireguard.android.util.UserKnobs
import com.wireguard.android.util.applicationScope
import com.wireguard.config.Config
import kotlinx.coroutines.CompletableDeferred
@ -29,6 +28,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -84,16 +84,12 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
}
@get:Bindable
@SuppressLint("ApplySharedPref")
var lastUsedTunnel: ObservableTunnel? = null
private set(value) {
if (value == field) return
field = value
notifyPropertyChanged(BR.lastUsedTunnel)
if (value != null)
getSharedPreferences().edit().putString(KEY_LAST_USED_TUNNEL, value.name).commit()
else
getSharedPreferences().edit().remove(KEY_LAST_USED_TUNNEL).commit()
applicationScope.launch { UserKnobs.setLastUsedTunnel(value?.name) }
}
suspend fun getTunnelConfig(tunnel: ObservableTunnel): Config = withContext(Dispatchers.Main.immediate) {
@ -113,12 +109,14 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
private fun onTunnelsLoaded(present: Iterable<String>, running: Collection<String>) {
for (name in present)
addToList(name, null, if (running.contains(name)) Tunnel.State.UP else Tunnel.State.DOWN)
val lastUsedName = getSharedPreferences().getString(KEY_LAST_USED_TUNNEL, null)
if (lastUsedName != null)
lastUsedTunnel = tunnelMap[lastUsedName]
haveLoaded = true
restoreState(true)
tunnels.complete(tunnelMap)
applicationScope.launch {
val lastUsedName = UserKnobs.lastUsedTunnel.first()
if (lastUsedName != null)
lastUsedTunnel = tunnelMap[lastUsedName]
haveLoaded = true
restoreState(true)
tunnels.complete(tunnelMap)
}
}
private fun refreshTunnelStates() {
@ -133,26 +131,22 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
}
}
fun restoreState(force: Boolean) {
if (!haveLoaded || (!force && !getSharedPreferences().getBoolean(KEY_RESTORE_ON_BOOT, false)))
suspend fun restoreState(force: Boolean) {
if (!haveLoaded || (!force && !UserKnobs.restoreOnBoot.first()))
return
val previouslyRunning = getSharedPreferences().getStringSet(KEY_RUNNING_TUNNELS, null)
?: return
val previouslyRunning = UserKnobs.runningTunnels.first()
if (previouslyRunning.isEmpty()) return
applicationScope.launch {
withContext(Dispatchers.IO) {
try {
tunnelMap.filter { previouslyRunning.contains(it.name) }.map { async(SupervisorJob()) { setTunnelState(it, Tunnel.State.UP) } }.awaitAll()
} catch (e: Throwable) {
Log.e(TAG, Log.getStackTraceString(e))
}
withContext(Dispatchers.IO) {
try {
tunnelMap.filter { previouslyRunning.contains(it.name) }.map { async(Dispatchers.IO + SupervisorJob()) { setTunnelState(it, Tunnel.State.UP) } }.awaitAll()
} catch (e: Throwable) {
Log.e(TAG, Log.getStackTraceString(e))
}
}
}
@SuppressLint("ApplySharedPref")
fun saveState() {
getSharedPreferences().edit().putStringSet(KEY_RUNNING_TUNNELS, tunnelMap.filter { it.state == Tunnel.State.UP }.map { it.name }.toSet()).commit()
suspend fun saveState() {
UserKnobs.setRunningTunnels(tunnelMap.filter { it.state == Tunnel.State.UP }.map { it.name }.toSet())
}
suspend fun setTunnelConfig(tunnel: ObservableTunnel, config: Config): Config = withContext(Dispatchers.Main.immediate) {
@ -216,23 +210,23 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
class IntentReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
val manager = getTunnelManager()
if (intent == null) return
val action = intent.action ?: return
if ("com.wireguard.android.action.REFRESH_TUNNEL_STATES" == action) {
manager.refreshTunnelStates()
return
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || !getSharedPreferences().getBoolean("allow_remote_control_intents", false))
return
val state: Tunnel.State
state = when (action) {
"com.wireguard.android.action.SET_TUNNEL_UP" -> Tunnel.State.UP
"com.wireguard.android.action.SET_TUNNEL_DOWN" -> Tunnel.State.DOWN
else -> return
}
val tunnelName = intent.getStringExtra("tunnel") ?: return
applicationScope.launch {
val manager = getTunnelManager()
if (intent == null) return@launch
val action = intent.action ?: return@launch
if ("com.wireguard.android.action.REFRESH_TUNNEL_STATES" == action) {
manager.refreshTunnelStates()
return@launch
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || !UserKnobs.allowRemoteControlIntents.first())
return@launch
val state: Tunnel.State
state = when (action) {
"com.wireguard.android.action.SET_TUNNEL_UP" -> Tunnel.State.UP
"com.wireguard.android.action.SET_TUNNEL_DOWN" -> Tunnel.State.DOWN
else -> return@launch
}
val tunnelName = intent.getStringExtra("tunnel") ?: return@launch
val tunnels = manager.getTunnels()
val tunnel = tunnels[tunnelName] ?: return@launch
manager.setTunnelState(tunnel, state)
@ -250,9 +244,5 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
companion object {
private const val TAG = "WireGuard/TunnelManager"
private const val KEY_LAST_USED_TUNNEL = "last_used_tunnel"
private const val KEY_RESTORE_ON_BOOT = "restore_on_boot"
private const val KEY_RUNNING_TUNNELS = "enabled_configs"
}
}

View File

@ -4,7 +4,6 @@
*/
package com.wireguard.android.preference
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.util.AttributeSet
@ -15,6 +14,7 @@ import com.wireguard.android.R
import com.wireguard.android.activity.SettingsActivity
import com.wireguard.android.backend.Tunnel
import com.wireguard.android.backend.WgQuickBackend
import com.wireguard.android.util.UserKnobs
import com.wireguard.android.util.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
@ -38,16 +38,15 @@ class KernelModuleDisablerPreference(context: Context, attrs: AttributeSet?) : P
override fun getTitle() = if (state == State.UNKNOWN) "" else context.getString(state.titleResourceId)
@SuppressLint("ApplySharedPref")
override fun onClick() {
if (state == State.DISABLED) {
setState(State.ENABLING)
Application.getSharedPreferences().edit().putBoolean("disable_kernel_module", false).commit()
} else if (state == State.ENABLED) {
setState(State.DISABLING)
Application.getSharedPreferences().edit().putBoolean("disable_kernel_module", true).commit()
}
lifecycleScope.launch {
if (state == State.DISABLED) {
setState(State.ENABLING)
UserKnobs.setDisableKernelModule(false)
} else if (state == State.ENABLED) {
setState(State.DISABLING)
UserKnobs.setDisableKernelModule(true)
}
val observableTunnels = Application.getTunnelManager().getTunnels()
val downings = observableTunnels.map { async(SupervisorJob()) { it.setStateAsync(Tunnel.State.DOWN) } }
try {

View File

@ -4,7 +4,6 @@
*/
package com.wireguard.android.preference
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.system.OsConstants
@ -15,6 +14,7 @@ import com.wireguard.android.Application
import com.wireguard.android.R
import com.wireguard.android.activity.SettingsActivity
import com.wireguard.android.util.ErrorMessages
import com.wireguard.android.util.UserKnobs
import com.wireguard.android.util.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -27,7 +27,6 @@ class ModuleDownloaderPreference(context: Context, attrs: AttributeSet?) : Prefe
override fun getTitle() = context.getString(R.string.module_installer_title)
@SuppressLint("ApplySharedPref")
override fun onClick() {
setState(State.WORKING)
lifecycleScope.launch {
@ -36,7 +35,7 @@ class ModuleDownloaderPreference(context: Context, attrs: AttributeSet?) : Prefe
OsConstants.ENOENT -> setState(State.NOTFOUND)
OsConstants.EXIT_SUCCESS -> {
setState(State.SUCCESS)
Application.getSharedPreferences().edit().remove("disable_kernel_module").commit()
UserKnobs.setDisableKernelModule(null)
withContext(Dispatchers.IO) {
val restartIntent = Intent(context, SettingsActivity::class.java)
restartIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)

View File

@ -0,0 +1,132 @@
/*
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.preference
import androidx.datastore.DataStore
import androidx.datastore.preferences.Preferences
import androidx.datastore.preferences.edit
import androidx.datastore.preferences.preferencesKey
import androidx.datastore.preferences.preferencesSetKey
import androidx.datastore.preferences.remove
import androidx.preference.PreferenceDataStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class PreferencesPreferenceDataStore(private val coroutineScope: CoroutineScope, private val dataStore: DataStore<Preferences>) : PreferenceDataStore() {
override fun putString(key: String?, value: String?) {
if (key == null) return
val pk = preferencesKey<String>(key)
coroutineScope.launch {
dataStore.edit {
if (value == null) it.remove(pk)
else it[pk] = value
}
}
}
override fun putStringSet(key: String?, values: Set<String?>?) {
if (key == null) return
val pk = preferencesSetKey<String>(key)
val filteredValues = values?.filterNotNull()?.toSet()
coroutineScope.launch {
dataStore.edit {
if (filteredValues == null || filteredValues.isEmpty()) it.remove(pk)
else it[pk] = filteredValues
}
}
}
override fun putInt(key: String?, value: Int) {
if (key == null) return
val pk = preferencesKey<Int>(key)
coroutineScope.launch {
dataStore.edit {
it[pk] = value
}
}
}
override fun putLong(key: String?, value: Long) {
if (key == null) return
val pk = preferencesKey<Long>(key)
coroutineScope.launch {
dataStore.edit {
it[pk] = value
}
}
}
override fun putFloat(key: String?, value: Float) {
if (key == null) return
val pk = preferencesKey<Float>(key)
coroutineScope.launch {
dataStore.edit {
it[pk] = value
}
}
}
override fun putBoolean(key: String?, value: Boolean) {
if (key == null) return
val pk = preferencesKey<Boolean>(key)
coroutineScope.launch {
dataStore.edit {
it[pk] = value
}
}
}
override fun getString(key: String?, defValue: String?): String? {
if (key == null) return defValue
val pk = preferencesKey<String>(key)
return runBlocking {
dataStore.data.map { it[pk] ?: defValue }.first()
}
}
override fun getStringSet(key: String?, defValues: Set<String?>?): Set<String?>? {
if (key == null) return defValues
val pk = preferencesSetKey<String>(key)
return runBlocking {
dataStore.data.map { it[pk] ?: defValues }.first()
}
}
override fun getInt(key: String?, defValue: Int): Int {
if (key == null) return defValue
val pk = preferencesKey<Int>(key)
return runBlocking {
dataStore.data.map { it[pk] ?: defValue }.first()
}
}
override fun getLong(key: String?, defValue: Long): Long {
if (key == null) return defValue
val pk = preferencesKey<Long>(key)
return runBlocking {
dataStore.data.map { it[pk] ?: defValue }.first()
}
}
override fun getFloat(key: String?, defValue: Float): Float {
if (key == null) return defValue
val pk = preferencesKey<Float>(key)
return runBlocking {
dataStore.data.map { it[pk] ?: defValue }.first()
}
}
override fun getBoolean(key: String?, defValue: Boolean): Boolean {
if (key == null) return defValue
val pk = preferencesKey<Boolean>(key)
return runBlocking {
dataStore.data.map { it[pk] ?: defValue }.first()
}
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.util
import androidx.datastore.preferences.edit
import androidx.datastore.preferences.preferencesKey
import androidx.datastore.preferences.preferencesSetKey
import androidx.datastore.preferences.remove
import com.wireguard.android.Application
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
object UserKnobs {
private val DISABLE_KERNEL_MODULE = preferencesKey<Boolean>("disable_kernel_module")
val disableKernelModule: Flow<Boolean>
get() = Application.getPreferencesDataStore().data.map {
it[DISABLE_KERNEL_MODULE] ?: false
}
suspend fun setDisableKernelModule(disable: Boolean?) {
Application.getPreferencesDataStore().edit {
if (disable == null)
it.remove(DISABLE_KERNEL_MODULE)
else
it[DISABLE_KERNEL_MODULE] = disable
}
}
private val MULTIPLE_TUNNELS = preferencesKey<Boolean>("multiple_tunnels")
val multipleTunnels: Flow<Boolean>
get() = Application.getPreferencesDataStore().data.map {
it[MULTIPLE_TUNNELS] ?: false
}
private val DARK_THEME = preferencesKey<Boolean>("dark_theme")
val darkTheme: Flow<Boolean>
get() = Application.getPreferencesDataStore().data.map {
it[DARK_THEME] ?: false
}
private val ALLOW_REMOTE_CONTROL_INTENTS = preferencesKey<Boolean>("allow_remote_control_intents")
val allowRemoteControlIntents: Flow<Boolean>
get() = Application.getPreferencesDataStore().data.map {
it[ALLOW_REMOTE_CONTROL_INTENTS] ?: false
}
private val RESTORE_ON_BOOT = preferencesKey<Boolean>("restore_on_boot")
val restoreOnBoot: Flow<Boolean>
get() = Application.getPreferencesDataStore().data.map {
it[RESTORE_ON_BOOT] ?: false
}
private val LAST_USED_TUNNEL = preferencesKey<String>("last_used_tunnel")
val lastUsedTunnel: Flow<String?>
get() = Application.getPreferencesDataStore().data.map {
it[LAST_USED_TUNNEL]
}
suspend fun setLastUsedTunnel(lastUsedTunnel: String?) {
Application.getPreferencesDataStore().edit {
if (lastUsedTunnel == null)
it.remove(LAST_USED_TUNNEL)
else
it[LAST_USED_TUNNEL] = lastUsedTunnel
}
}
private val RUNNING_TUNNELS = preferencesSetKey<String>("enabled_configs")
val runningTunnels: Flow<Set<String>>
get() = Application.getPreferencesDataStore().data.map {
it[RUNNING_TUNNELS] ?: emptySet()
}
suspend fun setRunningTunnels(runningTunnels: Set<String>) {
Application.getPreferencesDataStore().edit {
if (runningTunnels.isEmpty())
it.remove(RUNNING_TUNNELS)
else
it[RUNNING_TUNNELS] = runningTunnels
}
}
}