diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthComposeHelpers.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthComposeHelpers.kt new file mode 100644 index 0000000000..33f641ed21 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthComposeHelpers.kt @@ -0,0 +1,63 @@ +/* + * 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.service.relayClient.authCommand.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.commons.relayauth.AuthPurposeKind +import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.quartz.nip01Core.core.HexKey + +/** + * Loads [pubkey] from the local cache, get-or-creating (and subscribing) if absent, then hands the + * [User] (or null while it loads) to [content]. Shared by the auth prompt dialog and the relay-auth + * settings screen, which both render a person by pubkey while their metadata streams in. + */ +@Composable +internal fun LoadRelayAuthUser( + pubkey: HexKey, + accountViewModel: AccountViewModel, + content: @Composable (User?) -> Unit, +) { + var user by remember(pubkey) { mutableStateOf(accountViewModel.getUserIfExists(pubkey)) } + if (user == null) { + LaunchedEffect(pubkey) { user = accountViewModel.checkGetOrCreateUser(pubkey) } + } + content(user) +} + +/** The string explaining a single [AuthPurposeKind] ("To send DMs to", "To download posts from", …). */ +internal fun relayAuthReasonRes(kind: AuthPurposeKind): Int = + when (kind) { + AuthPurposeKind.SEND_DM -> R.string.relay_auth_reason_send_dm + AuthPurposeKind.NOTIFY_INBOX -> R.string.relay_auth_reason_notify_inbox + AuthPurposeKind.READ_OUTBOX -> R.string.relay_auth_reason_read_outbox + AuthPurposeKind.POST_VENUE -> R.string.relay_auth_reason_post_venue + AuthPurposeKind.READ_VENUE -> R.string.relay_auth_reason_read_venue + AuthPurposeKind.MY_OWN_RELAY -> R.string.relay_auth_reason_my_own_relay + AuthPurposeKind.OTHER -> R.string.relay_auth_reason_other + } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthPromptHost.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthPromptHost.kt index 39ca220c78..fb61055c3d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthPromptHost.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthPromptHost.kt @@ -56,7 +56,6 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.Channel import com.vitorpamplona.amethyst.commons.relayauth.AuthPurpose import com.vitorpamplona.amethyst.commons.relayauth.AuthPurposeKind -import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relayClient.authCommand.model.RelayAuthPrompt import com.vitorpamplona.amethyst.service.relayClient.authCommand.model.UserAuthChoice import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel @@ -143,7 +142,7 @@ private fun RelayAuthPromptDialog( Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { if (showLabels) { Text( - text = stringRes(reasonRes(purpose.kind)), + text = stringRes(relayAuthReasonRes(purpose.kind)), style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -211,7 +210,7 @@ private fun CounterpartyRow( pubkey: HexKey, accountViewModel: AccountViewModel, ) { - LoadUser(pubkey, accountViewModel) { user -> + LoadRelayAuthUser(pubkey, accountViewModel) { user -> if (user != null) { Row( verticalAlignment = Alignment.CenterVertically, @@ -234,7 +233,7 @@ private fun CounterpartyFacepile( horizontalArrangement = Arrangement.spacedBy((-8).dp), ) { pubkeys.take(FACEPILE_MAX).forEach { pubkey -> - LoadUser(pubkey, accountViewModel) { user -> + LoadRelayAuthUser(pubkey, accountViewModel) { user -> if (user != null) { ClickableUserPicture( baseUser = user, @@ -257,30 +256,6 @@ private fun CounterpartyFacepile( } } -@Composable -private fun LoadUser( - pubkey: HexKey, - accountViewModel: AccountViewModel, - content: @Composable (User?) -> Unit, -) { - var user by remember(pubkey) { mutableStateOf(accountViewModel.getUserIfExists(pubkey)) } - if (user == null) { - LaunchedEffect(pubkey) { user = accountViewModel.checkGetOrCreateUser(pubkey) } - } - content(user) -} - -private fun reasonRes(kind: AuthPurposeKind): Int = - when (kind) { - AuthPurposeKind.SEND_DM -> R.string.relay_auth_reason_send_dm - AuthPurposeKind.NOTIFY_INBOX -> R.string.relay_auth_reason_notify_inbox - AuthPurposeKind.READ_OUTBOX -> R.string.relay_auth_reason_read_outbox - AuthPurposeKind.POST_VENUE -> R.string.relay_auth_reason_post_venue - AuthPurposeKind.READ_VENUE -> R.string.relay_auth_reason_read_venue - AuthPurposeKind.MY_OWN_RELAY -> R.string.relay_auth_reason_my_own_relay - AuthPurposeKind.OTHER -> R.string.relay_auth_reason_other - } - /** The purpose that best describes what the user was doing (most user-facing first). */ private fun List.primaryNamed(): AuthPurpose? = listOf( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relayauth/RelayAuthSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relayauth/RelayAuthSettingsScreen.kt index 796cc69943..1e46fbc46b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relayauth/RelayAuthSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relayauth/RelayAuthSettingsScreen.kt @@ -63,7 +63,8 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.relayauth.AuthPurposeKind import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthDecision import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthPolicy -import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.service.relayClient.authCommand.compose.LoadRelayAuthUser +import com.vitorpamplona.amethyst.service.relayClient.authCommand.compose.relayAuthReasonRes import com.vitorpamplona.amethyst.service.relayClient.authCommand.model.DataStoreRelayAuthPermissionStore import com.vitorpamplona.amethyst.service.relayClient.authCommand.model.RelayAuthPermissionLedger import com.vitorpamplona.amethyst.ui.navigation.navs.INav @@ -308,7 +309,7 @@ private fun RelayRationaleCard( } rationale.forEach { (kind, pubkeys) -> Text( - text = stringResource(reasonRes(kind)), + text = stringResource(relayAuthReasonRes(kind)), style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -334,7 +335,7 @@ private fun RationaleUserRow( pubkey: HexKey, accountViewModel: AccountViewModel, ) { - LoadUserForRationale(pubkey, accountViewModel) { user -> + LoadRelayAuthUser(pubkey, accountViewModel) { user -> if (user != null) { Row( verticalAlignment = Alignment.CenterVertically, @@ -348,30 +349,6 @@ private fun RationaleUserRow( } } -@Composable -private fun LoadUserForRationale( - pubkey: HexKey, - accountViewModel: AccountViewModel, - content: @Composable (User?) -> Unit, -) { - var user by remember(pubkey) { mutableStateOf(accountViewModel.getUserIfExists(pubkey)) } - if (user == null) { - LaunchedEffect(pubkey) { user = accountViewModel.checkGetOrCreateUser(pubkey) } - } - content(user) -} - -private fun reasonRes(kind: AuthPurposeKind): Int = - when (kind) { - AuthPurposeKind.SEND_DM -> R.string.relay_auth_reason_send_dm - AuthPurposeKind.NOTIFY_INBOX -> R.string.relay_auth_reason_notify_inbox - AuthPurposeKind.READ_OUTBOX -> R.string.relay_auth_reason_read_outbox - AuthPurposeKind.POST_VENUE -> R.string.relay_auth_reason_post_venue - AuthPurposeKind.READ_VENUE -> R.string.relay_auth_reason_read_venue - AuthPurposeKind.MY_OWN_RELAY -> R.string.relay_auth_reason_my_own_relay - AuthPurposeKind.OTHER -> R.string.relay_auth_reason_other - } - @Composable private fun PerRelayOverrideRow( url: String,