android, desktop: block member for all (#3711)

* android: block member for all

* old buttons (revert this)

* blocked by admin text

* revise notification
This commit is contained in:
spaced4ndy 2024-01-20 14:33:49 +04:00 committed by GitHub
parent 7d2f6a3609
commit 106556812c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 270 additions and 38 deletions

View File

@ -1279,6 +1279,7 @@ data class GroupMember (
val memberCategory: GroupMemberCategory,
val memberStatus: GroupMemberStatus,
val memberSettings: GroupMemberSettings,
val blockedByAdmin: Boolean,
val invitedBy: InvitedBy,
val localDisplayName: String,
val memberProfile: LocalProfile,
@ -1297,6 +1298,7 @@ data class GroupMember (
val image: String? get() = memberProfile.image
val contactLink: String? = memberProfile.contactLink
val verified get() = activeConn?.connectionCode != null
val blocked get() = blockedByAdmin || !memberSettings.showMessages
val chatViewName: String
get() {
@ -1345,7 +1347,7 @@ data class GroupMember (
fun canBeRemoved(groupInfo: GroupInfo): Boolean {
val userRole = groupInfo.membership.memberRole
return memberStatus != GroupMemberStatus.MemRemoved && memberStatus != GroupMemberStatus.MemLeft
&& userRole >= GroupMemberRole.Admin && userRole >= memberRole && groupInfo.membership.memberCurrent
&& userRole >= GroupMemberRole.Admin && userRole >= memberRole && groupInfo.membership.memberActive
}
fun canChangeRoleTo(groupInfo: GroupInfo): List<GroupMemberRole>? =
@ -1354,6 +1356,12 @@ data class GroupMember (
GroupMemberRole.values().filter { it <= userRole && it != GroupMemberRole.Author }
}
fun canBlockForAll(groupInfo: GroupInfo): Boolean {
val userRole = groupInfo.membership.memberRole
return memberStatus != GroupMemberStatus.MemRemoved && memberStatus != GroupMemberStatus.MemLeft && memberRole < GroupMemberRole.Admin
&& userRole >= GroupMemberRole.Admin && userRole >= memberRole && groupInfo.membership.memberActive
}
val memberIncognito = memberProfile.profileId != memberContactProfileId
companion object {
@ -1365,6 +1373,7 @@ data class GroupMember (
memberCategory = GroupMemberCategory.InviteeMember,
memberStatus = GroupMemberStatus.MemComplete,
memberSettings = GroupMemberSettings(showMessages = true),
blockedByAdmin = false,
invitedBy = InvitedBy.IBUser(),
localDisplayName = "alice",
memberProfile = LocalProfile.sampleData,
@ -1734,6 +1743,7 @@ data class ChatItem (
is CIContent.RcvDeleted -> true
is CIContent.SndModerated -> true
is CIContent.RcvModerated -> true
is CIContent.RcvBlocked -> true
else -> false
}
@ -1785,20 +1795,18 @@ data class ChatItem (
}
}
private val showNtfDir: Boolean get() = !chatDir.sent
val showNotification: Boolean get() =
when (content) {
is CIContent.SndMsgContent -> showNtfDir
is CIContent.RcvMsgContent -> showNtfDir
is CIContent.SndDeleted -> showNtfDir
is CIContent.RcvDeleted -> showNtfDir
is CIContent.SndCall -> showNtfDir
is CIContent.SndMsgContent -> false
is CIContent.RcvMsgContent -> meta.itemDeleted == null
is CIContent.SndDeleted -> false
is CIContent.RcvDeleted -> false
is CIContent.SndCall -> false
is CIContent.RcvCall -> false // notification is shown on CallInvitation instead
is CIContent.RcvIntegrityError -> showNtfDir
is CIContent.RcvDecryptionError -> showNtfDir
is CIContent.RcvGroupInvitation -> showNtfDir
is CIContent.SndGroupInvitation -> showNtfDir
is CIContent.RcvIntegrityError -> false
is CIContent.RcvDecryptionError -> false
is CIContent.RcvGroupInvitation -> true
is CIContent.SndGroupInvitation -> false
is CIContent.RcvDirectEventContent -> when (content.rcvDirectEvent) {
is RcvDirectEvent.ContactDeleted -> false
is RcvDirectEvent.ProfileUpdated -> true
@ -1808,28 +1816,30 @@ data class ChatItem (
is RcvGroupEvent.MemberConnected -> false
is RcvGroupEvent.MemberLeft -> false
is RcvGroupEvent.MemberRole -> false
is RcvGroupEvent.UserRole -> showNtfDir
is RcvGroupEvent.MemberBlocked -> false
is RcvGroupEvent.UserRole -> true
is RcvGroupEvent.MemberDeleted -> false
is RcvGroupEvent.UserDeleted -> showNtfDir
is RcvGroupEvent.GroupDeleted -> showNtfDir
is RcvGroupEvent.UserDeleted -> true
is RcvGroupEvent.GroupDeleted -> true
is RcvGroupEvent.GroupUpdated -> false
is RcvGroupEvent.InvitedViaGroupLink -> false
is RcvGroupEvent.MemberCreatedContact -> false
is RcvGroupEvent.MemberProfileUpdated -> false
}
is CIContent.SndGroupEventContent -> showNtfDir
is CIContent.SndGroupEventContent -> false
is CIContent.RcvConnEventContent -> false
is CIContent.SndConnEventContent -> showNtfDir
is CIContent.SndConnEventContent -> false
is CIContent.RcvChatFeature -> false
is CIContent.SndChatFeature -> showNtfDir
is CIContent.SndChatFeature -> false
is CIContent.RcvChatPreference -> false
is CIContent.SndChatPreference -> showNtfDir
is CIContent.SndChatPreference -> false
is CIContent.RcvGroupFeature -> false
is CIContent.SndGroupFeature -> showNtfDir
is CIContent.RcvChatFeatureRejected -> showNtfDir
is CIContent.RcvGroupFeatureRejected -> showNtfDir
is CIContent.SndModerated -> true
is CIContent.RcvModerated -> true
is CIContent.SndGroupFeature -> false
is CIContent.RcvChatFeatureRejected -> true
is CIContent.RcvGroupFeatureRejected -> false
is CIContent.SndModerated -> false
is CIContent.RcvModerated -> false
is CIContent.RcvBlocked -> false
is CIContent.InvalidJSON -> false
}
@ -2174,6 +2184,7 @@ enum class SndCIStatusProgress {
sealed class CIDeleted {
@Serializable @SerialName("deleted") class Deleted(val deletedTs: Instant?): CIDeleted()
@Serializable @SerialName("blocked") class Blocked(val deletedTs: Instant?): CIDeleted()
@Serializable @SerialName("blockedByAdmin") class BlockedByAdmin(val deletedTs: Instant?): CIDeleted()
@Serializable @SerialName("moderated") class Moderated(val deletedTs: Instant?, val byGroupMember: GroupMember): CIDeleted()
}
@ -2218,6 +2229,7 @@ sealed class CIContent: ItemContent {
@Serializable @SerialName("rcvGroupFeatureRejected") class RcvGroupFeatureRejected(val groupFeature: GroupFeature): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("sndModerated") object SndModerated: CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvModerated") object RcvModerated: CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvBlocked") object RcvBlocked: CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("invalidJSON") data class InvalidJSON(val json: String): CIContent() { override val msgContent: MsgContent? get() = null }
override val text: String get() = when (this) {
@ -2246,6 +2258,7 @@ sealed class CIContent: ItemContent {
is RcvGroupFeatureRejected -> "${groupFeature.text}: ${generalGetString(MR.strings.feature_received_prohibited)}"
is SndModerated -> generalGetString(MR.strings.moderated_description)
is RcvModerated -> generalGetString(MR.strings.moderated_description)
is RcvBlocked -> generalGetString(MR.strings.blocked_by_admin_item_description)
is InvalidJSON -> "invalid data"
}
@ -2258,6 +2271,7 @@ sealed class CIContent: ItemContent {
is RcvDecryptionError -> true
is RcvGroupInvitation -> true
is RcvModerated -> true
is RcvBlocked -> true
is InvalidJSON -> true
else -> false
}
@ -2958,6 +2972,7 @@ sealed class RcvGroupEvent() {
@Serializable @SerialName("memberConnected") class MemberConnected(): RcvGroupEvent()
@Serializable @SerialName("memberLeft") class MemberLeft(): RcvGroupEvent()
@Serializable @SerialName("memberRole") class MemberRole(val groupMemberId: Long, val profile: Profile, val role: GroupMemberRole): RcvGroupEvent()
@Serializable @SerialName("memberBlocked") class MemberBlocked(val groupMemberId: Long, val profile: Profile, val blocked: Boolean): RcvGroupEvent()
@Serializable @SerialName("userRole") class UserRole(val role: GroupMemberRole): RcvGroupEvent()
@Serializable @SerialName("memberDeleted") class MemberDeleted(val groupMemberId: Long, val profile: Profile): RcvGroupEvent()
@Serializable @SerialName("userDeleted") class UserDeleted(): RcvGroupEvent()
@ -2972,6 +2987,11 @@ sealed class RcvGroupEvent() {
is MemberConnected -> generalGetString(MR.strings.rcv_group_event_member_connected)
is MemberLeft -> generalGetString(MR.strings.rcv_group_event_member_left)
is MemberRole -> String.format(generalGetString(MR.strings.rcv_group_event_changed_member_role), profile.profileViewName, role.text)
is MemberBlocked -> if (blocked) {
String.format(generalGetString(MR.strings.rcv_group_event_member_blocked), profile.profileViewName)
} else {
String.format(generalGetString(MR.strings.rcv_group_event_member_unblocked), profile.profileViewName)
}
is UserRole -> String.format(generalGetString(MR.strings.rcv_group_event_changed_your_role), role.text)
is MemberDeleted -> String.format(generalGetString(MR.strings.rcv_group_event_member_deleted), profile.profileViewName)
is UserDeleted -> generalGetString(MR.strings.rcv_group_event_user_deleted)
@ -3000,6 +3020,7 @@ sealed class RcvGroupEvent() {
sealed class SndGroupEvent() {
@Serializable @SerialName("memberRole") class MemberRole(val groupMemberId: Long, val profile: Profile, val role: GroupMemberRole): SndGroupEvent()
@Serializable @SerialName("userRole") class UserRole(val role: GroupMemberRole): SndGroupEvent()
@Serializable @SerialName("memberBlocked") class MemberBlocked(val groupMemberId: Long, val profile: Profile, val blocked: Boolean): SndGroupEvent()
@Serializable @SerialName("memberDeleted") class MemberDeleted(val groupMemberId: Long, val profile: Profile): SndGroupEvent()
@Serializable @SerialName("userLeft") class UserLeft(): SndGroupEvent()
@Serializable @SerialName("groupUpdated") class GroupUpdated(val groupProfile: GroupProfile): SndGroupEvent()
@ -3007,6 +3028,11 @@ sealed class SndGroupEvent() {
val text: String get() = when (this) {
is MemberRole -> String.format(generalGetString(MR.strings.snd_group_event_changed_member_role), profile.profileViewName, role.text)
is UserRole -> String.format(generalGetString(MR.strings.snd_group_event_changed_role_for_yourself), role.text)
is MemberBlocked -> if (blocked) {
String.format(generalGetString(MR.strings.snd_group_event_member_blocked), profile.profileViewName)
} else {
String.format(generalGetString(MR.strings.snd_group_event_member_unblocked), profile.profileViewName)
}
is MemberDeleted -> String.format(generalGetString(MR.strings.snd_group_event_member_deleted), profile.profileViewName)
is UserLeft -> generalGetString(MR.strings.snd_group_event_user_left)
is GroupUpdated -> generalGetString(MR.strings.snd_group_event_group_profile_updated)

View File

@ -1332,6 +1332,17 @@ object ChatController {
}
}
suspend fun apiBlockMemberForAll(rh: Long?, groupId: Long, memberId: Long, blocked: Boolean): GroupMember =
when (val r = sendCmd(rh, CC.ApiBlockMemberForAll(groupId, memberId, blocked))) {
is CR.MemberBlockedForAllUser -> r.member
else -> {
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiBlockMemberForAll", generalGetString(MR.strings.error_blocking_member_for_all), r)
}
throw Exception("failed to block member for all: ${r.responseType} ${r.details}")
}
}
suspend fun apiLeaveGroup(rh: Long?, groupId: Long): GroupInfo? {
val r = sendCmd(rh, CC.ApiLeaveGroup(groupId))
if (r is CR.LeftMemberUser) return r.groupInfo
@ -1786,6 +1797,10 @@ object ChatController {
if (active(r.user)) {
chatModel.upsertGroupMember(rhId, r.groupInfo, r.member)
}
is CR.MemberBlockedForAll ->
if (active(r.user)) {
chatModel.upsertGroupMember(rhId, r.groupInfo, r.member)
}
is CR.GroupDeleted -> // TODO update user member
if (active(r.user)) {
chatModel.updateGroup(rhId, r.groupInfo)
@ -2275,6 +2290,7 @@ sealed class CC {
class ApiAddMember(val groupId: Long, val contactId: Long, val memberRole: GroupMemberRole): CC()
class ApiJoinGroup(val groupId: Long): CC()
class ApiMemberRole(val groupId: Long, val memberId: Long, val memberRole: GroupMemberRole): CC()
class ApiBlockMemberForAll(val groupId: Long, val memberId: Long, val blocked: Boolean): CC()
class ApiRemoveMember(val groupId: Long, val memberId: Long): CC()
class ApiLeaveGroup(val groupId: Long): CC()
class ApiListMembers(val groupId: Long): CC()
@ -2409,6 +2425,7 @@ sealed class CC {
is ApiAddMember -> "/_add #$groupId $contactId ${memberRole.memberRole}"
is ApiJoinGroup -> "/_join #$groupId"
is ApiMemberRole -> "/_member role #$groupId $memberId ${memberRole.memberRole}"
is ApiBlockMemberForAll -> "/_block #$groupId $memberId blocked=${onOff(blocked)}"
is ApiRemoveMember -> "/_remove #$groupId $memberId"
is ApiLeaveGroup -> "/_leave #$groupId"
is ApiListMembers -> "/_members #$groupId"
@ -2538,6 +2555,7 @@ sealed class CC {
is ApiAddMember -> "apiAddMember"
is ApiJoinGroup -> "apiJoinGroup"
is ApiMemberRole -> "apiMemberRole"
is ApiBlockMemberForAll -> "apiBlockMemberForAll"
is ApiRemoveMember -> "apiRemoveMember"
is ApiLeaveGroup -> "apiLeaveGroup"
is ApiListMembers -> "apiListMembers"
@ -3928,6 +3946,8 @@ sealed class CR {
@Serializable @SerialName("joinedGroupMemberConnecting") class JoinedGroupMemberConnecting(val user: UserRef, val groupInfo: GroupInfo, val hostMember: GroupMember, val member: GroupMember): CR()
@Serializable @SerialName("memberRole") class MemberRole(val user: UserRef, val groupInfo: GroupInfo, val byMember: GroupMember, val member: GroupMember, val fromRole: GroupMemberRole, val toRole: GroupMemberRole): CR()
@Serializable @SerialName("memberRoleUser") class MemberRoleUser(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val fromRole: GroupMemberRole, val toRole: GroupMemberRole): CR()
@Serializable @SerialName("memberBlockedForAll") class MemberBlockedForAll(val user: UserRef, val groupInfo: GroupInfo, val byMember: GroupMember, val member: GroupMember, val blocked: Boolean): CR()
@Serializable @SerialName("memberBlockedForAllUser") class MemberBlockedForAllUser(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val blocked: Boolean): CR()
@Serializable @SerialName("deletedMemberUser") class DeletedMemberUser(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR()
@Serializable @SerialName("deletedMember") class DeletedMember(val user: UserRef, val groupInfo: GroupInfo, val byMember: GroupMember, val deletedMember: GroupMember): CR()
@Serializable @SerialName("leftMember") class LeftMember(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR()
@ -4080,6 +4100,8 @@ sealed class CR {
is JoinedGroupMemberConnecting -> "joinedGroupMemberConnecting"
is MemberRole -> "memberRole"
is MemberRoleUser -> "memberRoleUser"
is MemberBlockedForAll -> "memberBlockedForAll"
is MemberBlockedForAllUser -> "memberBlockedForAllUser"
is DeletedMemberUser -> "deletedMemberUser"
is DeletedMember -> "deletedMember"
is LeftMember -> "leftMember"
@ -4227,6 +4249,8 @@ sealed class CR {
is JoinedGroupMemberConnecting -> withUser(user, "groupInfo: $groupInfo\nhostMember: $hostMember\nmember: $member")
is MemberRole -> withUser(user, "groupInfo: $groupInfo\nbyMember: $byMember\nmember: $member\nfromRole: $fromRole\ntoRole: $toRole")
is MemberRoleUser -> withUser(user, "groupInfo: $groupInfo\nmember: $member\nfromRole: $fromRole\ntoRole: $toRole")
is MemberBlockedForAll -> withUser(user, "groupInfo: $groupInfo\nbyMember: $byMember\nmember: $member\nblocked: $blocked")
is MemberBlockedForAllUser -> withUser(user, "groupInfo: $groupInfo\nmember: $member\nblocked: $blocked")
is DeletedMemberUser -> withUser(user, "groupInfo: $groupInfo\nmember: $member")
is DeletedMember -> withUser(user, "groupInfo: $groupInfo\nbyMember: $byMember\ndeletedMember: $deletedMember")
is LeftMember -> withUser(user, "groupInfo: $groupInfo\nmember: $member")

View File

@ -368,6 +368,18 @@ private fun AddMembersButton(tint: Color = MaterialTheme.colors.primary, onClick
@Composable
private fun MemberRow(member: GroupMember, user: Boolean = false, onClick: (() -> Unit)? = null) {
@Composable
fun MemberInfo() {
if (member.blocked) {
Text(stringResource(MR.strings.member_info_member_blocked), color = MaterialTheme.colors.secondary)
} else {
val role = member.memberRole
if (role in listOf(GroupMemberRole.Owner, GroupMemberRole.Admin, GroupMemberRole.Observer)) {
Text(role.text, color = MaterialTheme.colors.secondary)
}
}
}
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
@ -401,10 +413,7 @@ private fun MemberRow(member: GroupMember, user: Boolean = false, onClick: (() -
)
}
}
val role = member.memberRole
if (role in listOf(GroupMemberRole.Owner, GroupMemberRole.Admin, GroupMemberRole.Observer)) {
Text(role.text, color = MaterialTheme.colors.secondary)
}
MemberInfo()
}
}
@ -415,6 +424,7 @@ private fun MemberVerifiedShield() {
@Composable
private fun DropDownMenuForMember(rhId: Long?, member: GroupMember, groupInfo: GroupInfo, showMenu: MutableState<Boolean>) {
// revert from this:
DefaultDropdownMenu(showMenu) {
if (member.canBeRemoved(groupInfo)) {
ItemAction(stringResource(MR.strings.remove_member_button), painterResource(MR.images.ic_delete), color = MaterialTheme.colors.error, onClick = {
@ -434,6 +444,49 @@ private fun DropDownMenuForMember(rhId: Long?, member: GroupMember, groupInfo: G
})
}
}
// revert to this: vvv
// if (groupInfo.membership.memberRole >= GroupMemberRole.Admin) {
// val canBlockForAll = member.canBlockForAll(groupInfo)
// val canRemove = member.canBeRemoved(groupInfo)
// if (canBlockForAll || canRemove) {
// DefaultDropdownMenu(showMenu) {
// if (canBlockForAll) {
// if (member.blockedByAdmin) {
// ItemAction(stringResource(MR.strings.unblock_for_all), painterResource(MR.images.ic_do_not_touch), onClick = {
// unblockForAllAlert(rhId, groupInfo, member)
// showMenu.value = false
// })
// } else {
// ItemAction(stringResource(MR.strings.block_for_all), painterResource(MR.images.ic_back_hand), color = MaterialTheme.colors.error, onClick = {
// blockForAllAlert(rhId, groupInfo, member)
// showMenu.value = false
// })
// }
// }
// if (canRemove) {
// ItemAction(stringResource(MR.strings.remove_member_button), painterResource(MR.images.ic_delete), color = MaterialTheme.colors.error, onClick = {
// removeMemberAlert(rhId, groupInfo, member)
// showMenu.value = false
// })
// }
// }
// }
// } else if (!member.blockedByAdmin) {
// DefaultDropdownMenu(showMenu) {
// if (member.memberSettings.showMessages) {
// ItemAction(stringResource(MR.strings.block_member_button), painterResource(MR.images.ic_back_hand), color = MaterialTheme.colors.error, onClick = {
// blockMemberAlert(rhId, groupInfo, member)
// showMenu.value = false
// })
// } else {
// ItemAction(stringResource(MR.strings.unblock_member_button), painterResource(MR.images.ic_do_not_touch), onClick = {
// unblockMemberAlert(rhId, groupInfo, member)
// showMenu.value = false
// })
// }
// }
// }
// ^^^
}
@Composable

View File

@ -3,9 +3,11 @@ package chat.simplex.common.views.chat.group
import InfoRow
import SectionBottomSpacer
import SectionDividerSpaced
import SectionItemView
import SectionSpacer
import SectionTextFooter
import SectionView
import TextIconSpaced
import androidx.compose.desktop.ui.tooling.preview.Preview
import java.net.URI
import androidx.compose.foundation.*
@ -99,6 +101,8 @@ fun GroupMemberInfoView(
},
blockMember = { blockMemberAlert(rhId, groupInfo, member) },
unblockMember = { unblockMemberAlert(rhId, groupInfo, member) },
blockForAll = { blockForAllAlert(rhId, groupInfo, member) },
unblockForAll = { unblockForAllAlert(rhId, groupInfo, member) },
removeMember = { removeMemberDialog(rhId, groupInfo, member, chatModel, close) },
onRoleSelected = {
if (it == newRole.value) return@GroupMemberInfoLayout
@ -230,6 +234,8 @@ fun GroupMemberInfoLayout(
connectViaAddress: (String) -> Unit,
blockMember: () -> Unit,
unblockMember: () -> Unit,
blockForAll: () -> Unit,
unblockForAll: () -> Unit,
removeMember: () -> Unit,
onRoleSelected: (GroupMemberRole) -> Unit,
switchMemberAddress: () -> Unit,
@ -248,6 +254,46 @@ fun GroupMemberInfoLayout(
}
}
@Composable
fun AdminDestructiveSection() {
val canBlockForAll = member.canBlockForAll(groupInfo)
val canRemove = member.canBeRemoved(groupInfo)
if (canBlockForAll || canRemove) {
SectionDividerSpaced(maxBottomPadding = false)
SectionView {
if (canBlockForAll) {
if (member.blockedByAdmin) {
UnblockForAllButton(unblockForAll)
} else {
BlockForAllButton(blockForAll)
}
}
if (canRemove) {
RemoveMemberButton(removeMember)
}
}
}
}
@Composable
fun NonAdminBlockSection() {
SectionDividerSpaced(maxBottomPadding = false)
SectionView {
if (member.blockedByAdmin) {
SettingsActionItem(
painterResource(MR.images.ic_back_hand),
stringResource(MR.strings.member_blocked_by_admin),
click = null,
disabled = true
)
} else if (member.memberSettings.showMessages) {
BlockMemberButton(blockMember)
} else {
UnblockMemberButton(unblockMember)
}
}
}
Column(
Modifier
.fillMaxWidth()
@ -344,6 +390,7 @@ fun GroupMemberInfoLayout(
}
}
// revert from this:
SectionDividerSpaced(maxBottomPadding = false)
SectionView {
if (member.memberSettings.showMessages) {
@ -355,6 +402,13 @@ fun GroupMemberInfoLayout(
RemoveMemberButton(removeMember)
}
}
// revert to this: vvv
// if (groupInfo.membership.memberRole >= GroupMemberRole.Admin) {
// AdminDestructiveSection()
// } else {
// NonAdminBlockSection()
// }
// ^^^
if (developerTools) {
SectionDividerSpaced()
@ -427,6 +481,26 @@ fun UnblockMemberButton(onClick: () -> Unit) {
)
}
@Composable
fun BlockForAllButton(onClick: () -> Unit) {
SettingsActionItem(
painterResource(MR.images.ic_back_hand),
stringResource(MR.strings.block_for_all),
click = onClick,
textColor = Color.Red,
iconColor = Color.Red,
)
}
@Composable
fun UnblockForAllButton(onClick: () -> Unit) {
SettingsActionItem(
painterResource(MR.images.ic_do_not_touch),
stringResource(MR.strings.unblock_for_all),
click = onClick
)
}
@Composable
fun RemoveMemberButton(onClick: () -> Unit) {
SettingsActionItem(
@ -553,6 +627,36 @@ fun updateMemberSettings(rhId: Long?, gInfo: GroupInfo, member: GroupMember, mem
}
}
fun blockForAllAlert(rhId: Long?, gInfo: GroupInfo, mem: GroupMember) {
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.block_for_all_question),
text = generalGetString(MR.strings.block_member_desc).format(mem.chatViewName),
confirmText = generalGetString(MR.strings.block_for_all),
onConfirm = {
blockMemberForAll(rhId, gInfo, mem, true)
},
destructive = true,
)
}
fun unblockForAllAlert(rhId: Long?, gInfo: GroupInfo, mem: GroupMember) {
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.unblock_for_all_question),
text = generalGetString(MR.strings.unblock_member_desc).format(mem.chatViewName),
confirmText = generalGetString(MR.strings.unblock_for_all),
onConfirm = {
blockMemberForAll(rhId, gInfo, mem, false)
},
)
}
fun blockMemberForAll(rhId: Long?, gInfo: GroupInfo, member: GroupMember, blocked: Boolean) {
withBGApi {
val updatedMember = ChatController.apiBlockMemberForAll(rhId, gInfo.groupId, member.groupMemberId, blocked)
chatModel.upsertGroupMember(rhId, gInfo, updatedMember)
}
}
@Preview
@Composable
fun PreviewGroupMemberInfoLayout() {
@ -570,6 +674,8 @@ fun PreviewGroupMemberInfoLayout() {
connectViaAddress = {},
blockMember = {},
unblockMember = {},
blockForAll = {},
unblockForAll = {},
removeMember = {},
onRoleSelected = {},
switchMemberAddress = {},

View File

@ -319,7 +319,7 @@ fun ChatItemView(
}
}
@Composable fun DeletedItem() {
@Composable fun LegacyDeletedItem() {
DeletedItemView(cItem, cInfo.timedMessagesTTL)
DefaultDropdownMenu(showMenu) {
ItemInfoAction(cInfo, cItem, showItemDetails, showMenu)
@ -371,7 +371,7 @@ fun ChatItemView(
}
@Composable
fun ModeratedItem() {
fun DeletedItem() {
MarkedDeletedItemView(cItem, cInfo.timedMessagesTTL, revealed)
DefaultDropdownMenu(showMenu) {
ItemInfoAction(cInfo, cItem, showItemDetails, showMenu)
@ -382,8 +382,8 @@ fun ChatItemView(
when (val c = cItem.content) {
is CIContent.SndMsgContent -> ContentItem()
is CIContent.RcvMsgContent -> ContentItem()
is CIContent.SndDeleted -> DeletedItem()
is CIContent.RcvDeleted -> DeletedItem()
is CIContent.SndDeleted -> LegacyDeletedItem()
is CIContent.RcvDeleted -> LegacyDeletedItem()
is CIContent.SndCall -> CallItem(c.status, c.duration)
is CIContent.RcvCall -> CallItem(c.status, c.duration)
is CIContent.RcvIntegrityError -> if (developerTools) {
@ -449,8 +449,9 @@ fun ChatItemView(
CIChatFeatureView(cItem, c.groupFeature, Color.Red, revealed = revealed, showMenu = showMenu)
MsgContentItemDropdownMenu()
}
is CIContent.SndModerated -> ModeratedItem()
is CIContent.RcvModerated -> ModeratedItem()
is CIContent.SndModerated -> DeletedItem()
is CIContent.RcvModerated -> DeletedItem()
is CIContent.RcvBlocked -> DeletedItem()
is CIContent.InvalidJSON -> CIInvalidJSONView(c.json)
}
}

View File

@ -209,7 +209,10 @@ fun FramedItemView(
is CIDeleted.Blocked -> {
FramedItemHeader(stringResource(MR.strings.blocked_item_description), true, painterResource(MR.images.ic_back_hand))
}
else -> {
is CIDeleted.BlockedByAdmin -> {
FramedItemHeader(stringResource(MR.strings.blocked_by_admin_item_description), true, painterResource(MR.images.ic_back_hand))
}
is CIDeleted.Deleted -> {
FramedItemHeader(stringResource(MR.strings.marked_deleted_description), true, painterResource(MR.images.ic_delete))
}
}

View File

@ -48,6 +48,7 @@ private fun MergedMarkedDeletedText(chatItem: ChatItem, revealed: MutableState<B
val reversedChatItems = ChatModel.chatItems.asReversed()
var moderated = 0
var blocked = 0
var blockedByAdmin = 0
var deleted = 0
val moderatedBy: MutableSet<String> = mutableSetOf()
while (i < reversedChatItems.size) {
@ -59,16 +60,19 @@ private fun MergedMarkedDeletedText(chatItem: ChatItem, revealed: MutableState<B
moderatedBy.add(itemDeleted.byGroupMember.displayName)
}
is CIDeleted.Blocked -> blocked += 1
is CIDeleted.BlockedByAdmin -> blockedByAdmin +=1
is CIDeleted.Deleted -> deleted += 1
}
i++
}
val total = moderated + blocked + deleted
val total = moderated + blocked + blockedByAdmin + deleted
if (total <= 1)
markedDeletedText(chatItem.meta)
else if (total == moderated)
stringResource(MR.strings.moderated_items_description).format(total, moderatedBy.joinToString(", "))
else if (total == blocked)
else if (total == blockedByAdmin)
stringResource(MR.strings.blocked_by_admin_items_description).format(total)
else if (total == blocked + blockedByAdmin)
stringResource(MR.strings.blocked_items_description).format(total)
else
stringResource(MR.strings.marked_deleted_items_description).format(total)
@ -93,7 +97,9 @@ private fun markedDeletedText(meta: CIMeta): String =
String.format(generalGetString(MR.strings.moderated_item_description), meta.itemDeleted.byGroupMember.displayName)
is CIDeleted.Blocked ->
generalGetString(MR.strings.blocked_item_description)
else ->
is CIDeleted.BlockedByAdmin ->
generalGetString(MR.strings.blocked_by_admin_item_description)
is CIDeleted.Deleted, null ->
generalGetString(MR.strings.marked_deleted_description)
}

View File

@ -35,7 +35,9 @@
<string name="moderated_item_description">moderated by %s</string>
<string name="moderated_items_description">%1$d messages moderated by %2$s</string>
<string name="blocked_item_description">blocked</string>
<string name="blocked_by_admin_item_description">blocked by admin</string>
<string name="blocked_items_description">%d messages blocked</string>
<string name="blocked_by_admin_items_description">%d messages blocked by admin</string>
<string name="sending_files_not_yet_supported">sending files is not supported yet</string>
<string name="receiving_files_not_yet_supported">receiving files is not supported yet</string>
<string name="sender_you_pronoun">you</string>
@ -1176,6 +1178,8 @@
<string name="rcv_group_event_member_connected">connected</string>
<string name="rcv_group_event_member_left">left</string>
<string name="rcv_group_event_changed_member_role">changed role of %s to %s</string>
<string name="rcv_group_event_member_blocked">blocked %s</string>
<string name="rcv_group_event_member_unblocked">unblocked %s</string>
<string name="rcv_group_event_changed_your_role">changed your role to %s</string>
<string name="rcv_group_event_member_deleted">removed %1$s</string>
<string name="rcv_group_event_user_deleted">removed you</string>
@ -1185,6 +1189,8 @@
<string name="rcv_group_event_member_created_contact">connected directly</string>
<string name="snd_group_event_changed_member_role">you changed role of %s to %s</string>
<string name="snd_group_event_changed_role_for_yourself">you changed role for yourself to %s</string>
<string name="snd_group_event_member_blocked">you blocked %s</string>
<string name="snd_group_event_member_unblocked">you unblocked %s</string>
<string name="snd_group_event_member_deleted">you removed %1$s</string>
<string name="snd_group_event_user_left">you left</string>
<string name="snd_group_event_group_profile_updated">group profile updated</string>
@ -1339,11 +1345,17 @@
<string name="block_member_question">Block member?</string>
<string name="block_member_button">Block member</string>
<string name="block_member_confirmation">Block</string>
<string name="block_for_all_question">Block member for all?</string>
<string name="block_for_all">Block for all</string>
<string name="block_member_desc">All new messages from %s will be hidden!</string>
<string name="unblock_member_question">Unblock member?</string>
<string name="unblock_member_button">Unblock member</string>
<string name="unblock_member_confirmation">Unblock</string>
<string name="unblock_for_all_question">Unblock member for all?</string>
<string name="unblock_for_all">Unblock for all</string>
<string name="unblock_member_desc">Messages from %s will be shown!</string>
<string name="member_blocked_by_admin">Blocked by admin</string>
<string name="member_info_member_blocked">blocked</string>
<string name="member_info_section_title_member">MEMBER</string>
<string name="role_in_group">Role</string>
<string name="change_role">Change role</string>
@ -1356,6 +1368,7 @@
<string name="connect_via_member_address_alert_desc">Сonnection request will be sent to this group member.</string>
<string name="error_removing_member">Error removing member</string>
<string name="error_changing_role">Error changing role</string>
<string name="error_blocking_member_for_all">Error blocking member for all</string>
<string name="info_row_group">Group</string>
<string name="info_row_connection">Connection</string>
<string name="conn_level_desc_direct">direct</string>