ios: add member ui wip (#834)

* ios: add member ui wip

* AddGroupMembersView

* clean up

* cleanup

* change new chat button

* update adding members

* add group name and image to adding members view

* adjust layout

* layout

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
JRoberts
2022-07-26 10:55:58 +04:00
committed by GitHub
co-authored by Evgeny Poberezkin
parent 6069108bb9
commit 608030dcaf
16 changed files with 237 additions and 53 deletions
+6 -7
View File
@@ -13,7 +13,6 @@ struct ContentView: View {
@ObservedObject var callController = CallController.shared
@Binding var doAuthenticate: Bool
@Binding var userAuthorized: Bool?
@State private var showChatInfo: Bool = false // TODO comprehensively close modal views on authentication
@AppStorage(DEFAULT_SHOW_LA_NOTICE) private var prefShowLANotice = false
@AppStorage(DEFAULT_LA_NOTICE_SHOWN) private var prefLANoticeShown = false
@AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false
@@ -42,7 +41,7 @@ struct ContentView: View {
private func mainView() -> some View {
ZStack(alignment: .top) {
ChatListView(showChatInfo: $showChatInfo)
ChatListView()
.onAppear {
NtfManager.shared.requestAuthorization(onDeny: {
alertManager.showAlert(notificationAlert())
@@ -64,11 +63,11 @@ struct ContentView: View {
private func runAuthenticate() {
if !prefPerformLA {
userAuthorized = true
} else if showChatInfo {
showChatInfo = false
DispatchQueue.main.async {
justAuthenticate()
}
// } else if showChatInfo {
// showChatInfo = false
// DispatchQueue.main.async {
// justAuthenticate()
// }
} else {
justAuthenticate()
}
-1
View File
@@ -24,7 +24,6 @@ final class ChatModel: ObservableObject {
@Published var chatId: String?
@Published var chatItems: [ChatItem] = []
@Published var chatToTop: String?
// @Published var groups: Dictionary<ChatId, SimpleXChat.Group> = [:]
// items in the terminal view
@Published var terminalItems: [TerminalItem] = []
@Published var userAddress: String?
+32 -6
View File
@@ -402,6 +402,12 @@ func clearChat(_ chat: Chat) async {
}
}
func apiListContacts() throws -> [Contact] {
let r = chatSendCmdSync(.listContacts)
if case let .contactsList(contacts) = r { return contacts }
throw r
}
func apiUpdateProfile(profile: Profile) async throws -> Profile? {
let r = await chatSendCmd(.apiUpdateProfile(profile: profile))
switch r {
@@ -559,36 +565,56 @@ func apiNewGroup(_ gp: GroupProfile) throws -> GroupInfo {
throw r
}
func joinGroup(groupId: Int64) async {
func addMember(groupId: Int64, contactId: Int64) async {
do {
let groupInfo = try await apiJoinGroup(groupId: groupId)
try await apiAddMember(groupId: groupId, contactId: contactId, memberRole: .admin)
} catch let error {
logger.error("addMember error: \(responseError(error))")
}
}
func apiAddMember(groupId: Int64, contactId: Int64, memberRole: GroupMemberRole) async throws {
let r = await chatSendCmd(.apiAddMember(groupId: groupId, contactId: contactId, memberRole: memberRole))
if case .sentGroupInvitation = r { return }
throw r
}
func joinGroup(_ groupId: Int64) async {
do {
let groupInfo = try await apiJoinGroup(groupId)
DispatchQueue.main.async { ChatModel.shared.updateGroup(groupInfo) }
} catch let error {
logger.error("joinGroup error: \(responseError(error))")
}
}
func apiJoinGroup(groupId: Int64) async throws -> GroupInfo {
func apiJoinGroup(_ groupId: Int64) async throws -> GroupInfo {
let r = await chatSendCmd(.apiJoinGroup(groupId: groupId))
if case let .userAcceptedGroupSent(groupInfo) = r { return groupInfo }
throw r
}
func leaveGroup(groupId: Int64) async {
func leaveGroup(_ groupId: Int64) async {
do {
let groupInfo = try await apiLeaveGroup(groupId: groupId)
let groupInfo = try await apiLeaveGroup(groupId)
DispatchQueue.main.async { ChatModel.shared.updateGroup(groupInfo) }
} catch let error {
logger.error("leaveGroup error: \(responseError(error))")
}
}
func apiLeaveGroup(groupId: Int64) async throws -> GroupInfo {
func apiLeaveGroup(_ groupId: Int64) async throws -> GroupInfo {
let r = await chatSendCmd(.apiLeaveGroup(groupId: groupId), bgTask: false)
if case let .leftMemberUser(groupInfo) = r { return groupInfo }
throw r
}
func apiListMembers(_ groupId: Int64) async -> [GroupMember] {
let r = await chatSendCmd(.apiListMembers(groupId: groupId))
if case let .groupMembers(group) = r { return group.members }
return []
}
func initializeChat(start: Bool) throws {
logger.debug("initializeChat")
do {
@@ -14,6 +14,7 @@ let chatImageColorDark = Color(red: 0.2, green: 0.2, blue: 0.2
struct ChatInfoToolbar: View {
@Environment(\.colorScheme) var colorScheme
@ObservedObject var chat: Chat
var imageSize: CGFloat = 32
var body: some View {
let cInfo = chat.chatInfo
@@ -24,7 +25,7 @@ struct ChatInfoToolbar: View {
? chatImageColorDark
: chatImageColorLight
)
.frame(width: 32, height: 32)
.frame(width: imageSize, height: imageSize)
.padding(.trailing, 4)
VStack {
Text(cInfo.displayName).font(.headline)
@@ -13,7 +13,7 @@ struct ChatInfoView: View {
@EnvironmentObject var chatModel: ChatModel
@ObservedObject var alertManager = AlertManager.shared
@ObservedObject var chat: Chat
@Binding var showChatInfo: Bool
@Binding var chatViewSheet: ChatViewSheet?
@State var alert: ChatInfoViewAlert? = nil
@State var deletingContact: Contact?
var contact: Contact
@@ -87,7 +87,7 @@ struct ChatInfoView: View {
try await apiDeleteChat(type: .direct, id: contact.apiId)
DispatchQueue.main.async {
chatModel.removeChat(contact.id)
showChatInfo = false
chatViewSheet = nil
}
} catch let error {
logger.error("ChatInfoView.deleteContactAlert apiDeleteChat error: \(error.localizedDescription)")
@@ -106,7 +106,7 @@ struct ChatInfoView: View {
Task {
await clearChat(chat)
DispatchQueue.main.async {
showChatInfo = false
chatViewSheet = nil
}
}
},
@@ -117,7 +117,7 @@ struct ChatInfoView: View {
struct ChatInfoView_Previews: PreviewProvider {
static var previews: some View {
@State var showChatInfo = true
return ChatInfoView(chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []), showChatInfo: $showChatInfo, contact: Contact.sampleData)
@State var chatViewSheet = ChatViewSheet.chatInfo
return ChatInfoView(chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []), chatViewSheet: Binding($chatViewSheet), contact: Contact.sampleData)
}
}
@@ -98,7 +98,7 @@ struct CIGroupInvitationView: View {
private func acceptInvitation() {
Task {
logger.debug("acceptInvitation")
await joinGroup(groupId: groupInvitation.groupId)
await joinGroup(groupInvitation.groupId)
}
}
}
+30 -8
View File
@@ -11,11 +11,18 @@ import SimpleXChat
private let memberImageSize: CGFloat = 34
enum ChatViewSheet: Identifiable {
case chatInfo
case addMember
var id: ChatViewSheet { get { self } }
}
struct ChatView: View {
@EnvironmentObject var chatModel: ChatModel
@Environment(\.colorScheme) var colorScheme
@ObservedObject var chat: Chat
@Binding var showChatInfo: Bool
@State private var chatViewSheet: ChatViewSheet?
@State private var composeState = ComposeState()
@State private var deletingItem: ChatItem? = nil
@FocusState private var keyboardVisible: Bool
@@ -99,15 +106,20 @@ struct ChatView: View {
}
ToolbarItem(placement: .principal) {
Button {
showChatInfo = true
chatViewSheet = .chatInfo
} label: {
ChatInfoToolbar(chat: chat)
}
.sheet(isPresented: $showChatInfo) {
if case let .direct(contact) = chat.chatInfo {
ChatInfoView(chat: chat, showChatInfo: $showChatInfo, contact: contact)
} else if case .group = chat.chatInfo {
GroupChatInfoView(chat: chat, showChatInfo: $showChatInfo)
.sheet(item: $chatViewSheet) { sheet in
switch sheet {
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)
}
case .addMember:
AddGroupMembersView(chat: chat, chatViewSheet: $chatViewSheet)
}
}
}
@@ -117,6 +129,8 @@ struct ChatView: View {
callButton(contact, .audio, imageName: "phone")
callButton(contact, .video, imageName: "video")
}
} else if case .group = chat.chatInfo {
addMemberButton()
}
}
}
@@ -131,6 +145,14 @@ struct ChatView: View {
}
}
private func addMemberButton() -> some View {
Button {
chatViewSheet = .addMember
} label: {
Image(systemName: "person.crop.circle.badge.plus")
}
}
private func chatItemWithMenu(_ ci: ChatItem, _ maxWidth: CGFloat, showMember: Bool = false) -> some View {
let alignment: Alignment = ci.chatDir.sent ? .trailing : .leading
return ChatItemView(chatInfo: chat.chatInfo, chatItem: ci, showMember: showMember, maxWidth: maxWidth)
@@ -276,7 +298,7 @@ struct ChatView_Previews: PreviewProvider {
ChatItem.getSample(9, .directSnd, .now, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
]
@State var showChatInfo = false
return ChatView(chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []), showChatInfo: $showChatInfo)
return ChatView(chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []))
.environmentObject(chatModel)
}
}
@@ -0,0 +1,109 @@
//
// AddGroupMembersView.swift
// SimpleX (iOS)
//
// Created by JRoberts on 22.07.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct AddGroupMembersView: View {
@EnvironmentObject var chatModel: ChatModel
var chat: Chat
@Binding var chatViewSheet: ChatViewSheet?
@State private var contactsToAdd: [Contact] = []
@State private var selectedContacts = Set<Int64>()
var body: some View {
VStack(alignment: .leading, spacing: 0) {
ChatInfoToolbar(chat: chat, imageSize: 48)
.padding()
.frame(maxWidth: .infinity, alignment: .center)
.background(Color(uiColor: .quaternarySystemFill))
if (contactsToAdd.isEmpty) {
Text("No contacts to add")
.foregroundColor(.secondary)
.padding()
.frame(maxWidth: .infinity, alignment: .center)
} else {
HStack {
let count = selectedContacts.count
Button {
Task {
for contactId in selectedContacts {
await addMember(groupId: chat.chatInfo.apiId, contactId: contactId)
}
chatViewSheet = nil
}
} label: {
Label(
count > 0 ? "Invite \(count) member(s)" : "Invite new members",
systemImage: "plus")
}
.disabled(count < 1)
Spacer()
if count > 0 {
Button {
selectedContacts.removeAll()
} label: {
Label("Clear", systemImage: "multiply")
}
}
}
.padding(.horizontal)
.padding(.bottom, 12)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color(uiColor: .quaternarySystemFill))
List(contactsToAdd) { contact in
contactCheckView(contact)
.listRowBackground(Color.clear)
}
.listStyle(.plain)
}
}
.frame(maxHeight: .infinity, alignment: .top)
.task {
contactsToAdd = await getContactsToAdd()
}
}
func getContactsToAdd() async -> [Contact] {
let memberContactIds = await apiListMembers(chat.chatInfo.apiId)
.compactMap{ $0.memberContactId }
return chatModel.chats
.compactMap{ $0.chatInfo.contact }
.filter{ !memberContactIds.contains($0.apiId) }
.sorted{ $0.displayName.lowercased() < $1.displayName.lowercased() }
}
func contactCheckView(_ contact: Contact) -> some View {
let checked = selectedContacts.contains(contact.apiId)
return Button {
if checked {
selectedContacts.remove(contact.apiId)
} else {
selectedContacts.insert(contact.apiId)
}
} label: {
HStack{
ProfileImage(imageStr: contact.image)
.frame(width: 30, height: 30)
.padding(.trailing, 2)
Text(ChatInfo.direct(contact: contact).chatViewName)
.lineLimit(1)
Spacer()
Image(systemName: checked ? "checkmark.circle.fill": "circle")
.foregroundColor(checked ? .accentColor : .secondary)
}
}
}
}
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))
}
}
@@ -13,7 +13,7 @@ struct GroupChatInfoView: View {
@EnvironmentObject var chatModel: ChatModel
@ObservedObject var alertManager = AlertManager.shared
@ObservedObject var chat: Chat
@Binding var showChatInfo: Bool
@Binding var chatViewSheet: ChatViewSheet?
@State var alert: ChatInfoViewAlert? = nil
@State var deletingContact: Contact?
@@ -69,7 +69,7 @@ struct GroupChatInfoView: View {
try await apiDeleteChat(type: .direct, id: contact.apiId)
DispatchQueue.main.async {
chatModel.removeChat(contact.id)
showChatInfo = false
chatViewSheet = nil
}
} catch let error {
logger.error("ChatInfoView.deleteContactAlert apiDeleteChat error: \(error.localizedDescription)")
@@ -89,7 +89,7 @@ struct GroupChatInfoView: View {
Task {
await clearChat(chat)
DispatchQueue.main.async {
showChatInfo = false
chatViewSheet = nil
}
}
},
@@ -100,7 +100,7 @@ struct GroupChatInfoView: View {
struct GroupChatInfoView_Previews: PreviewProvider {
static var previews: some View {
@State var showChatInfo = true
return GroupChatInfoView(chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []), showChatInfo: $showChatInfo)
@State var chatViewSheet = ChatViewSheet.chatInfo
return GroupChatInfoView(chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []), chatViewSheet: Binding($chatViewSheet))
}
}
@@ -12,7 +12,6 @@ import SimpleXChat
struct ChatListNavLink: View {
@EnvironmentObject var chatModel: ChatModel
@State var chat: Chat
@Binding var showChatInfo: Bool
@State private var showContactRequestDialog = false
@State private var showJoinGroupDialog = false
@@ -30,7 +29,7 @@ struct ChatListNavLink: View {
}
private func chatView() -> some View {
ChatView(chat: chat, showChatInfo: $showChatInfo)
ChatView(chat: chat)
.onAppear { loadChat(chat: chat) }
}
@@ -87,7 +86,7 @@ struct ChatListNavLink: View {
}
.onTapGesture { showJoinGroupDialog = true }
.confirmationDialog("Group invitation", isPresented: $showJoinGroupDialog, titleVisibility: .visible) {
Button("Join group") { Task { await joinGroup(groupId: groupInfo.groupId) } }
Button("Join group") { Task { await joinGroup(groupInfo.groupId) } }
Button("Delete invitation", role: .destructive) { Task { await deleteChat(chat) } }
}
case .memAccepted:
@@ -133,7 +132,7 @@ struct ChatListNavLink: View {
private func joinGroupButton() -> some View {
Button {
Task { await joinGroup(groupId: chat.chatInfo.apiId) }
Task { await joinGroup(chat.chatInfo.apiId) }
} label: {
Label("Join", systemImage: "ipad.and.arrow.forward")
}
@@ -238,7 +237,7 @@ struct ChatListNavLink: View {
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(groupId: groupInfo.groupId) }
Task { await leaveGroup(groupInfo.groupId) }
},
secondaryButton: .cancel()
)
@@ -324,20 +323,19 @@ struct ChatListNavLink: View {
struct ChatListNavLink_Previews: PreviewProvider {
static var previews: some View {
@State var chatId: String? = "@1"
@State var showChatInfo = false
return Group {
ChatListNavLink(chat: Chat(
chatInfo: ChatInfo.sampleData.direct,
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello")]
), showChatInfo: $showChatInfo)
))
ChatListNavLink(chat: Chat(
chatInfo: ChatInfo.sampleData.direct,
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello")]
), showChatInfo: $showChatInfo)
))
ChatListNavLink(chat: Chat(
chatInfo: ChatInfo.sampleData.contactRequest,
chatItems: []
), showChatInfo: $showChatInfo)
))
}
.previewLayout(.fixed(width: 360, height: 80))
}
@@ -11,7 +11,6 @@ import SimpleXChat
struct ChatListView: View {
@EnvironmentObject var chatModel: ChatModel
@Binding var showChatInfo: Bool
// not really used in this view
@State private var showSettings = false
@State private var searchText = ""
@@ -21,7 +20,7 @@ struct ChatListView: View {
let v = NavigationView {
List {
ForEach(filteredChats(), id: \.viewId) { chat in
ChatListNavLink(chat: chat, showChatInfo: $showChatInfo)
ChatListNavLink(chat: chat)
.padding(.trailing, -16)
.disabled(chatModel.chatRunning != true)
}
@@ -108,11 +107,10 @@ struct ChatListView_Previews: PreviewProvider {
)
]
@State var showChatInfo = false
return Group {
ChatListView(showChatInfo: $showChatInfo)
ChatListView()
.environmentObject(chatModel)
ChatListView(showChatInfo: $showChatInfo)
ChatListView()
.environmentObject(ChatModel())
}
}
@@ -22,6 +22,8 @@ struct AddGroupView: View {
Text("Create group")
.font(.largeTitle)
.padding(.bottom, 4)
Text("Enter group details")
.padding(.bottom)
ZStack(alignment: .topLeading) {
if !validDisplayName(displayName) {
Image(systemName: "exclamationmark.circle")
@@ -25,13 +25,16 @@ struct NewChatButton: View {
var body: some View {
Button { showAddChat = true } label: {
Image(systemName: "person.crop.circle.badge.plus")
Image(systemName: "plus.circle.fill")
.resizable()
.scaledToFit()
.frame(width: 24, height: 24 )
}
.confirmationDialog("Add contact to start a new chat", isPresented: $showAddChat, titleVisibility: .visible) {
Button("Create link / QR code") { addContactAction() }
Button("Paste received link") { actionSheet = .pasteLink }
Button("Scan QR code") { actionSheet = .scanQRCode }
// Button("Create group") { actionSheet = .createGroup }
Button("Create group") { actionSheet = .createGroup }
}
.sheet(item: $actionSheet) { sheet in
switch sheet {
@@ -112,6 +112,7 @@
5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; };
640F50E327CF991C001E05C2 /* SMPServers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640F50E227CF991C001E05C2 /* SMPServers.swift */; };
6440CA00288857A10062C672 /* CIGroupEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIGroupEventView.swift */; };
6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; };
6442E0BA287F169300CEC0F9 /* AddGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */; };
6442E0BE2880182D00CEC0F9 /* GroupChatInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6442E0BD2880182D00CEC0F9 /* GroupChatInfoView.swift */; };
6454036F2822A9750090DDFF /* ComposeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6454036E2822A9750090DDFF /* ComposeFileView.swift */; };
@@ -288,6 +289,7 @@
5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; };
640F50E227CF991C001E05C2 /* SMPServers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMPServers.swift; sourceTree = "<group>"; };
6440C9FF288857A10062C672 /* CIGroupEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIGroupEventView.swift; sourceTree = "<group>"; };
6440CA02288AECA70062C672 /* AddGroupMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupMembersView.swift; sourceTree = "<group>"; };
6442E0B9287F169300CEC0F9 /* AddGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupView.swift; sourceTree = "<group>"; };
6442E0BD2880182D00CEC0F9 /* GroupChatInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatInfoView.swift; sourceTree = "<group>"; };
6454036E2822A9750090DDFF /* ComposeFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeFileView.swift; sourceTree = "<group>"; };
@@ -379,6 +381,7 @@
5C5F4AC227A5E9AF00B51EF1 /* Chat */ = {
isa = PBXGroup;
children = (
6440CA01288AEC770062C672 /* Group */,
5CE4407427ADB657007B033A /* ChatItem */,
5CEACCE527DE977C000BD591 /* ComposeMessage */,
5C2E260E27A30FDC00F70299 /* ChatView.swift */,
@@ -622,6 +625,14 @@
path = Database;
sourceTree = "<group>";
};
6440CA01288AEC770062C672 /* Group */ = {
isa = PBXGroup;
children = (
6440CA02288AECA70062C672 /* AddGroupMembersView.swift */,
);
path = Group;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
@@ -812,6 +823,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */,
5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */,
5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */,
5C55A923283CEDE600C4E99E /* SoundPlayer.swift in Sources */,
+6
View File
@@ -50,6 +50,7 @@ public enum ChatCommand {
case connect(connReq: String)
case apiDeleteChat(type: ChatType, id: Int64)
case apiClearChat(type: ChatType, id: Int64)
case listContacts
case apiUpdateProfile(profile: Profile)
case createMyAddress
case deleteMyAddress
@@ -110,6 +111,7 @@ public enum ChatCommand {
case let .connect(connReq): return "/connect \(connReq)"
case let .apiDeleteChat(type, id): return "/_delete \(ref(type, id))"
case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id))"
case .listContacts: return "/contacts"
case let .apiUpdateProfile(profile): return "/_profile \(encodeJSON(profile))"
case .createMyAddress: return "/address"
case .deleteMyAddress: return "/delete_address"
@@ -170,6 +172,7 @@ public enum ChatCommand {
case .connect: return "connect"
case .apiDeleteChat: return "apiDeleteChat"
case .apiClearChat: return "apiClearChat"
case .listContacts: return "listContacts"
case .apiUpdateProfile: return "apiUpdateProfile"
case .createMyAddress: return "createMyAddress"
case .deleteMyAddress: return "deleteMyAddress"
@@ -246,6 +249,7 @@ public enum ChatResponse: Decodable, Error {
case chatItemStatusUpdated(chatItem: AChatItem)
case chatItemUpdated(chatItem: AChatItem)
case chatItemDeleted(deletedChatItem: AChatItem, toChatItem: AChatItem)
case contactsList(contacts: [Contact])
// group events
case groupCreated(groupInfo: GroupInfo)
case sentGroupInvitation(groupInfo: GroupInfo, contact: Contact)
@@ -335,6 +339,7 @@ public enum ChatResponse: Decodable, Error {
case .chatItemStatusUpdated: return "chatItemStatusUpdated"
case .chatItemUpdated: return "chatItemUpdated"
case .chatItemDeleted: return "chatItemDeleted"
case .contactsList: return "contactsList"
case .groupCreated: return "groupCreated"
case .sentGroupInvitation: return "sentGroupInvitation"
case .userAcceptedGroupSent: return "userAcceptedGroupSent"
@@ -424,6 +429,7 @@ public enum ChatResponse: Decodable, Error {
case let .chatItemStatusUpdated(chatItem): return String(describing: chatItem)
case let .chatItemUpdated(chatItem): return String(describing: chatItem)
case let .chatItemDeleted(deletedChatItem, toChatItem): return "deletedChatItem:\n\(String(describing: deletedChatItem))\ntoChatItem:\n\(String(describing: toChatItem))"
case let .contactsList(contacts): return String(describing: contacts)
case let .groupCreated(groupInfo): return String(describing: groupInfo)
case let .sentGroupInvitation(groupInfo, contact): return "groupInfo: \(groupInfo)\ncontact: \(contact)"
case let .userAcceptedGroupSent(groupInfo): return String(describing: groupInfo)
+12 -3
View File
@@ -180,6 +180,15 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
}
}
public var contact: Contact? {
get {
switch self {
case let .direct(contact): return contact
default: return nil
}
}
}
var createdAt: Date {
switch self {
case let .direct(contact): return contact.createdAt
@@ -410,8 +419,8 @@ public enum ConnStatus: String, Decodable {
}
public struct Group: Decodable {
var groupInfo: GroupInfo
var members: [GroupMember]
public var groupInfo: GroupInfo
public var members: [GroupMember]
}
public struct GroupInfo: Identifiable, Decodable, NamedChat {
@@ -472,7 +481,7 @@ public struct GroupMember: Decodable {
var invitedBy: InvitedBy
var localDisplayName: ContactName
public var memberProfile: Profile
var memberContactId: Int64?
public var memberContactId: Int64?
var activeConn: Connection?
var directChatId: ChatId? {