From 86a5da175a48bc8cb1a7e2461e46f950a25f4272 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 29 Sep 2026 13:54:48 -0400 Subject: [PATCH] fix(concord): confirm before banning a member Ban fired straight from the overflow menu while Remove asked first. A ban can be lifted here, but other clients (Armada) drop the community from a banned member's list on sight, so a mis-tap still costs them the community. Ask first, reusing the Remove dialog; unban stays one tap. Co-Authored-By: Claude Opus 5.5 --- .../composeResources/values/strings.xml | 2 + .../concord/ConcordMembersScreen.kt | 38 ++++++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/commonsUI/src/commonMain/composeResources/values/strings.xml b/commonsUI/src/commonMain/composeResources/values/strings.xml index 1003a3ee26..d83cb04e80 100644 --- a/commonsUI/src/commonMain/composeResources/values/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values/strings.xml @@ -3642,6 +3642,8 @@ %1$d members Ban + Ban member? + They can no longer post, and their messages are hidden from everyone. Other apps may remove the community from their list. They can still read new messages until you also remove them from the community. You can unban them later. Make admin Remove admin Could not update this member's roles. diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMembersScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMembersScreen.kt index 8df45dbba3..0ee288bd3b 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMembersScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMembersScreen.kt @@ -63,6 +63,8 @@ import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.back import com.vitorpamplona.amethyst.commons.resources.cancel import com.vitorpamplona.amethyst.commons.resources.concord_members_ban +import com.vitorpamplona.amethyst.commons.resources.concord_members_ban_message +import com.vitorpamplona.amethyst.commons.resources.concord_members_ban_title import com.vitorpamplona.amethyst.commons.resources.concord_members_empty import com.vitorpamplona.amethyst.commons.resources.concord_members_make_admin import com.vitorpamplona.amethyst.commons.resources.concord_members_remove @@ -92,6 +94,7 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.concord.cord04Roles.ConcordPermissions import com.vitorpamplona.quartz.nip01Core.core.HexKey import kotlinx.coroutines.flow.MutableStateFlow +import org.jetbrains.compose.resources.StringResource import com.vitorpamplona.amethyst.commons.icons.symbols.Icon as SymbolIcon /** @@ -289,9 +292,28 @@ private fun ConcordMemberRow( ) } + // A ban is reversible here, but not for the banned member: clients such as Armada drop the + // community from a banned member's list on sight, so a mis-tap still costs them the community. + var confirmBan by remember { mutableStateOf(false) } + if (confirmBan) { + ConcordConfirmMemberActionDialog( + title = Res.string.concord_members_ban_title, + message = Res.string.concord_members_ban_message, + confirm = Res.string.concord_members_ban, + onConfirm = { + accountViewModel.setConcordBan(communityId, entry.pubkey, ban = true) + confirmBan = false + }, + onDismiss = { confirmBan = false }, + ) + } + var confirmRemove by remember { mutableStateOf(false) } if (confirmRemove) { - ConcordRemoveMemberDialog( + ConcordConfirmMemberActionDialog( + title = Res.string.concord_members_remove_title, + message = Res.string.concord_members_remove_message, + confirm = Res.string.concord_members_remove_confirm, onConfirm = { accountViewModel.removeConcordMember(communityId, entry.pubkey) confirmRemove = false @@ -357,7 +379,8 @@ private fun ConcordMemberRow( DropdownMenuItem( text = { Text(stringRes(if (isBanned) Res.string.concord_members_unban else Res.string.concord_members_ban)) }, onClick = { - accountViewModel.setConcordBan(communityId, entry.pubkey, ban = !isBanned) + // Unbanning restores access, so only a ban asks first. + if (isBanned) accountViewModel.setConcordBan(communityId, entry.pubkey, ban = false) else confirmBan = true expanded = false }, ) @@ -471,17 +494,20 @@ private fun ConcordRolesDialog( /** Confirms a hard removal — spells out that it rotates the community key (CORD-06). */ @Composable -private fun ConcordRemoveMemberDialog( +private fun ConcordConfirmMemberActionDialog( + title: StringResource, + message: StringResource, + confirm: StringResource, onConfirm: () -> Unit, onDismiss: () -> Unit, ) { AlertDialog( onDismissRequest = onDismiss, - title = { Text(stringRes(Res.string.concord_members_remove_title)) }, - text = { Text(stringRes(Res.string.concord_members_remove_message)) }, + title = { Text(stringRes(title)) }, + text = { Text(stringRes(message)) }, confirmButton = { TextButton(onClick = onConfirm) { - Text(stringRes(Res.string.concord_members_remove_confirm), color = MaterialTheme.colorScheme.error) + Text(stringRes(confirm), color = MaterialTheme.colorScheme.error) } }, dismissButton = {