From 8559712a55922560e531b6a47b0569ce133a2f8f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 8 May 2026 09:23:30 +0000 Subject: [PATCH] refactor(privacy): move disableClientTag into NIP-78-synced security settings Aligns the toggle with its Security Filters siblings (warnAboutPostsWithReports, filterSpamFromStrangers, etc.): stored in AccountSecurityPreferences{Internal}, synced across devices via the app-specific data event, and updated through account.updateDisableClientTag -> sendNewAppSpecificData. The signer wrapper now reads the live value from synced settings, so changes still apply immediately to the next signed event. https://claude.ai/code/session_01KhNVTm8LLdVphqyxVkZtDS --- .../vitorpamplona/amethyst/LocalPreferences.kt | 4 ---- .../com/vitorpamplona/amethyst/model/Account.kt | 8 ++++++++ .../amethyst/model/AccountSettings.kt | 10 +++++----- .../amethyst/model/AccountSyncedSettings.kt | 15 +++++++++++++++ .../model/AccountSyncedSettingsInternal.kt | 1 + .../model/accountsCache/AccountCacheState.kt | 2 +- .../ui/screen/loggedIn/AccountViewModel.kt | 2 ++ .../loggedIn/settings/SecurityFiltersScreen.kt | 4 ++-- 8 files changed, 34 insertions(+), 12 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt index 864a2706a..ce6324ffc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt @@ -95,7 +95,6 @@ private object PrefKeys { const val LOCAL_RELAY_SERVERS = "localRelayServers" const val DEFAULT_FILE_SERVER = "defaultFileServer" const val STRIP_LOCATION_ON_UPLOAD = "stripLocationOnUpload" - const val DISABLE_CLIENT_TAG = "disableClientTag" const val DEFAULT_HOME_FOLLOW_LIST = "defaultHomeFollowList" const val DEFAULT_STORIES_FOLLOW_LIST = "defaultStoriesFollowList" const val DEFAULT_NOTIFICATION_FOLLOW_LIST = "defaultNotificationFollowList" @@ -347,7 +346,6 @@ object LocalPreferences { ) putBoolean(PrefKeys.STRIP_LOCATION_ON_UPLOAD, settings.stripLocationOnUpload) - putBoolean(PrefKeys.DISABLE_CLIENT_TAG, settings.disableClientTag) putString(PrefKeys.DEFAULT_HOME_FOLLOW_LIST, JsonMapper.toJson(settings.defaultHomeFollowList.value)) putString(PrefKeys.DEFAULT_STORIES_FOLLOW_LIST, JsonMapper.toJson(settings.defaultStoriesFollowList.value)) @@ -515,7 +513,6 @@ object LocalPreferences { Log.d("LocalPreferences") { "Load account from file $npub - keys ready" } val stripLocationOnUpload = getBoolean(PrefKeys.STRIP_LOCATION_ON_UPLOAD, true) - val disableClientTag = getBoolean(PrefKeys.DISABLE_CLIENT_TAG, false) val hideDeleteRequestDialog = getBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, false) val hideBlockAlertDialog = getBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, false) val hideNIP17WarningDialog = getBoolean(PrefKeys.HIDE_NIP_17_WARNING_DIALOG, false) @@ -623,7 +620,6 @@ object LocalPreferences { localRelayServers = MutableStateFlow(localRelayServers), defaultFileServer = defaultFileServer.await(), stripLocationOnUpload = stripLocationOnUpload, - disableClientTag = disableClientTag, defaultHomeFollowList = MutableStateFlow(followListPrefs.home), defaultStoriesFollowList = MutableStateFlow(followListPrefs.stories), defaultNotificationFollowList = MutableStateFlow(followListPrefs.notification), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 10e9d405c..6154f2693 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -529,6 +529,14 @@ class Account( return false } + suspend fun updateDisableClientTag(disable: Boolean): Boolean { + if (settings.updateDisableClientTag(disable)) { + sendNewAppSpecificData() + return true + } + return false + } + suspend fun updateFilterSpam(filterSpam: Boolean): Boolean { if (settings.updateFilterSpam(filterSpam)) { if (!settings.syncedSettings.security.filterSpamFromStrangers.value) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt index ea8b1b3c5..568925f1d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt @@ -149,7 +149,6 @@ class AccountSettings( var localRelayServers: MutableStateFlow> = MutableStateFlow(setOf()), var defaultFileServer: ServerName = DEFAULT_MEDIA_SERVERS[0], var stripLocationOnUpload: Boolean = true, - var disableClientTag: Boolean = false, val defaultHomeFollowList: MutableStateFlow = MutableStateFlow(TopFilter.AllFollows), val defaultStoriesFollowList: MutableStateFlow = MutableStateFlow(TopFilter.Global), val defaultNotificationFollowList: MutableStateFlow = MutableStateFlow(TopFilter.Global), @@ -404,12 +403,13 @@ class AccountSettings( } } - fun changeDisableClientTag(disable: Boolean) { - if (disableClientTag != disable) { - disableClientTag = disable + fun updateDisableClientTag(disable: Boolean): Boolean = + if (syncedSettings.security.updateDisableClientTag(disable)) { saveAccountSettings() + true + } else { + false } - } // --- // list names diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettings.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettings.kt index adc3299ca..9d3721b3b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettings.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettings.kt @@ -55,6 +55,7 @@ class AccountSyncedSettings( MutableStateFlow(internalSettings.security.filterSpamFromStrangers), MutableStateFlow(internalSettings.security.maxHashtagLimit), MutableStateFlow(internalSettings.security.sendKind0EventsToLocalRelay), + MutableStateFlow(internalSettings.security.disableClientTag), ) val videoPlayer = AccountVideoPlayerPreferences( @@ -82,6 +83,7 @@ class AccountSyncedSettings( security.filterSpamFromStrangers.value, security.maxHashtagLimit.value, security.sendKind0EventsToLocalRelay.value, + security.disableClientTag.value, ), videoPlayer = AccountVideoPlayerPreferencesInternal(videoPlayer.buttonItems.value), ) @@ -138,6 +140,10 @@ class AccountSyncedSettings( security.sendKind0EventsToLocalRelay.tryEmit(syncedSettingsInternal.security.sendKind0EventsToLocalRelay) } + if (security.disableClientTag.value != syncedSettingsInternal.security.disableClientTag) { + security.disableClientTag.tryEmit(syncedSettingsInternal.security.disableClientTag) + } + val newVideoPlayerButtonItems = syncedSettingsInternal.videoPlayer.buttonItems.toImmutableList() if (!equalImmutableLists(videoPlayer.buttonItems.value, newVideoPlayerButtonItems)) { videoPlayer.buttonItems.tryEmit(newVideoPlayerButtonItems) @@ -233,6 +239,7 @@ class AccountSecurityPreferences( var filterSpamFromStrangers: MutableStateFlow = MutableStateFlow(true), val maxHashtagLimit: MutableStateFlow = MutableStateFlow(5), var sendKind0EventsToLocalRelay: MutableStateFlow = MutableStateFlow(false), + val disableClientTag: MutableStateFlow = MutableStateFlow(false), ) { fun updateShowSensitiveContent(show: Boolean?): Boolean { if (showSensitiveContent.value != show) { @@ -273,4 +280,12 @@ class AccountSecurityPreferences( } else { false } + + fun updateDisableClientTag(disable: Boolean): Boolean = + if (disable != disableClientTag.value) { + disableClientTag.tryEmit(disable) + true + } else { + false + } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettingsInternal.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettingsInternal.kt index efc03475a..a272bfe02 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettingsInternal.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettingsInternal.kt @@ -147,4 +147,5 @@ class AccountSecurityPreferencesInternal( var filterSpamFromStrangers: Boolean = true, val maxHashtagLimit: Int = 5, var sendKind0EventsToLocalRelay: Boolean = false, + var disableClientTag: Boolean = false, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/accountsCache/AccountCacheState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/accountsCache/AccountCacheState.kt index 01d416674..655af6970 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/accountsCache/AccountCacheState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/accountsCache/AccountCacheState.kt @@ -139,7 +139,7 @@ class AccountCacheState( NostrSignerWithClientTag( inner = signer, clientName = CLIENT_TAG_NAME, - disabled = { accountSettings.disableClientTag }, + disabled = { accountSettings.syncedSettings.security.disableClientTag.value }, ) val accountDir = File(rootFilesDir(), "accounts/${signer.pubKey}").apply { mkdirs() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 60a1d5624..886cf761f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -1112,6 +1112,8 @@ class AccountViewModel( fun updateWarnReports(warnReports: Boolean) = launchSigner { account.updateWarnReports(warnReports) } + fun updateDisableClientTag(disable: Boolean) = launchSigner { account.updateDisableClientTag(disable) } + fun updateFilterSpam(filterSpam: Boolean) = launchSigner { if (account.updateFilterSpam(filterSpam)) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SecurityFiltersScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SecurityFiltersScreen.kt index 544788b17..2dda65234 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SecurityFiltersScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SecurityFiltersScreen.kt @@ -375,13 +375,13 @@ private fun HeaderOptions(accountViewModel: AccountViewModel) { R.string.disable_client_tag_title, R.string.disable_client_tag_explainer, ) { - var disableClientTag by remember { mutableStateOf(accountViewModel.account.settings.disableClientTag) } + var disableClientTag by remember { mutableStateOf(accountViewModel.account.settings.syncedSettings.security.disableClientTag.value) } Switch( checked = disableClientTag, onCheckedChange = { disableClientTag = it - accountViewModel.account.settings.changeDisableClientTag(it) + accountViewModel.updateDisableClientTag(disableClientTag) }, ) }