mobile: refine allowed group actions; inactive group indicator (#852)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user