Merge pull request #3021 from vitorpamplona/claude/add-public-chip-confirmation-2Q7sh

Add warning dialog for copying public onchain addresses
This commit is contained in:
Vitor Pamplona
2026-05-20 17:38:45 -04:00
committed by GitHub
5 changed files with 95 additions and 1 deletions
@@ -52,6 +52,7 @@ data class UiSettings(
val showProfileAppRecommendations: Boolean = true,
val showProfileZapReceivedFeed: Boolean = true,
val showProfileFollowersFeed: Boolean = true,
val dontShowOnchainPublicWarning: Boolean = false,
)
enum class ThemeType(
@@ -52,6 +52,7 @@ class UiSettingsFlow(
val showProfileAppRecommendations: MutableStateFlow<Boolean> = MutableStateFlow(true),
val showProfileZapReceivedFeed: MutableStateFlow<Boolean> = MutableStateFlow(true),
val showProfileFollowersFeed: MutableStateFlow<Boolean> = MutableStateFlow(true),
val dontShowOnchainPublicWarning: MutableStateFlow<Boolean> = MutableStateFlow(false),
) {
val listOfFlows: List<Flow<Any?>> =
listOf<Flow<Any?>>(
@@ -78,6 +79,7 @@ class UiSettingsFlow(
showProfileAppRecommendations,
showProfileZapReceivedFeed,
showProfileFollowersFeed,
dontShowOnchainPublicWarning,
)
// emits at every change in any of the propertyes.
@@ -108,6 +110,7 @@ class UiSettingsFlow(
flows[20] as Boolean,
flows[21] as Boolean,
flows[22] as Boolean,
flows[23] as Boolean,
)
}
@@ -136,6 +139,7 @@ class UiSettingsFlow(
showProfileAppRecommendations.value,
showProfileZapReceivedFeed.value,
showProfileFollowersFeed.value,
dontShowOnchainPublicWarning.value,
)
fun update(torSettings: UiSettings): Boolean {
@@ -233,6 +237,10 @@ class UiSettingsFlow(
showProfileFollowersFeed.tryEmit(torSettings.showProfileFollowersFeed)
any = true
}
if (dontShowOnchainPublicWarning.value != torSettings.dontShowOnchainPublicWarning) {
dontShowOnchainPublicWarning.tryEmit(torSettings.dontShowOnchainPublicWarning)
any = true
}
return any
}
@@ -249,6 +257,12 @@ class UiSettingsFlow(
}
}
fun dontShowOnchainPublicWarning() {
if (!dontShowOnchainPublicWarning.value) {
dontShowOnchainPublicWarning.tryEmit(true)
}
}
companion object {
fun build(uiSettings: UiSettings): UiSettingsFlow =
UiSettingsFlow(
@@ -275,6 +289,7 @@ class UiSettingsFlow(
MutableStateFlow(uiSettings.showProfileAppRecommendations),
MutableStateFlow(uiSettings.showProfileZapReceivedFeed),
MutableStateFlow(uiSettings.showProfileFollowersFeed),
MutableStateFlow(uiSettings.dontShowOnchainPublicWarning),
)
}
}
@@ -116,6 +116,7 @@ class UiSharedPreferences(
val UI_SHOW_PROFILE_APP_RECOMMENDATIONS = booleanPreferencesKey("ui.show_profile_app_recommendations")
val UI_SHOW_PROFILE_ZAP_RECEIVED_FEED = booleanPreferencesKey("ui.show_profile_zap_received_feed")
val UI_SHOW_PROFILE_FOLLOWERS_FEED = booleanPreferencesKey("ui.show_profile_followers_feed")
val UI_DONT_SHOW_ONCHAIN_PUBLIC_WARNING = booleanPreferencesKey("ui.dont_show_onchain_public_warning")
suspend fun uiPreferences(context: Context): UiSettings? =
try {
@@ -150,6 +151,7 @@ class UiSharedPreferences(
showProfileAppRecommendations = preferences[UI_SHOW_PROFILE_APP_RECOMMENDATIONS] ?: true,
showProfileZapReceivedFeed = preferences[UI_SHOW_PROFILE_ZAP_RECEIVED_FEED] ?: true,
showProfileFollowersFeed = preferences[UI_SHOW_PROFILE_FOLLOWERS_FEED] ?: true,
dontShowOnchainPublicWarning = preferences[UI_DONT_SHOW_ONCHAIN_PUBLIC_WARNING] ?: false,
)
} catch (e: Exception) {
if (e is CancellationException) throw e
@@ -197,6 +199,7 @@ class UiSharedPreferences(
preferences[UI_SHOW_PROFILE_APP_RECOMMENDATIONS] = sharedSettings.showProfileAppRecommendations
preferences[UI_SHOW_PROFILE_ZAP_RECEIVED_FEED] = sharedSettings.showProfileZapReceivedFeed
preferences[UI_SHOW_PROFILE_FOLLOWERS_FEED] = sharedSettings.showProfileFollowersFeed
preferences[UI_DONT_SHOW_ONCHAIN_PUBLIC_WARNING] = sharedSettings.dontShowOnchainPublicWarning
}
} catch (e: Exception) {
if (e is CancellationException) throw e
@@ -26,6 +26,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@@ -62,6 +63,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
@@ -360,7 +362,15 @@ private fun ActionRow(
) {
val clipboard = LocalClipboard.current
val scope = rememberCoroutineScope()
val sharedPrefs = accountViewModel.settings.uiSettingsFlow
val dontShowCopyWarning by sharedPrefs.dontShowOnchainPublicWarning.collectAsStateWithLifecycle()
var showSendDialog by remember { mutableStateOf(false) }
var showCopyWarning by remember { mutableStateOf(false) }
val copyAddress = {
scope.launch { clipboard.setText(address) }
Unit
}
Row(
modifier = Modifier.fillMaxWidth(),
@@ -368,7 +378,13 @@ private fun ActionRow(
verticalAlignment = Alignment.CenterVertically,
) {
OutlinedButton(
onClick = { scope.launch { clipboard.setText(address) } },
onClick = {
if (dontShowCopyWarning) {
copyAddress()
} else {
showCopyWarning = true
}
},
modifier = Modifier.height(36.dp),
shape = RoundedCornerShape(8.dp),
) {
@@ -409,4 +425,62 @@ private fun ActionRow(
onDismiss = { showSendDialog = false },
)
}
if (showCopyWarning) {
CopyPublicAddressDialog(
onConfirm = {
showCopyWarning = false
copyAddress()
},
onDontShowAgain = {
sharedPrefs.dontShowOnchainPublicWarning()
showCopyWarning = false
copyAddress()
},
onDismiss = { showCopyWarning = false },
)
}
}
@Composable
private fun CopyPublicAddressDialog(
onConfirm: () -> Unit,
onDontShowAgain: () -> Unit,
onDismiss: () -> Unit,
) {
AlertDialog(
onDismissRequest = onDismiss,
icon = {
Icon(
symbol = MaterialSymbols.Info,
contentDescription = null,
modifier = Modifier.size(24.dp),
)
},
title = { Text(stringRes(R.string.wallet_onchain_public_dialog_title)) },
text = { Text(stringRes(R.string.wallet_onchain_public_dialog_body)) },
confirmButton = {
Row(
modifier = Modifier.padding(all = 8.dp).fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
TextButton(onClick = onDontShowAgain) {
Text(stringRes(R.string.quick_action_dont_show_again_button))
}
Button(
onClick = onConfirm,
contentPadding = PaddingValues(horizontal = 16.dp),
) {
Icon(
symbol = MaterialSymbols.ContentCopy,
contentDescription = null,
modifier = Modifier.size(16.dp),
)
Spacer(Modifier.width(8.dp))
Text(stringRes(R.string.wallet_onchain_copy_dialog_confirm))
}
}
},
)
}
+1
View File
@@ -1867,6 +1867,7 @@
<string name="wallet_onchain_public_dialog_title">This wallet is public</string>
<string name="wallet_onchain_public_dialog_body">Your Taproot address is derived from your Nostr public key, so anyone who knows your npub can see this wallet\'s balance and transaction history on the blockchain.\n\nTo preserve your privacy, fund and drain this wallet from and to non-private accounts, like exchanges. Never mix these funds with your cold wallets, and treat them as money you can lose, since anyone in control of your nsec can spend it.</string>
<string name="wallet_onchain_public_dialog_confirm">Got it</string>
<string name="wallet_onchain_copy_dialog_confirm">Copy address</string>
<string name="route_security_filters">Security Filters</string>
<string name="route_import_follows">Import Follows</string>