diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/UiSettings.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/UiSettings.kt index 79a9af5791..fc453f5c29 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/UiSettings.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/UiSettings.kt @@ -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( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/UiSettingsFlow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/UiSettingsFlow.kt index 8241424952..146a6a8fc6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/UiSettingsFlow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/UiSettingsFlow.kt @@ -52,6 +52,7 @@ class UiSettingsFlow( val showProfileAppRecommendations: MutableStateFlow = MutableStateFlow(true), val showProfileZapReceivedFeed: MutableStateFlow = MutableStateFlow(true), val showProfileFollowersFeed: MutableStateFlow = MutableStateFlow(true), + val dontShowOnchainPublicWarning: MutableStateFlow = MutableStateFlow(false), ) { val listOfFlows: List> = listOf>( @@ -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), ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/preferences/UISharedPreferences.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/preferences/UISharedPreferences.kt index ee4561b004..ebf0c03d44 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/preferences/UISharedPreferences.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/preferences/UISharedPreferences.kt @@ -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 diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainSection.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainSection.kt index 92b39e41d0..edb46d9c02 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainSection.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainSection.kt @@ -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)) + } + } + }, + ) } diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index bcd67af071..a4f33e0697 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -1867,6 +1867,7 @@ This wallet is public 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. Got it + Copy address Security Filters Import Follows