mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-01 20:08:57 +00:00
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:
co-authored by
Evgeny Poberezkin
parent
6069108bb9
commit
608030dcaf
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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? {
|
||||
|
||||
Reference in New Issue
Block a user