ios: group & group member info views (#841)

* ios: group member wip

* wip

* wip

* wip

* wip

* refactor alerts

* .navigationBarHidden(true)

* await MainActor.run

* refactor

* fix

* update layout

* tex

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
JRoberts
2022-07-26 12:33:10 +04:00
committed by GitHub
parent 608030dcaf
commit a4aaf36774
9 changed files with 364 additions and 88 deletions

View File

@@ -594,6 +594,12 @@ func apiJoinGroup(_ groupId: Int64) async throws -> GroupInfo {
throw r
}
func apiRemoveMember(groupId: Int64, memberId: Int64) async throws -> GroupMember {
let r = await chatSendCmd(.apiRemoveMember(groupId: groupId, memberId: memberId), bgTask: false)
if case let .userDeletedMember(_, member) = r { return member }
throw r
}
func leaveGroup(_ groupId: Int64) async {
do {
let groupInfo = try await apiLeaveGroup(groupId)

View File

@@ -13,10 +13,8 @@ struct ChatInfoView: View {
@EnvironmentObject var chatModel: ChatModel
@ObservedObject var alertManager = AlertManager.shared
@ObservedObject var chat: Chat
@Binding var chatViewSheet: ChatViewSheet?
@Binding var showSheet: Bool
@State var alert: ChatInfoViewAlert? = nil
@State var deletingContact: Contact?
var contact: Contact
enum ChatInfoViewAlert: Identifiable {
case deleteContactAlert
@@ -26,7 +24,7 @@ struct ChatInfoView: View {
}
var body: some View {
VStack{
VStack {
ChatInfoImage(chat: chat)
.frame(width: 192, height: 192)
.padding(.top, 48)
@@ -55,7 +53,6 @@ struct ChatInfoView: View {
}
.tint(Color.orange)
Button(role: .destructive) {
deletingContact = contact
alert = .deleteContactAlert
} label: {
Label("Delete contact", systemImage: "trash")
@@ -64,7 +61,7 @@ struct ChatInfoView: View {
}
.alert(item: $alert) { alertItem in
switch(alertItem) {
case .deleteContactAlert: return deleteContactAlert(deletingContact!)
case .deleteContactAlert: return deleteContactAlert()
case .clearChatAlert: return clearChatAlert()
}
}
@@ -77,20 +74,20 @@ struct ChatInfoView: View {
.foregroundColor(status == .connected ? .green : .secondary)
}
private func deleteContactAlert(_ contact: Contact) -> Alert {
private func deleteContactAlert() -> Alert {
Alert(
title: Text("Delete contact?"),
message: Text("Contact and all messages will be deleted - this cannot be undone!"),
primaryButton: .destructive(Text("Delete")) {
Task {
do {
try await apiDeleteChat(type: .direct, id: contact.apiId)
DispatchQueue.main.async {
chatModel.removeChat(contact.id)
chatViewSheet = nil
try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId)
await MainActor.run {
chatModel.removeChat(chat.chatInfo.id)
showSheet = false
}
} catch let error {
logger.error("ChatInfoView.deleteContactAlert apiDeleteChat error: \(error.localizedDescription)")
logger.error("deleteContactAlert apiDeleteChat error: \(error.localizedDescription)")
}
}
},
@@ -105,8 +102,8 @@ struct ChatInfoView: View {
primaryButton: .destructive(Text("Clear")) {
Task {
await clearChat(chat)
DispatchQueue.main.async {
chatViewSheet = nil
await MainActor.run {
showSheet = false
}
}
},
@@ -117,7 +114,7 @@ struct ChatInfoView: View {
struct ChatInfoView_Previews: PreviewProvider {
static var previews: some View {
@State var chatViewSheet = ChatViewSheet.chatInfo
return ChatInfoView(chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []), chatViewSheet: Binding($chatViewSheet), contact: Contact.sampleData)
@State var showSheet = true
return ChatInfoView(chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []), showSheet: $showSheet)
}
}

View File

@@ -22,6 +22,7 @@ struct ChatView: View {
@EnvironmentObject var chatModel: ChatModel
@Environment(\.colorScheme) var colorScheme
@ObservedObject var chat: Chat
@State private var showChatViewSheet: Bool = false
@State private var chatViewSheet: ChatViewSheet?
@State private var composeState = ComposeState()
@State private var deletingItem: ChatItem? = nil
@@ -107,19 +108,21 @@ struct ChatView: View {
ToolbarItem(placement: .principal) {
Button {
chatViewSheet = .chatInfo
showChatViewSheet = true
} label: {
ChatInfoToolbar(chat: chat)
}
.sheet(item: $chatViewSheet) { sheet in
switch sheet {
.sheet(isPresented: $showChatViewSheet) {
switch chatViewSheet {
case .chatInfo:
if case let .direct(contact) = chat.chatInfo {
ChatInfoView(chat: chat, chatViewSheet: $chatViewSheet, contact: contact)
} else if case .group = chat.chatInfo {
GroupChatInfoView(chat: chat, chatViewSheet: $chatViewSheet)
if case .direct = chat.chatInfo {
ChatInfoView(chat: chat, showSheet: $showChatViewSheet)
} else if case let .group(groupInfo) = chat.chatInfo {
GroupChatInfoView(chat: chat, groupInfo: groupInfo, showSheet: $showChatViewSheet)
}
case .addMember:
AddGroupMembersView(chat: chat, chatViewSheet: $chatViewSheet)
AddGroupMembersView(chat: chat, showSheet: $showChatViewSheet)
default: EmptyView()
}
}
}
@@ -130,7 +133,7 @@ struct ChatView: View {
callButton(contact, .video, imageName: "video")
}
} else if case .group = chat.chatInfo {
addMemberButton()
addMembersButton()
}
}
}
@@ -145,9 +148,10 @@ struct ChatView: View {
}
}
private func addMemberButton() -> some View {
private func addMembersButton() -> some View {
Button {
chatViewSheet = .addMember
showChatViewSheet = true
} label: {
Image(systemName: "person.crop.circle.badge.plus")
}

View File

@@ -12,7 +12,7 @@ import SimpleXChat
struct AddGroupMembersView: View {
@EnvironmentObject var chatModel: ChatModel
var chat: Chat
@Binding var chatViewSheet: ChatViewSheet?
@Binding var showSheet: Bool
@State private var contactsToAdd: [Contact] = []
@State private var selectedContacts = Set<Int64>()
@@ -35,7 +35,7 @@ struct AddGroupMembersView: View {
for contactId in selectedContacts {
await addMember(groupId: chat.chatInfo.apiId, contactId: contactId)
}
chatViewSheet = nil
showSheet = false
}
} label: {
Label(
@@ -103,7 +103,7 @@ struct AddGroupMembersView: View {
struct AddGroupMembersView_Previews: PreviewProvider {
static var previews: some View {
@State var chatViewSheet = ChatViewSheet.chatInfo
return AddGroupMembersView(chat: Chat(chatInfo: ChatInfo.sampleData.group), chatViewSheet: Binding($chatViewSheet))
@State var showSheet = true
return AddGroupMembersView(chat: Chat(chatInfo: ChatInfo.sampleData.group), showSheet: $showSheet)
}
}

View File

@@ -0,0 +1,104 @@
//
// GroupMemberInfoView.swift
// SimpleX (iOS)
//
// Created by JRoberts on 25.07.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct GroupMemberInfoView: View {
@EnvironmentObject var chatModel: ChatModel
var member: GroupMember
@State private var alert: GroupMemberInfoViewAlert? = nil
enum GroupMemberInfoViewAlert: Identifiable {
case removeMemberAlert
var id: GroupMemberInfoViewAlert { get { self } }
}
var body: some View {
NavigationView {
List {
groupMemberInfoHeader()
.listRowBackground(Color.clear)
// TODO server status
Section(header: Text("Info")) {
Text("Role: ") + Text(member.memberRole.text)
// TODO invited by - need to get contact by contact id
Text("Status: ") + Text(member.memberStatus.text)
if let conn = member.activeConn {
let connLevelDesc = conn.connLevel == 0 ? "Direct" : "Indirect (\(conn.connLevel))"
Text("Connection level: \(connLevelDesc)")
}
}
Section {
removeMemberButton()
}
}
.navigationBarHidden(true)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.alert(item: $alert) { alertItem in
switch(alertItem) {
case .removeMemberAlert: return removeMemberAlert()
}
}
}
func groupMemberInfoHeader() -> some View {
VStack {
ProfileImage(imageStr: member.image, color: Color(uiColor: .tertiarySystemFill))
.frame(width: 192, height: 192)
.padding(.top, 12)
.padding()
Text(member.localDisplayName)
.font(.largeTitle)
.lineLimit(1)
.padding(.bottom, 2)
Text(member.fullName)
.font(.title2)
.lineLimit(2)
}
.frame(maxWidth: .infinity, alignment: .center)
}
func removeMemberButton() -> some View {
Button(role: .destructive) {
alert = .removeMemberAlert
} label: {
Label("Remove member", systemImage: "trash")
.foregroundColor(Color.red)
}
}
private func removeMemberAlert() -> Alert {
Alert(
title: Text("Remove member?"),
message: Text("Member will be removed from group - this cannot be undone!"),
primaryButton: .destructive(Text("Remove")) {
Task {
do {
_ = try await apiRemoveMember(groupId: member.groupId, memberId: member.groupMemberId)
// TODO navigate back
} catch let error {
logger.error("removeMemberAlert apiRemoveMember error: \(error.localizedDescription)")
}
}
},
secondaryButton: .cancel()
)
}
}
struct GroupMemberInfoView_Previews: PreviewProvider {
static var previews: some View {
GroupMemberInfoView(member: GroupMember.sampleData)
}
}

View File

@@ -13,44 +13,83 @@ struct GroupChatInfoView: View {
@EnvironmentObject var chatModel: ChatModel
@ObservedObject var alertManager = AlertManager.shared
@ObservedObject var chat: Chat
@Binding var chatViewSheet: ChatViewSheet?
@State var alert: ChatInfoViewAlert? = nil
@State var deletingContact: Contact?
var groupInfo: GroupInfo
@Binding var showSheet: Bool
@State private var members: [GroupMember] = []
@State private var alert: GroupChatInfoViewAlert? = nil
@State private var showAddMembersSheet: Bool = false
enum ChatInfoViewAlert: Identifiable {
case deleteContactAlert
enum GroupChatInfoViewAlert: Identifiable {
case deleteGroupAlert
case clearChatAlert
case leaveGroupAlert
var id: ChatInfoViewAlert { get { self } }
var id: GroupChatInfoViewAlert { get { self } }
}
var body: some View {
VStack{
ChatInfoImage(chat: chat)
.frame(width: 192, height: 192)
.padding(.top, 48)
.padding()
Text(chat.chatInfo.localDisplayName).font(.largeTitle)
.padding(.bottom, 2)
Text(chat.chatInfo.fullName).font(.title)
.padding(.bottom)
NavigationView {
List {
groupInfoHeader()
.listRowBackground(Color.clear)
Spacer()
Button() {
alert = .clearChatAlert
} label: {
Label("Clear conversation", systemImage: "gobackward")
Section(header: Text("\(members.count) Members")) {
addMembersButton()
ForEach(members) { member in
memberView(member)
}
}
Section {
clearChatButton()
if groupInfo.canDelete {
deleteGroupButton()
}
leaveGroupButton()
}
}
.tint(Color.orange)
.padding()
.navigationBarHidden(true)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.sheet(isPresented: $showAddMembersSheet) {
AddGroupMembersView(chat: chat, showSheet: $showAddMembersSheet)
}
.alert(item: $alert) { alertItem in
switch(alertItem) {
case .deleteContactAlert: return deleteContactAlert(deletingContact!)
case .deleteGroupAlert: return deleteGroupAlert()
case .clearChatAlert: return clearChatAlert()
case .leaveGroupAlert: return leaveGroupAlert()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.task {
members = await apiListMembers(chat.chatInfo.apiId)
.sorted{ $0.displayName.lowercased() < $1.displayName.lowercased() } // TODO owner first
}
}
func groupInfoHeader() -> some View {
VStack {
ChatInfoImage(chat: chat, color: Color(uiColor: .tertiarySystemFill))
.frame(width: 192, height: 192)
.padding(.top, 12)
.padding()
Text(chat.chatInfo.localDisplayName)
.font(.largeTitle)
.lineLimit(1)
.padding(.bottom, 2)
Text(chat.chatInfo.fullName)
.font(.title2)
.lineLimit(2)
}
.frame(maxWidth: .infinity, alignment: .center)
}
private func addMembersButton() -> some View {
Button {
showAddMembersSheet = true
} label: {
Label("Invite members", systemImage: "plus")
}
}
func serverImage() -> some View {
@@ -59,20 +98,75 @@ struct GroupChatInfoView: View {
.foregroundColor(status == .connected ? .green : .secondary)
}
private func deleteContactAlert(_ contact: Contact) -> Alert {
func memberView(_ member: GroupMember) -> some View {
NavigationLink {
GroupMemberInfoView(member: member)
} label: {
HStack{
ProfileImage(imageStr: member.image)
.frame(width: 38, height: 38)
.padding(.trailing, 2)
// TODO server connection status
VStack(alignment: .leading) {
Text(member.chatViewName)
.lineLimit(1)
Text(member.memberStatus.shortText)
.lineLimit(1)
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
let role = member.memberRole
if role == .owner || role == .admin {
Text(member.memberRole.text)
}
}
}
}
func deleteGroupButton() -> some View {
Button(role: .destructive) {
alert = .deleteGroupAlert
} label: {
Label("Delete group", systemImage: "trash")
.foregroundColor(Color.red)
}
}
func clearChatButton() -> some View {
Button() {
alert = .clearChatAlert
} label: {
Label("Clear conversation", systemImage: "gobackward")
.foregroundColor(Color.orange)
}
.tint(Color.orange)
}
func leaveGroupButton() -> some View {
Button(role: .destructive) {
alert = .leaveGroupAlert
} label: {
Label("Leave group", systemImage: "rectangle.portrait.and.arrow.right")
.foregroundColor(Color.red)
}
}
// TODO reuse this and clearChatAlert with ChatInfoView
private func deleteGroupAlert() -> Alert {
Alert(
title: Text("Delete contact?"),
message: Text("Contact and all messages will be deleted - this cannot be undone!"),
title: Text("Delete group?"),
message: Text("Group will be deleted for all members - this cannot be undone!"),
primaryButton: .destructive(Text("Delete")) {
Task {
do {
try await apiDeleteChat(type: .direct, id: contact.apiId)
DispatchQueue.main.async {
chatModel.removeChat(contact.id)
chatViewSheet = nil
try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId)
await MainActor.run {
chatModel.removeChat(chat.chatInfo.id)
showSheet = false
}
} catch let error {
logger.error("ChatInfoView.deleteContactAlert apiDeleteChat error: \(error.localizedDescription)")
logger.error("deleteGroupAlert apiDeleteChat error: \(error.localizedDescription)")
}
}
},
@@ -80,7 +174,6 @@ struct GroupChatInfoView: View {
)
}
// TODO reuse between this and ChatInfoView
private func clearChatAlert() -> Alert {
Alert(
title: Text("Clear conversation?"),
@@ -88,8 +181,24 @@ struct GroupChatInfoView: View {
primaryButton: .destructive(Text("Clear")) {
Task {
await clearChat(chat)
DispatchQueue.main.async {
chatViewSheet = nil
await MainActor.run {
showSheet = false
}
}
},
secondaryButton: .cancel()
)
}
private func leaveGroupAlert() -> Alert {
Alert(
title: Text("Leave group?"),
message: Text("You will stop receiving messages from this group. Chat history will be preserved."),
primaryButton: .destructive(Text("Leave")) {
Task {
await leaveGroup(chat.chatInfo.apiId)
await MainActor.run {
showSheet = false
}
}
},
@@ -100,7 +209,7 @@ struct GroupChatInfoView: View {
struct GroupChatInfoView_Previews: PreviewProvider {
static var previews: some View {
@State var chatViewSheet = ChatViewSheet.chatInfo
return GroupChatInfoView(chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []), chatViewSheet: Binding($chatViewSheet))
@State var showSheet = true
return GroupChatInfoView(chat: Chat(chatInfo: ChatInfo.sampleData.group, chatItems: []), groupInfo: GroupInfo.sampleData, showSheet: $showSheet)
}
}

View File

@@ -53,7 +53,7 @@ struct ChatListNavLink: View {
Button(role: .destructive) {
AlertManager.shared.showAlert(
contact.ready
? deleteChatAlert(chat.chatInfo)
? deleteContactAlert(chat.chatInfo)
: deletePendingContactAlert(chat, contact)
)
} label: {
@@ -80,7 +80,7 @@ struct ChatListNavLink: View {
joinGroupButton()
}
.swipeActions(edge: .trailing) {
if groupInfo.canDelete() {
if groupInfo.canDelete {
deleteGroupChatButton(groupInfo)
}
}
@@ -123,7 +123,7 @@ struct ChatListNavLink: View {
}
}
.swipeActions(edge: .trailing) {
if groupInfo.canDelete() {
if groupInfo.canDelete {
deleteGroupChatButton(groupInfo)
}
}
@@ -159,7 +159,7 @@ struct ChatListNavLink: View {
@ViewBuilder private func deleteGroupChatButton(_ groupInfo: GroupInfo) -> some View {
Button(role: .destructive) {
AlertManager.shared.showAlert(deleteChatAlert(.group(groupInfo: groupInfo)))
AlertManager.shared.showAlert(deleteGroupAlert(.group(groupInfo: groupInfo)))
} label: {
Label("Delete", systemImage: "trash")
}
@@ -210,10 +210,21 @@ struct ChatListNavLink: View {
}
}
private func deleteChatAlert(_ chatInfo: ChatInfo) -> Alert {
private func deleteContactAlert(_ chatInfo: ChatInfo) -> Alert {
Alert(
title: Text("Delete chat?"),
message: Text("Chat and all messages will be deleted - this cannot be undone!"),
title: Text("Delete contact?"),
message: Text("Contact and all messages will be deleted - this cannot be undone!"),
primaryButton: .destructive(Text("Delete")) {
Task { await deleteChat(chat) }
},
secondaryButton: .cancel()
)
}
private func deleteGroupAlert(_ chatInfo: ChatInfo) -> Alert {
Alert(
title: Text("Delete group?"),
message: Text("Group will be deleted for all members - this cannot be undone!"),
primaryButton: .destructive(Text("Delete")) {
Task { await deleteChat(chat) }
},

View File

@@ -118,6 +118,7 @@
6454036F2822A9750090DDFF /* ComposeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6454036E2822A9750090DDFF /* ComposeFileView.swift */; };
646BB38C283BEEB9001CE359 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 646BB38B283BEEB9001CE359 /* LocalAuthentication.framework */; };
646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646BB38D283FDB6D001CE359 /* LocalAuthenticationUtils.swift */; };
647F090E288EA27B00644C40 /* GroupMemberInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647F090D288EA27B00644C40 /* GroupMemberInfoView.swift */; };
648010AB281ADD15009009B9 /* CIFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648010AA281ADD15009009B9 /* CIFileView.swift */; };
649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; };
649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; };
@@ -295,6 +296,7 @@
6454036E2822A9750090DDFF /* ComposeFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeFileView.swift; sourceTree = "<group>"; };
646BB38B283BEEB9001CE359 /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.4.sdk/System/Library/Frameworks/LocalAuthentication.framework; sourceTree = DEVELOPER_DIR; };
646BB38D283FDB6D001CE359 /* LocalAuthenticationUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthenticationUtils.swift; sourceTree = "<group>"; };
647F090D288EA27B00644C40 /* GroupMemberInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberInfoView.swift; sourceTree = "<group>"; };
648010AA281ADD15009009B9 /* CIFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIFileView.swift; sourceTree = "<group>"; };
6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = "<group>"; };
@@ -629,6 +631,7 @@
isa = PBXGroup;
children = (
6440CA02288AECA70062C672 /* AddGroupMembersView.swift */,
647F090D288EA27B00644C40 /* GroupMemberInfoView.swift */,
);
path = Group;
sourceTree = "<group>";
@@ -869,6 +872,7 @@
5CB0BA962827143500B3292C /* MakeConnection.swift in Sources */,
5C7505A527B679EE00BE3227 /* NavLinkPlain.swift in Sources */,
5CB346E72868D76D001FD2EF /* NotificationsView.swift in Sources */,
647F090E288EA27B00644C40 /* GroupMemberInfoView.swift in Sources */,
646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */,
5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */,
5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */,

View File

@@ -284,7 +284,7 @@ public struct ContactSubStatus: Decodable {
public struct Connection: Decodable {
var connId: Int64
var connStatus: ConnStatus
var connLevel: Int
public var connLevel: Int
public var id: ChatId { get { ":\(connId)" } }
@@ -439,12 +439,12 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat {
public var fullName: String { get { groupProfile.fullName } }
public var image: String? { get { groupProfile.image } }
public func canDelete() -> Bool {
public var canDelete: Bool {
let s = membership.memberStatus
return membership.memberRole == .owner || (s == .memRemoved || s == .memLeft || s == .memGroupDeleted || s == .memInvited)
}
static let sampleData = GroupInfo(
public static let sampleData = GroupInfo(
groupId: 1,
localDisplayName: "team",
groupProfile: GroupProfile.sampleData,
@@ -471,18 +471,23 @@ public struct GroupProfile: Codable, NamedChat {
)
}
public struct GroupMember: Decodable {
public struct GroupMember: Identifiable, Decodable {
public var groupMemberId: Int64
var groupId: Int64
var memberId: String
var memberRole: GroupMemberRole
var memberCategory: GroupMemberCategory
public var groupId: Int64
public var memberId: String
public var memberRole: GroupMemberRole
public var memberCategory: GroupMemberCategory
public var memberStatus: GroupMemberStatus
var invitedBy: InvitedBy
var localDisplayName: ContactName
public var invitedBy: InvitedBy
public var localDisplayName: ContactName
public var memberProfile: Profile
public var memberContactId: Int64?
var activeConn: Connection?
public var activeConn: Connection?
public var id: String { "#\(groupId) @\(groupMemberId)" }
public var displayName: String { get { memberProfile.displayName } }
public var fullName: String { get { memberProfile.fullName } }
public var image: String? { get { memberProfile.image } }
var directChatId: ChatId? {
get {
@@ -494,10 +499,6 @@ public struct GroupMember: Decodable {
}
}
public var id: String {
"#\(groupId) @\(groupMemberId)"
}
public var chatViewName: String {
get {
let p = memberProfile
@@ -542,6 +543,14 @@ public enum GroupMemberRole: String, Decodable {
case member = "member"
case admin = "admin"
case owner = "owner"
public var text: LocalizedStringKey {
switch self {
case .member: return "Member"
case .admin: return "Admin"
case .owner: return "Owner"
}
}
}
public enum GroupMemberCategory: String, Decodable {
@@ -564,6 +573,38 @@ public enum GroupMemberStatus: String, Decodable {
case memConnected = "connected"
case memComplete = "complete"
case memCreator = "creator"
public var text: LocalizedStringKey {
switch self {
case .memRemoved: return "Removed"
case .memLeft: return "Left"
case .memGroupDeleted: return "Group deleted"
case .memInvited: return "Invited"
case .memIntroduced: return "Connecting (introduced)"
case .memIntroInvited: return "Connecting (introduction invitation)"
case .memAccepted: return "Connecting (accepted)"
case .memAnnounced: return "Connecting (announced)"
case .memConnected: return "Connected"
case .memComplete: return "Complete"
case .memCreator: return "Creator"
}
}
public var shortText: LocalizedStringKey {
switch self {
case .memRemoved: return "removed"
case .memLeft: return "left"
case .memGroupDeleted: return "group deleted"
case .memInvited: return "invited"
case .memIntroduced: return "connecting"
case .memIntroInvited: return "connecting"
case .memAccepted: return "connecting"
case .memAnnounced: return "connecting"
case .memConnected: return "connected"
case .memComplete: return "complete"
case .memCreator: return "creator"
}
}
}
public enum InvitedBy: Decodable {