feat(privacy): persist I2pSettings and preferredClearnetTransport
Mirrors the existing Tor persistence stack so I2P settings + the global clearnet-transport preference survive process restart. New on Android: - I2pSettingsFlow — StateFlow-per-field shape matching TorSettingsFlow, including the propertyWatchFlow that the prefs class listens to. - I2pSharedPreferences — DataStore-backed load/save with `i2p.*` pref keys, debounced-save wired to propertyWatchFlow. - PrivacySharedPreferences — single MutableStateFlow<PrivacyTransport> for preferredClearnetTransport, persisted under `privacy.preferredClearnetTransport`. Kept separate from Tor/I2p prefs because the decision spans both transports. AppModules: - Parallel-load i2pPrefs and privacyPrefs alongside torPrefs in async/await pairs so cold-start blocking time stays at ~max() not sum(). - Lazy accessors mirror torPrefs/uiPrefs. No routing wired yet — RoleBasedHttpClientBuilder still consumes torPrefs only. That swap is the next chunk.
This commit is contained in:
@@ -24,6 +24,7 @@ import android.content.Context
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import coil3.disk.DiskCache
|
||||
import coil3.memory.MemoryCache
|
||||
import com.vitorpamplona.amethyst.commons.i2p.I2pSettings
|
||||
import com.vitorpamplona.amethyst.commons.model.NoteState
|
||||
import com.vitorpamplona.amethyst.commons.robohash.CachedRobohash
|
||||
import com.vitorpamplona.amethyst.commons.tor.TorSettings
|
||||
@@ -34,8 +35,10 @@ import com.vitorpamplona.amethyst.model.accountsCache.AccountCacheState
|
||||
import com.vitorpamplona.amethyst.model.nip03Timestamp.IncomingOtsEventVerifier
|
||||
import com.vitorpamplona.amethyst.model.nip03Timestamp.TorAwareOkHttpOtsResolverBuilder
|
||||
import com.vitorpamplona.amethyst.model.nip11RelayInfo.Nip11CachedRetriever
|
||||
import com.vitorpamplona.amethyst.model.preferences.I2pSharedPreferences
|
||||
import com.vitorpamplona.amethyst.model.preferences.NamecoinSharedPreferences
|
||||
import com.vitorpamplona.amethyst.model.preferences.OtsSharedPreferences
|
||||
import com.vitorpamplona.amethyst.model.preferences.PrivacySharedPreferences
|
||||
import com.vitorpamplona.amethyst.model.preferences.TorSharedPreferences
|
||||
import com.vitorpamplona.amethyst.model.preferences.UiSharedPreferences
|
||||
import com.vitorpamplona.amethyst.model.privacyOptions.RoleBasedHttpClientBuilder
|
||||
@@ -149,6 +152,18 @@ class AppModules(
|
||||
TorSharedPreferences(prefs, appContext, applicationIOScope)
|
||||
}
|
||||
|
||||
private val i2pPrefsDeferred =
|
||||
applicationIOScope.async {
|
||||
val prefs = I2pSharedPreferences.i2pPreferences(appContext) ?: I2pSettings()
|
||||
I2pSharedPreferences(prefs, appContext, applicationIOScope)
|
||||
}
|
||||
|
||||
private val privacyPrefsDeferred =
|
||||
applicationIOScope.async {
|
||||
val initial = PrivacySharedPreferences.preferredClearnetTransport(appContext)
|
||||
PrivacySharedPreferences(initial, appContext, applicationIOScope)
|
||||
}
|
||||
|
||||
// Blocking load of UI Preferences to avoid theme/language blinking
|
||||
val uiPrefs by lazy {
|
||||
Log.d("AppModules", "UiSharedPreferences Init")
|
||||
@@ -161,6 +176,21 @@ class AppModules(
|
||||
runBlocking { torPrefsDeferred.await() }
|
||||
}
|
||||
|
||||
// Blocking load of I2P settings — paired with torPrefs since both feed the
|
||||
// privacy transport routing decision and we don't want clearnet to leak before
|
||||
// either is restored.
|
||||
val i2pPrefs by lazy {
|
||||
Log.d("AppModules", "I2pSharedPreferences Init")
|
||||
runBlocking { i2pPrefsDeferred.await() }
|
||||
}
|
||||
|
||||
// Picks which transport (if any) carries clearnet traffic. Loaded blocking for
|
||||
// the same reason as torPrefs/i2pPrefs.
|
||||
val privacyPrefs by lazy {
|
||||
Log.d("AppModules", "PrivacySharedPreferences Init")
|
||||
runBlocking { privacyPrefsDeferred.await() }
|
||||
}
|
||||
|
||||
// Namecoin ElectrumX server preferences (global, like Tor settings)
|
||||
val namecoinPrefs by lazy {
|
||||
Log.d("AppModules", "NamecoinSharedPreferences Init")
|
||||
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model.preferences
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import com.vitorpamplona.amethyst.commons.i2p.I2pSettings
|
||||
import com.vitorpamplona.amethyst.commons.i2p.I2pType
|
||||
import com.vitorpamplona.amethyst.ui.i2p.I2pSettingsFlow
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
@Stable
|
||||
class I2pSharedPreferences(
|
||||
prefs: I2pSettings,
|
||||
val context: Context,
|
||||
val scope: CoroutineScope,
|
||||
) {
|
||||
val value = I2pSettingsFlow.build(prefs)
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
val saving =
|
||||
value.propertyWatchFlow
|
||||
.debounce(1000)
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
save(it, context)
|
||||
}.flowOn(Dispatchers.IO)
|
||||
.stateIn(
|
||||
scope,
|
||||
SharingStarted.Eagerly,
|
||||
value.toSettings(),
|
||||
)
|
||||
|
||||
companion object {
|
||||
val I2P_TYPE_KEY = stringPreferencesKey("i2p.i2pType")
|
||||
val EXTERNAL_SOCKS_PORT_KEY = intPreferencesKey("i2p.externalSocksPort")
|
||||
val I2P_RELAYS_VIA_I2P_KEY = booleanPreferencesKey("i2p.i2pRelaysViaI2p")
|
||||
val DM_RELAYS_VIA_I2P_KEY = booleanPreferencesKey("i2p.dmRelaysViaI2p")
|
||||
val NEW_RELAYS_VIA_I2P_KEY = booleanPreferencesKey("i2p.newRelaysViaI2p")
|
||||
val TRUSTED_RELAYS_VIA_I2P_KEY = booleanPreferencesKey("i2p.trustedRelaysViaI2p")
|
||||
val URL_PREVIEWS_VIA_I2P_KEY = booleanPreferencesKey("i2p.urlPreviewsViaI2p")
|
||||
val PROFILE_PICS_VIA_I2P_KEY = booleanPreferencesKey("i2p.profilePicsViaI2p")
|
||||
val IMAGES_VIA_I2P_KEY = booleanPreferencesKey("i2p.imagesViaI2p")
|
||||
val VIDEOS_VIA_I2P_KEY = booleanPreferencesKey("i2p.videosViaI2p")
|
||||
val MONEY_OPERATIONS_VIA_I2P_KEY = booleanPreferencesKey("i2p.moneyOperationsViaI2p")
|
||||
val NIP05_VERIFICATIONS_VIA_I2P_KEY = booleanPreferencesKey("i2p.nip05VerificationsViaI2p")
|
||||
val MEDIA_UPLOADS_VIA_I2P_KEY = booleanPreferencesKey("i2p.mediaUploadsViaI2p")
|
||||
|
||||
suspend fun i2pPreferences(context: Context): I2pSettings? =
|
||||
try {
|
||||
val preferences = context.sharedPreferencesDataStore.data.first()
|
||||
I2pSettings(
|
||||
i2pType = preferences[I2P_TYPE_KEY]?.let { I2pType.valueOf(it) } ?: I2pType.OFF,
|
||||
externalSocksPort = preferences[EXTERNAL_SOCKS_PORT_KEY] ?: 4447,
|
||||
i2pRelaysViaI2p = preferences[I2P_RELAYS_VIA_I2P_KEY] ?: true,
|
||||
dmRelaysViaI2p = preferences[DM_RELAYS_VIA_I2P_KEY] ?: false,
|
||||
newRelaysViaI2p = preferences[NEW_RELAYS_VIA_I2P_KEY] ?: false,
|
||||
trustedRelaysViaI2p = preferences[TRUSTED_RELAYS_VIA_I2P_KEY] ?: false,
|
||||
urlPreviewsViaI2p = preferences[URL_PREVIEWS_VIA_I2P_KEY] ?: false,
|
||||
profilePicsViaI2p = preferences[PROFILE_PICS_VIA_I2P_KEY] ?: false,
|
||||
imagesViaI2p = preferences[IMAGES_VIA_I2P_KEY] ?: false,
|
||||
videosViaI2p = preferences[VIDEOS_VIA_I2P_KEY] ?: false,
|
||||
moneyOperationsViaI2p = preferences[MONEY_OPERATIONS_VIA_I2P_KEY] ?: false,
|
||||
nip05VerificationsViaI2p = preferences[NIP05_VERIFICATIONS_VIA_I2P_KEY] ?: false,
|
||||
mediaUploadsViaI2p = preferences[MEDIA_UPLOADS_VIA_I2P_KEY] ?: false,
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("SharedPreferences") { "Error reading I2P DataStore preferences: ${e.message}" }
|
||||
null
|
||||
}
|
||||
|
||||
suspend fun save(
|
||||
settings: I2pSettings,
|
||||
context: Context,
|
||||
) {
|
||||
try {
|
||||
context.sharedPreferencesDataStore.edit { preferences ->
|
||||
preferences[I2P_TYPE_KEY] = settings.i2pType.name
|
||||
preferences[EXTERNAL_SOCKS_PORT_KEY] = settings.externalSocksPort
|
||||
preferences[I2P_RELAYS_VIA_I2P_KEY] = settings.i2pRelaysViaI2p
|
||||
preferences[DM_RELAYS_VIA_I2P_KEY] = settings.dmRelaysViaI2p
|
||||
preferences[NEW_RELAYS_VIA_I2P_KEY] = settings.newRelaysViaI2p
|
||||
preferences[TRUSTED_RELAYS_VIA_I2P_KEY] = settings.trustedRelaysViaI2p
|
||||
preferences[URL_PREVIEWS_VIA_I2P_KEY] = settings.urlPreviewsViaI2p
|
||||
preferences[PROFILE_PICS_VIA_I2P_KEY] = settings.profilePicsViaI2p
|
||||
preferences[IMAGES_VIA_I2P_KEY] = settings.imagesViaI2p
|
||||
preferences[VIDEOS_VIA_I2P_KEY] = settings.videosViaI2p
|
||||
preferences[MONEY_OPERATIONS_VIA_I2P_KEY] = settings.moneyOperationsViaI2p
|
||||
preferences[NIP05_VERIFICATIONS_VIA_I2P_KEY] = settings.nip05VerificationsViaI2p
|
||||
preferences[MEDIA_UPLOADS_VIA_I2P_KEY] = settings.mediaUploadsViaI2p
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("SharedPreferences") { "Error saving I2P DataStore preferences: ${e.message}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model.preferences
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import com.vitorpamplona.amethyst.commons.privacy.PrivacyTransport
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
// Global pick of which privacy transport carries clearnet traffic. Lives apart
|
||||
// from TorSharedPreferences / I2pSharedPreferences because it spans both — each
|
||||
// transport owns its own daemon-enable + per-feature toggles; this owns the
|
||||
// "which one is in charge of clearnet" decision.
|
||||
@Stable
|
||||
class PrivacySharedPreferences(
|
||||
initial: PrivacyTransport,
|
||||
val context: Context,
|
||||
val scope: CoroutineScope,
|
||||
) {
|
||||
val preferredClearnetTransport: MutableStateFlow<PrivacyTransport> = MutableStateFlow(initial)
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
val saving =
|
||||
preferredClearnetTransport
|
||||
.debounce(1000)
|
||||
.distinctUntilChanged()
|
||||
.onEach { save(it, context) }
|
||||
.flowOn(Dispatchers.IO)
|
||||
.stateIn(scope, SharingStarted.Eagerly, initial)
|
||||
|
||||
companion object {
|
||||
val PREFERRED_CLEARNET_TRANSPORT_KEY = stringPreferencesKey("privacy.preferredClearnetTransport")
|
||||
|
||||
suspend fun preferredClearnetTransport(context: Context): PrivacyTransport =
|
||||
try {
|
||||
val name = context.sharedPreferencesDataStore.data.first()[PREFERRED_CLEARNET_TRANSPORT_KEY]
|
||||
name?.let { runCatching { PrivacyTransport.valueOf(it) }.getOrNull() } ?: PrivacyTransport.DIRECT
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("SharedPreferences") { "Error reading preferredClearnetTransport: ${e.message}" }
|
||||
PrivacyTransport.DIRECT
|
||||
}
|
||||
|
||||
suspend fun save(
|
||||
transport: PrivacyTransport,
|
||||
context: Context,
|
||||
) {
|
||||
try {
|
||||
context.sharedPreferencesDataStore.edit { prefs ->
|
||||
prefs[PREFERRED_CLEARNET_TRANSPORT_KEY] = transport.name
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("SharedPreferences") { "Error saving preferredClearnetTransport: ${e.message}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.i2p
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.commons.i2p.I2pSettings
|
||||
import com.vitorpamplona.amethyst.commons.i2p.I2pType
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
@Stable
|
||||
class I2pSettingsFlow(
|
||||
val i2pType: MutableStateFlow<I2pType> = MutableStateFlow(I2pType.OFF),
|
||||
val externalSocksPort: MutableStateFlow<Int> = MutableStateFlow(4447),
|
||||
val i2pRelaysViaI2p: MutableStateFlow<Boolean> = MutableStateFlow(true),
|
||||
val dmRelaysViaI2p: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
||||
val newRelaysViaI2p: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
||||
val trustedRelaysViaI2p: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
||||
val urlPreviewsViaI2p: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
||||
val profilePicsViaI2p: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
||||
val imagesViaI2p: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
||||
val videosViaI2p: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
||||
val moneyOperationsViaI2p: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
||||
val nip05VerificationsViaI2p: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
||||
val mediaUploadsViaI2p: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
||||
) {
|
||||
val propertyWatchFlow =
|
||||
combine<Any, I2pSettings>(
|
||||
listOf(
|
||||
i2pType,
|
||||
externalSocksPort,
|
||||
i2pRelaysViaI2p,
|
||||
dmRelaysViaI2p,
|
||||
newRelaysViaI2p,
|
||||
trustedRelaysViaI2p,
|
||||
urlPreviewsViaI2p,
|
||||
profilePicsViaI2p,
|
||||
imagesViaI2p,
|
||||
videosViaI2p,
|
||||
moneyOperationsViaI2p,
|
||||
nip05VerificationsViaI2p,
|
||||
mediaUploadsViaI2p,
|
||||
),
|
||||
) { flows ->
|
||||
I2pSettings(
|
||||
flows[0] as I2pType,
|
||||
flows[1] as Int,
|
||||
flows[2] as Boolean,
|
||||
flows[3] as Boolean,
|
||||
flows[4] as Boolean,
|
||||
flows[5] as Boolean,
|
||||
flows[6] as Boolean,
|
||||
flows[7] as Boolean,
|
||||
flows[8] as Boolean,
|
||||
flows[9] as Boolean,
|
||||
flows[10] as Boolean,
|
||||
flows[11] as Boolean,
|
||||
flows[12] as Boolean,
|
||||
)
|
||||
}
|
||||
|
||||
fun toSettings(): I2pSettings =
|
||||
I2pSettings(
|
||||
i2pType.value,
|
||||
externalSocksPort.value,
|
||||
i2pRelaysViaI2p.value,
|
||||
dmRelaysViaI2p.value,
|
||||
newRelaysViaI2p.value,
|
||||
trustedRelaysViaI2p.value,
|
||||
urlPreviewsViaI2p.value,
|
||||
profilePicsViaI2p.value,
|
||||
imagesViaI2p.value,
|
||||
videosViaI2p.value,
|
||||
moneyOperationsViaI2p.value,
|
||||
nip05VerificationsViaI2p.value,
|
||||
mediaUploadsViaI2p.value,
|
||||
)
|
||||
|
||||
fun update(settings: I2pSettings): Boolean {
|
||||
var any = false
|
||||
|
||||
if (i2pType.value != settings.i2pType) {
|
||||
i2pType.tryEmit(settings.i2pType)
|
||||
any = true
|
||||
}
|
||||
if (externalSocksPort.value != settings.externalSocksPort) {
|
||||
externalSocksPort.tryEmit(settings.externalSocksPort)
|
||||
any = true
|
||||
}
|
||||
if (i2pRelaysViaI2p.value != settings.i2pRelaysViaI2p) {
|
||||
i2pRelaysViaI2p.tryEmit(settings.i2pRelaysViaI2p)
|
||||
any = true
|
||||
}
|
||||
if (dmRelaysViaI2p.value != settings.dmRelaysViaI2p) {
|
||||
dmRelaysViaI2p.tryEmit(settings.dmRelaysViaI2p)
|
||||
any = true
|
||||
}
|
||||
if (newRelaysViaI2p.value != settings.newRelaysViaI2p) {
|
||||
newRelaysViaI2p.tryEmit(settings.newRelaysViaI2p)
|
||||
any = true
|
||||
}
|
||||
if (trustedRelaysViaI2p.value != settings.trustedRelaysViaI2p) {
|
||||
trustedRelaysViaI2p.tryEmit(settings.trustedRelaysViaI2p)
|
||||
any = true
|
||||
}
|
||||
if (urlPreviewsViaI2p.value != settings.urlPreviewsViaI2p) {
|
||||
urlPreviewsViaI2p.tryEmit(settings.urlPreviewsViaI2p)
|
||||
any = true
|
||||
}
|
||||
if (profilePicsViaI2p.value != settings.profilePicsViaI2p) {
|
||||
profilePicsViaI2p.tryEmit(settings.profilePicsViaI2p)
|
||||
any = true
|
||||
}
|
||||
if (imagesViaI2p.value != settings.imagesViaI2p) {
|
||||
imagesViaI2p.tryEmit(settings.imagesViaI2p)
|
||||
any = true
|
||||
}
|
||||
if (videosViaI2p.value != settings.videosViaI2p) {
|
||||
videosViaI2p.tryEmit(settings.videosViaI2p)
|
||||
any = true
|
||||
}
|
||||
if (moneyOperationsViaI2p.value != settings.moneyOperationsViaI2p) {
|
||||
moneyOperationsViaI2p.tryEmit(settings.moneyOperationsViaI2p)
|
||||
any = true
|
||||
}
|
||||
if (nip05VerificationsViaI2p.value != settings.nip05VerificationsViaI2p) {
|
||||
nip05VerificationsViaI2p.tryEmit(settings.nip05VerificationsViaI2p)
|
||||
any = true
|
||||
}
|
||||
if (mediaUploadsViaI2p.value != settings.mediaUploadsViaI2p) {
|
||||
mediaUploadsViaI2p.tryEmit(settings.mediaUploadsViaI2p)
|
||||
any = true
|
||||
}
|
||||
|
||||
return any
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun build(settings: I2pSettings): I2pSettingsFlow =
|
||||
I2pSettingsFlow(
|
||||
MutableStateFlow(settings.i2pType),
|
||||
MutableStateFlow(settings.externalSocksPort),
|
||||
MutableStateFlow(settings.i2pRelaysViaI2p),
|
||||
MutableStateFlow(settings.dmRelaysViaI2p),
|
||||
MutableStateFlow(settings.newRelaysViaI2p),
|
||||
MutableStateFlow(settings.trustedRelaysViaI2p),
|
||||
MutableStateFlow(settings.urlPreviewsViaI2p),
|
||||
MutableStateFlow(settings.profilePicsViaI2p),
|
||||
MutableStateFlow(settings.imagesViaI2p),
|
||||
MutableStateFlow(settings.videosViaI2p),
|
||||
MutableStateFlow(settings.moneyOperationsViaI2p),
|
||||
MutableStateFlow(settings.nip05VerificationsViaI2p),
|
||||
MutableStateFlow(settings.mediaUploadsViaI2p),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user