mobile: refine allowed group actions; inactive group indicator (#852)

This commit is contained in:
JRoberts
2022-07-30 16:49:34 +04:00
committed by GitHub
parent de0f231c60
commit 1dd7520bbd
13 changed files with 110 additions and 76 deletions

View File

@@ -487,7 +487,7 @@ class Profile(
override val fullName: String,
override val image: String? = null
): NamedChat {
val displayNameWithOptionalFullName: String
val profileViewName: String
get() {
return if (fullName == "" || displayName == fullName) displayName else "$displayName ($fullName)"
}
@@ -525,11 +525,7 @@ class GroupInfo (
override val image get() = groupProfile.image
val canDelete: Boolean
get() {
val s = membership.memberStatus
return membership.memberRole == GroupMemberRole.Owner
|| (s == GroupMemberStatus.MemRemoved || s == GroupMemberStatus.MemLeft || s == GroupMemberStatus.MemGroupDeleted || s == GroupMemberStatus.MemInvited)
}
get() = membership.memberRole == GroupMemberRole.Owner || !membership.memberCurrent
val canAddMembers: Boolean
get() = membership.memberRole >= GroupMemberRole.Admin && membership.memberActive
@@ -610,8 +606,11 @@ class GroupMember (
GroupMemberStatus.MemCreator -> true
}
fun canRemove(userRole: GroupMemberRole): Boolean =
userRole >= GroupMemberRole.Admin && userRole >= memberRole
fun canBeRemoved(membership: GroupMember): Boolean {
val userRole = membership.memberRole
return memberStatus != GroupMemberStatus.MemRemoved && memberStatus != GroupMemberStatus.MemLeft
&& userRole >= GroupMemberRole.Admin && userRole >= memberRole && membership.memberCurrent
}
companion object {
val sampleData = GroupMember(
@@ -1367,10 +1366,10 @@ sealed class RcvGroupEvent() {
@Serializable @SerialName("groupDeleted") class GroupDeleted(): RcvGroupEvent()
val text: String get() = when (this) {
is MemberAdded -> String.format(generalGetString(R.string.rcv_group_event_member_added), profile.displayNameWithOptionalFullName)
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 MemberDeleted -> String.format(generalGetString(R.string.rcv_group_event_member_deleted), profile.displayNameWithOptionalFullName)
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)
}
@@ -1382,7 +1381,7 @@ sealed class SndGroupEvent() {
@Serializable @SerialName("userLeft") class UserLeft(): SndGroupEvent()
val text: String get() = when (this) {
is MemberDeleted -> String.format(generalGetString(R.string.snd_group_event_member_deleted), profile.displayNameWithOptionalFullName)
is MemberDeleted -> String.format(generalGetString(R.string.snd_group_event_member_deleted), profile.profileViewName)
is UserLeft -> generalGetString(R.string.snd_group_event_user_left)
}
}

View File

@@ -152,7 +152,7 @@ fun GroupChatInfoLayout(
DeleteGroupButton(deleteGroup)
}
}
if (groupInfo.membership.memberStatus != GroupMemberStatus.MemLeft) {
if (groupInfo.membership.memberCurrent) {
SectionDivider()
SectionItemView {
LeaveGroupButton(leaveGroup)
@@ -190,8 +190,6 @@ fun AddMembersButton(addMembers: () -> Unit) {
@Composable
fun MembersList(members: List<GroupMember>, showMemberInfo: (GroupMember) -> Unit) {
// LazyColumn {
// itemsIndexed(members) { index, member ->
Column {
members.forEachIndexed { index, member ->
SectionItemView(height = 50.dp) {

View File

@@ -107,7 +107,7 @@ fun GroupMemberInfoLayout(
}
}
if (member.canRemove(userRole = groupInfo.membership.memberRole) && member.memberStatus != GroupMemberStatus.MemRemoved) {
if (member.canBeRemoved(groupInfo.membership)) {
SectionView {
SectionItemView {
RemoveMemberButton(removeMember)

View File

@@ -125,7 +125,7 @@ fun GroupMenuItems(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, showM
MarkReadChatAction(chat, chatModel, showMenu)
}
ClearChatAction(chat, chatModel, showMenu)
if (groupInfo.membership.memberStatus != GroupMemberStatus.MemLeft) {
if (groupInfo.membership.memberCurrent) {
LeaveGroupAction(groupInfo, chatModel, showMenu)
}
if (groupInfo.canDelete) {

View File

@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.RemoveCircle
import androidx.compose.material.icons.outlined.ErrorOutline
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -29,6 +30,28 @@ import chat.simplex.app.views.helpers.badgeLayout
fun ChatPreviewView(chat: Chat, stopped: Boolean) {
val cInfo = chat.chatInfo
@Composable
fun groupInactiveIcon() {
Icon(
Icons.Filled.RemoveCircle,
stringResource(R.string.icon_descr_group_inactive),
Modifier.size(18.dp).background(MaterialTheme.colors.background, CircleShape),
tint = Color.Red
)
}
@Composable
fun chatPreviewImageOverlayIcon() {
if (cInfo is ChatInfo.Group) {
when (cInfo.groupInfo.membership.memberStatus) {
GroupMemberStatus.MemLeft -> groupInactiveIcon()
GroupMemberStatus.MemRemoved -> groupInactiveIcon()
GroupMemberStatus.MemGroupDeleted -> groupInactiveIcon()
else -> {}
}
}
}
@Composable
fun chatPreviewTitleText(color: Color = Color.Unspecified) {
Text(
@@ -50,18 +73,6 @@ fun ChatPreviewView(chat: Chat, stopped: Boolean) {
when (cInfo.groupInfo.membership.memberStatus) {
GroupMemberStatus.MemInvited -> chatPreviewTitleText(MaterialTheme.colors.primary)
GroupMemberStatus.MemAccepted -> chatPreviewTitleText(HighOrLowlight)
GroupMemberStatus.MemLeft ->
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
stringResource(R.string.group_left_description),
style = MaterialTheme.typography.h3,
fontWeight = FontWeight.Bold,
color = HighOrLowlight
)
chatPreviewTitleText()
}
else -> chatPreviewTitleText()
}
else -> chatPreviewTitleText()
@@ -87,7 +98,7 @@ fun ChatPreviewView(chat: Chat, stopped: Boolean) {
}
is ChatInfo.Group ->
when (cInfo.groupInfo.membership.memberStatus) {
GroupMemberStatus.MemInvited -> Text(stringResource(R.string.you_are_invited_to_group))
GroupMemberStatus.MemInvited -> Text(stringResource(R.string.group_preview_you_are_invited))
GroupMemberStatus.MemAccepted -> Text(stringResource(R.string.group_connection_pending), color = HighOrLowlight)
else -> {}
}
@@ -97,7 +108,12 @@ fun ChatPreviewView(chat: Chat, stopped: Boolean) {
}
Row {
ChatInfoImage(cInfo, size = 72.dp)
Box(contentAlignment = Alignment.BottomEnd) {
ChatInfoImage(cInfo, size = 72.dp)
Box(Modifier.padding(end = 6.dp, bottom = 6.dp)) {
chatPreviewImageOverlayIcon()
}
}
Column(
modifier = Modifier
.padding(horizontal = 8.dp)

View File

@@ -112,6 +112,7 @@
<string name="this_text_is_available_in_settings">Этот текст можно найти в Настройках</string>
<string name="your_chats">Ваши чаты</string>
<string name="contact_connection_pending">соединяется…</string>
<string name="group_preview_you_are_invited">вы приглашены в группу</string>
<string name="group_connection_pending">соединяется…</string>
<!-- ComposeView.kt, helpers -->
@@ -506,8 +507,8 @@
<string name="leave_group_button">Выйти</string>
<string name="leave_group_question">Выйти из группы</string>
<string name="you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved">Вы перестанете получать сообщения от этой группы. История чата будет сохранена.</string>
<string name="group_left_description">[покинута]</string>
<string name="icon_descr_add_members">Пригласить участников</string>
<string name="icon_descr_group_inactive">Группа неактивна</string>
<!-- CIGroupInvitationView.kt -->
<string name="you_sent_group_invitation">Вы отправили приглашение в группу</string>

View File

@@ -112,6 +112,7 @@
<string name="this_text_is_available_in_settings">This text is available in settings</string>
<string name="your_chats">Your chats</string>
<string name="contact_connection_pending">connecting…</string>
<string name="group_preview_you_are_invited">you are invited to group</string>
<string name="group_connection_pending">connecting…</string>
<!-- ComposeView.kt, helpers -->
@@ -508,8 +509,8 @@
<string name="leave_group_button">Leave</string>
<string name="leave_group_question">Leave group?</string>
<string name="you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved">You will stop receiving messages from this group. Chat history will be preserved.</string>
<string name="group_left_description">[left]</string>
<string name="icon_descr_add_members">Invite members</string>
<string name="icon_descr_group_inactive">Group inactive</string>
<!-- CIGroupInvitationView.kt -->
<string name="you_sent_group_invitation">You sent group invitation</string>

View File

@@ -48,8 +48,8 @@ struct GroupMemberInfoView: View {
}
}
Section {
if member.canRemove(userRole: groupInfo.membership.memberRole) && member.memberStatus != .memRemoved {
if member.canBeRemoved(membership: groupInfo.membership) {
Section {
removeMemberButton()
}
}

View File

@@ -35,19 +35,21 @@ struct GroupChatInfoView: View {
groupInfoHeader()
.listRowBackground(Color.clear)
Section {
Button {
showGroupProfile = true
} label: {
Label("Edit group profile", systemImage: "pencil")
if groupInfo.canEdit {
Section {
Button {
showGroupProfile = true
} label: {
Label("Edit group profile", systemImage: "pencil")
}
}
.sheet(isPresented: $showGroupProfile) {
GroupProfileView(groupId: groupInfo.apiId, groupProfile: groupInfo.groupProfile)
}
}
.sheet(isPresented: $showGroupProfile) {
GroupProfileView(groupId: groupInfo.apiId, groupProfile: groupInfo.groupProfile)
}
Section("\(members.count + 1) members") {
if (groupInfo.canAddMembers) {
if groupInfo.canAddMembers {
addMembersButton()
}
memberView(groupInfo.membership, user: true)
@@ -67,7 +69,7 @@ struct GroupChatInfoView: View {
if groupInfo.canDelete {
deleteGroupButton()
}
if (groupInfo.membership.memberStatus != .memLeft) {
if groupInfo.membership.memberCurrent {
leaveGroupButton()
}
}

View File

@@ -113,7 +113,7 @@ struct ChatListNavLink: View {
clearChatButton()
}
.swipeActions(edge: .trailing) {
if (groupInfo.membership.memberStatus != .memLeft) {
if (groupInfo.membership.memberCurrent) {
Button {
AlertManager.shared.showAlert(leaveGroupAlert(groupInfo))
} label: {

View File

@@ -18,9 +18,14 @@ struct ChatPreviewView: View {
let cItem = chat.chatItems.last
let unread = chat.chatStats.unreadCount
return HStack(spacing: 8) {
ChatInfoImage(chat: chat)
.frame(width: 63, height: 63)
.padding(.leading, 4)
ZStack(alignment: .bottomTrailing) {
ChatInfoImage(chat: chat)
.frame(width: 63, height: 63)
chatPreviewImageOverlayIcon()
.padding([.bottom, .trailing], 1)
}
.padding(.leading, 4)
VStack(spacing: 0) {
HStack(alignment: .top) {
@@ -51,6 +56,28 @@ struct ChatPreviewView: View {
}
}
@ViewBuilder private func chatPreviewImageOverlayIcon() -> some View {
if case let .group(groupInfo) = chat.chatInfo {
switch (groupInfo.membership.memberStatus) {
case .memLeft:
groupInactiveIcon()
case .memRemoved:
groupInactiveIcon()
case .memGroupDeleted:
groupInactiveIcon()
default: EmptyView()
}
} else {
EmptyView()
}
}
@ViewBuilder private func groupInactiveIcon() -> some View {
Image(systemName: "minus.circle.fill")
.foregroundColor(.red)
.background(Circle().foregroundColor(Color(uiColor: .systemBackground)))
}
@ViewBuilder private func chatPreviewTitle() -> some View {
let v = Text(chat.chatInfo.chatViewName)
.font(.title3)
@@ -66,18 +93,6 @@ struct ChatPreviewView: View {
v.foregroundColor(.accentColor)
case .memAccepted:
v.foregroundColor(.secondary)
case .memLeft:
HStack {
Text(NSLocalizedString("[left]", comment: "group left description"))
.font(.title3)
.fontWeight(.bold)
.foregroundColor(.secondary)
Text(chat.chatInfo.chatViewName)
.font(.title3)
.fontWeight(.bold)
.lineLimit(1)
}
.frame(maxHeight: .infinity, alignment: .topLeading)
default: v
}
default: v
@@ -106,12 +121,12 @@ struct ChatPreviewView: View {
switch (chat.chatInfo) {
case let .direct(contact):
if !contact.ready {
chatPreviewInfoText("Connecting...")
chatPreviewInfoText("connecting...")
}
case let .group(groupInfo):
switch (groupInfo.membership.memberStatus) {
case .memInvited: chatPreviewInfoText("You are invited to group")
case .memAccepted: chatPreviewInfoText("Connecting...")
case .memInvited: chatPreviewInfoText("you are invited to group")
case .memAccepted: chatPreviewInfoText("connecting...")
default: EmptyView()
}
default: EmptyView()

View File

@@ -44,7 +44,7 @@ public struct Profile: Codable, NamedChat {
public var fullName: String
public var image: String?
var displayNameWithOptionalFullName: String {
var profileViewName: String {
(fullName == "" || displayName == fullName) ? displayName : "\(displayName) (\(fullName))"
}
@@ -444,9 +444,12 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat {
public var fullName: String { get { groupProfile.fullName } }
public var image: String? { get { groupProfile.image } }
public var canEdit: Bool {
return membership.memberRole == .owner && membership.memberCurrent
}
public var canDelete: Bool {
let s = membership.memberStatus
return membership.memberRole == .owner || (s == .memRemoved || s == .memLeft || s == .memGroupDeleted || s == .memInvited)
return membership.memberRole == .owner || !membership.memberCurrent
}
public var canAddMembers: Bool {
@@ -547,8 +550,10 @@ public struct GroupMember: Identifiable, Decodable {
}
}
public func canRemove(userRole: GroupMemberRole) -> Bool {
return userRole >= .admin && userRole >= memberRole
public func canBeRemoved(membership: GroupMember) -> Bool {
let userRole = membership.memberRole
return memberStatus != .memRemoved && memberStatus != .memLeft
&& userRole >= .admin && userRole >= memberRole && membership.memberCurrent
}
public static let sampleData = GroupMember(
@@ -1271,11 +1276,11 @@ public enum RcvGroupEvent: Decodable {
var text: String {
switch self {
case let .memberAdded(_, profile):
return String.localizedStringWithFormat(NSLocalizedString("invited %@", comment: "rcv group event chat item"), profile.displayNameWithOptionalFullName)
return String.localizedStringWithFormat(NSLocalizedString("invited %@", comment: "rcv group event chat item"), profile.profileViewName)
case .memberConnected: return NSLocalizedString("member connected", comment: "rcv group event chat item")
case .memberLeft: return NSLocalizedString("left", comment: "rcv group event chat item")
case let .memberDeleted(_, profile):
return String.localizedStringWithFormat(NSLocalizedString("removed %@", comment: "rcv group event chat item"), profile.displayNameWithOptionalFullName)
return String.localizedStringWithFormat(NSLocalizedString("removed %@", comment: "rcv group event chat item"), profile.profileViewName)
case .userDeleted: return NSLocalizedString("removed you", comment: "rcv group event chat item")
case .groupDeleted: return NSLocalizedString("deleted group", comment: "rcv group event chat item")
case .groupUpdated: return NSLocalizedString("updated group profile", comment: "rcv group event chat item")
@@ -1291,7 +1296,7 @@ public enum SndGroupEvent: Decodable {
var text: String {
switch self {
case let .memberDeleted(_, profile):
return String.localizedStringWithFormat(NSLocalizedString("you removed %@", comment: "snd group event chat item"), profile.displayNameWithOptionalFullName)
return String.localizedStringWithFormat(NSLocalizedString("you removed %@", comment: "snd group event chat item"), profile.profileViewName)
case .userLeft: return NSLocalizedString("you left", comment: "snd group event chat item")
case .groupUpdated: return NSLocalizedString("group profile updated", comment: "snd group event chat item")
}