android: Change member role (#1186)

* android: Change member role

* Don't do anything when role is unchanged
This commit is contained in:
Stanislav Dmitrenko
2022-10-07 23:05:48 +03:00
committed by GitHub
parent af17daafaa
commit ca423024c5
8 changed files with 166 additions and 15 deletions

View File

@@ -733,12 +733,18 @@ class GroupMember (
GroupMemberStatus.MemCreator -> true
}
fun canBeRemoved(membership: GroupMember): Boolean {
val userRole = membership.memberRole
fun canBeRemoved(groupInfo: GroupInfo): Boolean {
val userRole = groupInfo.membership.memberRole
return memberStatus != GroupMemberStatus.MemRemoved && memberStatus != GroupMemberStatus.MemLeft
&& userRole >= GroupMemberRole.Admin && userRole >= memberRole && membership.memberCurrent
&& userRole >= GroupMemberRole.Admin && userRole >= memberRole && groupInfo.membership.memberCurrent
}
fun canChangeRoleTo(groupInfo: GroupInfo): List<GroupMemberRole>? =
if (!canBeRemoved(groupInfo)) null
else groupInfo.membership.memberRole.let { userRole ->
GroupMemberRole.values().filter { it <= userRole }
}
val memberIncognito = memberProfile.profileId != memberContactProfileId
companion object {
@@ -1025,6 +1031,8 @@ data class ChatItem (
is RcvGroupEvent.GroupDeleted -> false
is RcvGroupEvent.MemberAdded -> false
is RcvGroupEvent.MemberLeft -> false
is RcvGroupEvent.MemberRole -> true
is RcvGroupEvent.UserRole -> false
is RcvGroupEvent.MemberDeleted -> false
}
is CIContent.SndGroupEventContent -> true
@@ -1527,6 +1535,8 @@ sealed class RcvGroupEvent() {
@Serializable @SerialName("memberAdded") class MemberAdded(val groupMemberId: Long, val profile: Profile): 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("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()
@Serializable @SerialName("groupDeleted") class GroupDeleted(): RcvGroupEvent()
@@ -1536,6 +1546,8 @@ sealed class RcvGroupEvent() {
is MemberAdded -> String.format(generalGetString(R.string.rcv_group_event_member_added), profile.profileViewName)
is MemberConnected -> generalGetString(R.string.rcv_group_event_member_connected)
is MemberLeft -> generalGetString(R.string.rcv_group_event_member_left)
is MemberRole -> String.format(generalGetString(R.string.member_role), profile.profileViewName, role.text)
is UserRole -> String.format(generalGetString(R.string.your_member_role), role.text)
is MemberDeleted -> String.format(generalGetString(R.string.rcv_group_event_member_deleted), profile.profileViewName)
is UserDeleted -> generalGetString(R.string.rcv_group_event_user_deleted)
is GroupDeleted -> generalGetString(R.string.rcv_group_event_group_deleted)
@@ -1545,11 +1557,15 @@ sealed class RcvGroupEvent() {
@Serializable
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("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()
val text: String get() = when (this) {
is MemberRole -> String.format(generalGetString(R.string.member_role), profile.profileViewName, role.text)
is UserRole -> String.format(generalGetString(R.string.your_member_role), role.text)
is MemberDeleted -> String.format(generalGetString(R.string.snd_group_event_member_deleted), profile.profileViewName)
is UserLeft -> generalGetString(R.string.snd_group_event_user_left)
is GroupUpdated -> generalGetString(R.string.snd_group_event_group_profile_updated)

View File

@@ -785,12 +785,27 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
}
}
suspend fun apiRemoveMember(groupId: Long, memberId: Long): GroupMember? {
val r = sendCmd(CC.ApiRemoveMember(groupId, memberId))
if (r is CR.UserDeletedMember) return r.member
Log.e(TAG, "apiRemoveMember bad response: ${r.responseType} ${r.details}")
return null
}
suspend fun apiRemoveMember(groupId: Long, memberId: Long): GroupMember? =
when (val r = sendCmd(CC.ApiRemoveMember(groupId, memberId))) {
is CR.UserDeletedMember -> r.member
else -> {
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiRemoveMember", generalGetString(R.string.error_removing_member), r)
}
null
}
}
suspend fun apiMemberRole(groupId: Long, memberId: Long, memberRole: GroupMemberRole): GroupMember =
when (val r = sendCmd(CC.ApiMemberRole(groupId, memberId, memberRole))) {
is CR.MemberRoleUser -> r.member
else -> {
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiMemberRole", generalGetString(R.string.error_changing_role), r)
}
throw Exception("failed to change member role: ${r.responseType} ${r.details}")
}
}
suspend fun apiLeaveGroup(groupId: Long): GroupInfo? {
val r = sendCmd(CC.ApiLeaveGroup(groupId))
@@ -1342,7 +1357,7 @@ sealed class CC {
class NewGroup(val groupProfile: GroupProfile): 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 ApiMemberRole(val groupId: Long, val memberId: Long, val memberRole: GroupMemberRole): CC()
class ApiRemoveMember(val groupId: Long, val memberId: Long): CC()
class ApiLeaveGroup(val groupId: Long): CC()
class ApiListMembers(val groupId: Long): CC()
@@ -1400,6 +1415,7 @@ sealed class CC {
is NewGroup -> "/_group ${json.encodeToString(groupProfile)}"
is ApiAddMember -> "/_add #$groupId $contactId ${memberRole.memberRole}"
is ApiJoinGroup -> "/_join #$groupId"
is ApiMemberRole -> "/_member role #$groupId $memberId ${memberRole.memberRole}"
is ApiRemoveMember -> "/_remove #$groupId $memberId"
is ApiLeaveGroup -> "/_leave #$groupId"
is ApiListMembers -> "/_members #$groupId"
@@ -1458,6 +1474,7 @@ sealed class CC {
is NewGroup -> "newGroup"
is ApiAddMember -> "apiAddMember"
is ApiJoinGroup -> "apiJoinGroup"
is ApiMemberRole -> "apiMemberRole"
is ApiRemoveMember -> "apiRemoveMember"
is ApiLeaveGroup -> "apiLeaveGroup"
is ApiListMembers -> "apiListMembers"
@@ -1710,6 +1727,8 @@ sealed class CR {
@Serializable @SerialName("receivedGroupInvitation") class ReceivedGroupInvitation(val groupInfo: GroupInfo, val contact: Contact, val memberRole: GroupMemberRole): CR()
@Serializable @SerialName("groupDeletedUser") class GroupDeletedUser(val groupInfo: GroupInfo): CR()
@Serializable @SerialName("joinedGroupMemberConnecting") class JoinedGroupMemberConnecting(val groupInfo: GroupInfo, val hostMember: GroupMember, val member: GroupMember): CR()
@Serializable @SerialName("memberRole") class MemberRole(val groupInfo: GroupInfo, val byMember: GroupMember, val member: GroupMember, val fromRole: GroupMemberRole, val toRole: GroupMemberRole): CR()
@Serializable @SerialName("memberRoleUser") class MemberRoleUser(val groupInfo: GroupInfo, val member: GroupMember, val fromRole: GroupMemberRole, val toRole: GroupMemberRole): CR()
@Serializable @SerialName("deletedMemberUser") class DeletedMemberUser(val groupInfo: GroupInfo, val member: GroupMember): CR()
@Serializable @SerialName("deletedMember") class DeletedMember(val groupInfo: GroupInfo, val byMember: GroupMember, val deletedMember: GroupMember): CR()
@Serializable @SerialName("leftMember") class LeftMember(val groupInfo: GroupInfo, val member: GroupMember): CR()
@@ -1799,6 +1818,8 @@ sealed class CR {
is ReceivedGroupInvitation -> "receivedGroupInvitation"
is GroupDeletedUser -> "groupDeletedUser"
is JoinedGroupMemberConnecting -> "joinedGroupMemberConnecting"
is MemberRole -> "memberRole"
is MemberRoleUser -> "memberRoleUser"
is DeletedMemberUser -> "deletedMemberUser"
is DeletedMember -> "deletedMember"
is LeftMember -> "leftMember"
@@ -1887,6 +1908,8 @@ sealed class CR {
is ReceivedGroupInvitation -> "groupInfo: $groupInfo\ncontact: $contact\nmemberRole: $memberRole"
is GroupDeletedUser -> json.encodeToString(groupInfo)
is JoinedGroupMemberConnecting -> "groupInfo: $groupInfo\nhostMember: $hostMember\nmember: $member"
is MemberRole -> "groupInfo: $groupInfo\nbyMember: $byMember\nmember: $member\nfromRole: $fromRole\ntoRole: $toRole"
is MemberRoleUser -> "groupInfo: $groupInfo\nmember: $member\nfromRole: $fromRole\ntoRole: $toRole"
is DeletedMemberUser -> "groupInfo: $groupInfo\nmember: $member"
is DeletedMember -> "groupInfo: $groupInfo\nbyMember: $byMember\ndeletedMember: $deletedMember"
is LeftMember -> "groupInfo: $groupInfo\nmember: $member"

View File

@@ -138,7 +138,7 @@ fun AddGroupMembersLayout(
}
@Composable
fun RoleSelectionRow(groupInfo: GroupInfo, selectedRole: MutableState<GroupMemberRole>) {
private fun RoleSelectionRow(groupInfo: GroupInfo, selectedRole: MutableState<GroupMemberRole>) {
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,

View File

@@ -2,6 +2,7 @@ package chat.simplex.app.views.chat.group
import InfoRow
import SectionDivider
import SectionItemView
import SectionSpacer
import SectionView
import androidx.activity.compose.BackHandler
@@ -10,7 +11,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -40,10 +41,12 @@ fun GroupMemberInfoView(
val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value }
val developerTools = chatModel.controller.appPrefs.developerTools.get()
if (chat != null) {
val newRole = remember { mutableStateOf(member.memberRole) }
GroupMemberInfoLayout(
groupInfo,
member,
connStats,
newRole,
developerTools,
openDirectChat = {
withApi {
@@ -61,7 +64,24 @@ fun GroupMemberInfoView(
closeAll()
}
},
removeMember = { removeMemberDialog(groupInfo, member, chatModel, close) }
removeMember = { removeMemberDialog(groupInfo, member, chatModel, close) },
onRoleSelected = {
if (it == newRole.value) return@GroupMemberInfoLayout
val prevValue = newRole.value
newRole.value = it
updateMemberRoleDialog(it, member, onDismiss = {
newRole.value = prevValue
}) {
withApi {
kotlin.runCatching {
val mem = chatModel.controller.apiMemberRole(groupInfo.groupId, member.groupMemberId, it)
chatModel.upsertGroupMember(groupInfo, mem)
}.onFailure {
newRole.value = prevValue
}
}
}
}
)
}
}
@@ -88,9 +108,11 @@ fun GroupMemberInfoLayout(
groupInfo: GroupInfo,
member: GroupMember,
connStats: ConnectionStats?,
newRole: MutableState<GroupMemberRole>,
developerTools: Boolean,
openDirectChat: () -> Unit,
removeMember: () -> Unit,
onRoleSelected: (GroupMemberRole) -> Unit,
) {
Column(
Modifier
@@ -113,6 +135,15 @@ fun GroupMemberInfoLayout(
SectionView(title = stringResource(R.string.member_info_section_title_member)) {
InfoRow(stringResource(R.string.info_row_group), groupInfo.displayName)
SectionDivider()
val roles = remember { member.canChangeRoleTo(groupInfo) }
if (roles != null) {
SectionItemView {
RoleSelectionRow(roles, newRole, onRoleSelected)
}
} else {
InfoRow(stringResource(R.string.role_in_group), member.memberRole.text)
}
val conn = member.activeConn
if (conn != null) {
SectionDivider()
@@ -143,7 +174,7 @@ fun GroupMemberInfoLayout(
}
}
if (member.canBeRemoved(groupInfo.membership)) {
if (member.canBeRemoved(groupInfo)) {
SectionView {
RemoveMemberButton(removeMember)
}
@@ -207,6 +238,48 @@ fun OpenChatButton(onClick: () -> Unit) {
)
}
@Composable
private fun RoleSelectionRow(
roles: List<GroupMemberRole>,
selectedRole: MutableState<GroupMemberRole>,
onSelected: (GroupMemberRole) -> Unit
) {
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
val values = remember { roles.map { it to it.text } }
ExposedDropDownSettingRow(
generalGetString(R.string.change_role),
values,
selectedRole,
icon = null,
enabled = remember { mutableStateOf(true) },
onSelected = onSelected
)
}
}
private fun updateMemberRoleDialog(
newRole: GroupMemberRole,
member: GroupMember,
onDismiss: () -> Unit,
onConfirm: () -> Unit
) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.change_member_role_question),
text = if (member.memberCurrent)
String.format(generalGetString(R.string.member_role_will_be_changed_with_notification), newRole.text)
else
String.format(generalGetString(R.string.member_role_will_be_changed_with_invitation), newRole.text),
confirmText = generalGetString(R.string.change_verb),
onDismiss = onDismiss,
onConfirm = onConfirm,
onDismissRequest = onDismiss
)
}
@Preview
@Composable
fun PreviewGroupMemberInfoLayout() {
@@ -215,9 +288,11 @@ fun PreviewGroupMemberInfoLayout() {
groupInfo = GroupInfo.sampleData,
member = GroupMember.sampleData,
connStats = null,
newRole = remember { mutableStateOf(GroupMemberRole.Member) },
developerTools = false,
openDirectChat = {},
removeMember = {}
removeMember = {},
onRoleSelected = {}
)
}
}

View File

@@ -92,6 +92,13 @@ class AlertManager {
}
}
fun showAlertMsg(
title: Int,
text: Int? = null,
confirmText: Int = R.string.ok,
onConfirm: (() -> Unit)? = null
) = showAlertMsg(generalGetString(title), if (text != null) generalGetString(text) else null, generalGetString(confirmText), onConfirm)
@Composable
fun showInView() {
if (presentAlert.value) alertView.value?.invoke()

View File

@@ -770,6 +770,16 @@
<string name="member_will_be_removed_from_group_cannot_be_undone">Das Mitglied wird aus der Gruppe entfernt - dies kann nicht rückgängig gemacht werden!</string>
<string name="remove_member_confirmation">Entfernen</string>
<string name="member_info_section_title_member">MITGLIED</string>
<string name="role_in_group">Role</string>
<string name="change_role">Change role</string>
<string name="change_verb">Change</string>
<string name="change_member_role_question">Change group role?</string>
<string name="member_role_will_be_changed_with_notification">The role will be changed to \"%s\". Everyone in the group will be notified.</string>
<string name="member_role_will_be_changed_with_invitation">The role will be changed to \"%s\". The member will receive a new invitation.</string>
<string name="error_removing_member">Error removing member</string>
<string name="error_changing_role">Error changing role</string>
<string name="member_role">member %s role: %s</string>
<string name="your_member_role">your role: %s</string>
<string name="info_row_group">Gruppe</string>
<string name="info_row_connection">Verbindung</string>
<string name="conn_level_desc_direct">direkt</string>

View File

@@ -770,6 +770,16 @@
<string name="member_will_be_removed_from_group_cannot_be_undone">Член группы будет удален - это действие нельзя отменить!</string>
<string name="remove_member_confirmation">Удалить</string>
<string name="member_info_section_title_member">ЧЛЕН ГРУППЫ</string>
<string name="role_in_group">Роль</string>
<string name="change_role">Поменять роль</string>
<string name="change_verb">Поменять</string>
<string name="change_member_role_question">Поменять роль в группе?</string>
<string name="member_role_will_be_changed_with_notification">Роль будет изменена на \"%s\". Все в группе получат сообщение.</string>
<string name="member_role_will_be_changed_with_invitation">Роль будет изменена на \"%s\". Будет отправлено новое приглашение.</string>
<string name="error_removing_member">Ошибка при удалении члена группы</string>
<string name="error_changing_role">Ошибка при изменении роли</string>
<string name="member_role">роль %s: %s</string>
<string name="your_member_role">ваша роль: %s</string>
<string name="info_row_group">Группа</string>
<string name="info_row_connection">Соединение</string>
<string name="conn_level_desc_direct">прямое</string>

View File

@@ -770,6 +770,16 @@
<string name="member_will_be_removed_from_group_cannot_be_undone">Member will be removed from group - this cannot be undone!</string>
<string name="remove_member_confirmation">Remove</string>
<string name="member_info_section_title_member">MEMBER</string>
<string name="role_in_group">Role</string>
<string name="change_role">Change role</string>
<string name="change_verb">Change</string>
<string name="change_member_role_question">Change group role?</string>
<string name="member_role_will_be_changed_with_notification">The role will be changed to \"%s\". Everyone in the group will be notified.</string>
<string name="member_role_will_be_changed_with_invitation">The role will be changed to \"%s\". The member will receive a new invitation.</string>
<string name="error_removing_member">Error removing member</string>
<string name="error_changing_role">Error changing role</string>
<string name="member_role">member %s role: %s</string>
<string name="your_member_role">your role: %s</string>
<string name="info_row_group">Group</string>
<string name="info_row_connection">Connection</string>
<string name="conn_level_desc_direct">direct</string>