diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index 5a107ce5a..45298422e 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -5,73 +5,25 @@ // Created by Evgeny Poberezkin on 17/01/2022. // -//import SwiftUI - -//struct ContentView: View { -// @State var messages: [String] = ["Start session:"] -// @State var text: String = "" -// -// func sendMessage() { -// } -// -// var body: some View { -// VStack { -// ScrollView { -// LazyVStack { -// ForEach(messages, id: \.self) { msg in -// MessageView(message: msg, sent: false) -// } -// } -// .padding(10) -// } -// .frame(minWidth: 0, -// maxWidth: .infinity, -// minHeight: 0, -// maxHeight: .infinity, -// alignment: .topLeading) -// HStack { -// TextField("Message...", text: $text) -// .textFieldStyle(RoundedBorderTextFieldStyle()) -// .frame(minHeight: CGFloat(30)) -// Button(action: sendMessage) { -// Text("Send") -// }.disabled(text.isEmpty) -// } -// .frame(minHeight: CGFloat(30)) -// .padding() -// } -// } -//} - import SwiftUI struct ContentView: View { @EnvironmentObject var chatModel: ChatModel -// var chatStore: chat_store -// private let controller: chat_controller - -// init(chatStore: chat_store) { -// self.chatStore = chatStore -// } - - -// @State private var logbuffer = [String]() -// @State private var chatcmd: String = "" -// @State private var chatlog: String = "" -// @FocusState private var focused: Bool -// -// func addLine(line: String) { -// print(line) -// logbuffer.append(line) -// if(logbuffer.count > 50) { _ = logbuffer.dropFirst() } -// chatlog = logbuffer.joined(separator: "\n") -// } - var body: some View { if let user = chatModel.currentUser { ChatListView(user: user) .onAppear { + DispatchQueue.global().async { + while(true) { + do { + try processReceivedMsg(chatModel, chatRecvMsg()) + } catch { + print("error receiving message: ", error) + } + } + } + do { let chats = try apiGetChats() chatModel.chatPreviews = chats @@ -82,29 +34,6 @@ struct ContentView: View { } else { WelcomeView() } - -// return VStack { -// ScrollView { -// VStack(alignment: .leading) { -// HStack { Spacer() } -// Text(chatlog) -// .lineLimit(nil) -// .font(.system(.body, design: .monospaced)) -// } -// .frame(maxWidth: .infinity) -// } -// -// TextField("Chat command", text: $chatcmd) -// .focused($focused) -// .onSubmit { -// print(chatcmd) -// var cCmd = chatcmd.cString(using: .utf8)! -// print(String.init(cString: chat_send_cmd(controller, &cCmd))) -// } -// .textInputAutocapitalization(.never) -// .disableAutocorrection(true) -// .padding() -// } } } diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 1e4895365..5a866bc61 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -8,21 +8,30 @@ import Foundation import Combine +import SwiftUI final class ChatModel: ObservableObject { @Published var currentUser: User? @Published var chats: Dictionary = [:] - @Published var chatPreviews: [ChatPreview] = [] + @Published var chatPreviews: [Chat] = [] @Published var chatItems: [ChatItem] = [] @Published var terminalItems: [TerminalItem] = [] } -struct User: Codable { +class User: Codable { var userId: Int64 var userContactId: Int64 var localDisplayName: ContactName var profile: Profile var activeUser: Bool + + internal init(userId: Int64, userContactId: Int64, localDisplayName: ContactName, profile: Profile, activeUser: Bool) { + self.userId = userId + self.userContactId = userContactId + self.localDisplayName = localDisplayName + self.profile = profile + self.activeUser = activeUser + } } let sampleUser = User( @@ -47,15 +56,6 @@ let sampleProfile = Profile( fullName: "Alice" ) -struct ChatPreview: Identifiable, Decodable { - var chatInfo: ChatInfo - var lastChatItem: ChatItem? - - var id: String { - get { chatInfo.id } - } -} - enum ChatType: String { case direct = "@" case group = "#" @@ -106,7 +106,7 @@ let sampleDirectChatInfo = ChatInfo.direct(contact: sampleContact) let sampleGroupChatInfo = ChatInfo.group(groupInfo: sampleGroupInfo) -class Chat: Decodable { +class Chat: Decodable, Identifiable { var chatInfo: ChatInfo var chatItems: [ChatItem] @@ -114,6 +114,8 @@ class Chat: Decodable { self.chatInfo = chatInfo self.chatItems = chatItems } + + var id: String { get { chatInfo.id } } } struct Contact: Identifiable, Codable { @@ -172,11 +174,30 @@ struct ChatItem: Identifiable, Decodable { var id: Int64 { get { meta.itemId } } } +func chatItemSample(_ id: Int64, _ dir: CIDirection, _ ts: Date, _ text: String) -> ChatItem { + ChatItem( + chatDir: dir, + meta: ciMetaSample(id, ts, text), + content: .sndMsgContent(msgContent: .text(text)) + ) +} + enum CIDirection: Decodable { case directSnd case directRcv case groupSnd case groupRcv(GroupMember) + + var sent: Bool { + get { + switch self { + case .directSnd: return true + case .directRcv: return false + case .groupSnd: return true + case .groupRcv: return false + } + } + } } struct CIMeta: Decodable { @@ -186,6 +207,15 @@ struct CIMeta: Decodable { var createdAt: Date } +func ciMetaSample(_ id: Int64, _ ts: Date, _ text: String) -> CIMeta { + CIMeta( + itemId: id, + itemTs: ts, + itemText: text, + createdAt: ts + ) +} + enum CIContent: Decodable { case sndMsgContent(msgContent: MsgContent) case rcvMsgContent(msgContent: MsgContent) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 944c206c3..a91192a6f 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -20,6 +20,8 @@ enum ChatCommand { case apiSendMessage(type: ChatType, id: Int64, msg: MsgContent) case addContact case connect(connReq: String) + case apiDeleteChat(type: ChatType, id: Int64) + case apiUpdateProfile(profile: Profile) case string(String) var cmdString: String { @@ -35,6 +37,10 @@ enum ChatCommand { return "/c" case let .connect(connReq): return "/c \(connReq)" + case let .apiDeleteChat(type, id): + return "/_del \(type.rawValue)\(id)" + case let .apiUpdateProfile(profile): + return "/p \(profile.displayName) \(profile.fullName)" case let .string(str): return str } @@ -48,11 +54,14 @@ struct APIResponse: Decodable { enum ChatResponse: Decodable, Error { case response(type: String, json: String) - case apiChats(chats: [ChatPreview]) + case apiChats(chats: [Chat]) case apiChat(chat: Chat) case invitation(connReqInvitation: String) case sentConfirmation case sentInvitation + case contactDeleted(contact: Contact) + case userProfileNoChange + case userProfileUpdated(fromProfile: Profile, toProfile: Profile) // case newSentInvitation case contactConnected(contact: Contact) case newChatItem(chatItem: AChatItem) @@ -66,6 +75,9 @@ enum ChatResponse: Decodable, Error { case .invitation: return "invitation" case .sentConfirmation: return "sentConfirmation" case .sentInvitation: return "sentInvitation" + case .contactDeleted: return "contactDeleted" + case .userProfileNoChange: return "userProfileNoChange" + case .userProfileUpdated: return "userProfileNoChange" case .contactConnected: return "contactConnected" case .newChatItem: return "newChatItem" } @@ -81,6 +93,9 @@ enum ChatResponse: Decodable, Error { case let .invitation(connReqInvitation): return connReqInvitation case .sentConfirmation: return "sentConfirmation: no details" case .sentInvitation: return "sentInvitation: no details" + case let .contactDeleted(contact): return String(describing: contact) + case .userProfileNoChange: return "userProfileNoChange: no details" + case let .userProfileUpdated(_, toProfile): return String(describing: toProfile) case let .contactConnected(contact): return String(describing: contact) case let .newChatItem(chatItem): return String(describing: chatItem) } @@ -156,7 +171,7 @@ func chatRecvMsg() throws -> ChatResponse { chatResponse(chat_recv_msg(getChatCtrl())!) } -func apiGetChats() throws -> [ChatPreview] { +func apiGetChats() throws -> [Chat] { let r = try chatSendCmd(.apiGetChats) if case let .apiChats(chats) = r { return chats } throw r @@ -189,13 +204,28 @@ func apiConnect(connReq: String) throws { } } +func apiDeleteChat(type: ChatType, id: Int64) throws { + let r = try chatSendCmd(.apiDeleteChat(type: type, id: id)) + if case .contactDeleted = r { return } + throw r +} + +func apiUpdateProfile(profile: Profile) throws -> Profile? { + let r = try chatSendCmd(.apiUpdateProfile(profile: profile)) + switch r { + case .userProfileNoChange: return nil + case let .userProfileUpdated(_, toProfile): return toProfile + default: throw r + } +} + func processReceivedMsg(_ chatModel: ChatModel, _ res: ChatResponse) { DispatchQueue.main.async { chatModel.terminalItems.append(.resp(Date.now, res)) switch res { case let .contactConnected(contact): chatModel.chatPreviews.insert( - ChatPreview(chatInfo: .direct(contact: contact)), + Chat(chatInfo: .direct(contact: contact), chatItems: []), at: 0 ) case let .newChatItem(aChatItem): @@ -204,7 +234,7 @@ func processReceivedMsg(_ chatModel: ChatModel, _ res: ChatResponse) { chatModel.chats[ci.id] = chat chat.chatItems.append(aChatItem.chatItem) default: - print("unsupported response: ", res) + print("unsupported response: ", res.responseType) } } } @@ -216,7 +246,6 @@ private struct UserResponse: Decodable { private func chatResponse(_ cjson: UnsafePointer) -> ChatResponse { let s = String.init(cString: cjson) - print("chatResponse", s) let d = s.data(using: .utf8)! // TODO is there a way to do it without copying the data? e.g: // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) @@ -270,7 +299,6 @@ private func getChatCtrl() -> chat_ctrl { private func decodeCJSON(_ cjson: UnsafePointer) -> T? { let s = String.init(cString: cjson) - print("decodeCJSON", s) let d = s.data(using: .utf8)! // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) // let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) @@ -286,6 +314,5 @@ private func getJSONObject(_ cjson: UnsafePointer) -> NSDictionary? { private func encodeCJSON(_ value: T) -> [CChar] { let data = try! jsonEncoder.encode(value) let str = String(decoding: data, as: UTF8.self) - print("encodeCJSON", str) return str.cString(using: .utf8)! } diff --git a/apps/ios/Shared/Views/ChatListView.swift b/apps/ios/Shared/Views/ChatListView.swift index a29283ee6..8f69dbb9e 100644 --- a/apps/ios/Shared/Views/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatListView.swift @@ -10,19 +10,13 @@ import SwiftUI struct ChatListView: View { @EnvironmentObject var chatModel: ChatModel + @State private var chatId: String? + @State private var chatsToBeDeleted: IndexSet? + @State private var showDeleteAlert = false + var user: User var body: some View { - DispatchQueue.global().async { - while(true) { - do { - try processReceivedMsg(chatModel, chatRecvMsg()) - } catch { - print("error receiving message: ", error) - } - } - } - return VStack { // if chatModel.chats.isEmpty { // VStack { @@ -31,8 +25,8 @@ struct ChatListView: View { // } // } - ChatHeaderView() - + ChatHeaderView(chatId: $chatId) + NavigationView { List { NavigationLink { @@ -40,35 +34,86 @@ struct ChatListView: View { } label: { Text("Terminal") } - + ForEach(chatModel.chatPreviews) { chatPreview in - NavigationLink { - ChatView(chatInfo: chatPreview.chatInfo) - .onAppear { - do { - let ci = chatPreview.chatInfo - let chat = try apiGetChat(type: ci.chatType, id: ci.apiId) - chatModel.chats[ci.id] = chat - } catch { - print("apiGetChatItems", error) + NavigationLink( + tag: chatPreview.chatInfo.id, + selection: $chatId, + destination: { + ChatView(chatInfo: chatPreview.chatInfo) + .onAppear { + do { + let ci = chatPreview.chatInfo + let chat = try apiGetChat(type: ci.chatType, id: ci.apiId) + chatModel.chats[ci.id] = chat + } catch { + print("apiGetChatItems", error) + } } - } - } label: { - ChatPreviewView(chatPreview: chatPreview) - } + }, label: { + ChatPreviewView(chatPreview: chatPreview) + .alert(isPresented: $showDeleteAlert) { + deleteChatAlert((chatsToBeDeleted?.first)!) + } + } + ) + .frame(height: 80) + } + .onDelete { idx in + chatsToBeDeleted = idx + showDeleteAlert = true } } + .padding(0) + .offset(x: -8) + .listStyle(.plain) + .edgesIgnoringSafeArea(.top) } - .navigationViewStyle(.stack) + } + } + + func deleteChatAlert(_ ix: IndexSet.Element) -> Alert { + let ci = chatModel.chatPreviews[ix].chatInfo + switch ci { + case .direct: + return Alert( + title: Text("Delete contact?"), + message: Text("Contact and all messages will be deleted"), + primaryButton: .destructive(Text("Delete")) { + do { + try apiDeleteChat(type: ci.chatType, id: ci.apiId) + chatModel.chatPreviews.remove(at: ix) + } catch let error { + print("Error: \(error)") + } + chatsToBeDeleted = nil + }, secondaryButton: .cancel() { + chatsToBeDeleted = nil + } + ) + case .group: + return Alert( + title: Text("Delete group"), + message: Text("Group deletion is not supported") + ) } } } -//struct ChatListView_Previews: PreviewProvider { -// static var previews: some View { -// let chatModel = ChatModel() -// chatModel.chatPreviews = [] -// return ChatListView(user: sampleUser) -// .environmentObject(chatModel) -// } -//} +struct ChatListView_Previews: PreviewProvider { + static var previews: some View { + let chatModel = ChatModel() + chatModel.chatPreviews = [ + Chat( + chatInfo: sampleDirectChatInfo, + chatItems: [chatItemSample(1, .directSnd, Date.now, "hello")] + ), + Chat( + chatInfo: sampleGroupChatInfo, + chatItems: [chatItemSample(1, .directSnd, Date.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.")] + ) + ] + return ChatListView(user: sampleUser) + .environmentObject(chatModel) + } +} diff --git a/apps/ios/Shared/Views/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatPreviewView.swift index 16a0cb871..eb86336f0 100644 --- a/apps/ios/Shared/Views/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatPreviewView.swift @@ -9,19 +9,55 @@ import SwiftUI struct ChatPreviewView: View { - var chatPreview: ChatPreview + var chatPreview: Chat var body: some View { - Text(chatPreview.chatInfo.localDisplayName) + let ci = chatPreview.chatItems.last + return VStack(spacing: 4) { + HStack(alignment: .top) { + Text(chatPreview.chatInfo.localDisplayName) + .font(.title3) + .fontWeight(.bold) + .padding(.leading, 8) + .padding(.top, 4) + .frame(maxHeight: .infinity, alignment: .topLeading) + Spacer() + if let ci = ci { + Text(getDateFormatter().string(from: ci.meta.itemTs)) + .font(.subheadline) + .padding(.trailing, 8) + .padding(.top, 4) + .frame(minWidth: 60, alignment: .trailing) + .foregroundColor(.secondary) + } + } + if let ci = ci { + Text(ci.content.text) + .frame(minWidth: 0, maxWidth: .infinity, minHeight: 44, maxHeight: 44, alignment: .topLeading) + .padding([.leading, .trailing], 8) + .padding(.bottom, 4) + .padding(.top, 1) + } + } } } struct ChatPreviewView_Previews: PreviewProvider { static var previews: some View { Group{ - ChatPreviewView(chatPreview: ChatPreview(chatInfo: sampleDirectChatInfo)) - ChatPreviewView(chatPreview: ChatPreview(chatInfo: sampleGroupChatInfo)) + ChatPreviewView(chatPreview: Chat( + chatInfo: sampleDirectChatInfo, + chatItems: [] + )) + ChatPreviewView(chatPreview: Chat( + chatInfo: sampleDirectChatInfo, + chatItems: [chatItemSample(1, .directSnd, Date.now, "hello")] + )) + ChatPreviewView(chatPreview: Chat( + chatInfo: sampleGroupChatInfo, + chatItems: [chatItemSample(1, .directSnd, Date.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.")] + )) } - .previewLayout(.fixed(width: 300, height: 70)) + .previewLayout(.fixed(width: 360, height: 80)) } } diff --git a/apps/ios/Shared/Views/ChatView.swift b/apps/ios/Shared/Views/ChatView.swift index 419fef2f7..fc323fce7 100644 --- a/apps/ios/Shared/Views/ChatView.swift +++ b/apps/ios/Shared/Views/ChatView.swift @@ -18,9 +18,9 @@ struct ChatView: View { if let chat: Chat = chatModel.chats[chatInfo.id] { VStack { ScrollView { - LazyVStack { - ForEach(chat.chatItems) { chatItem in - Text(chatItem.content.text) + LazyVStack(spacing: 5) { + ForEach(chat.chatItems) { + ChatItemView(chatItem: $0) } } } @@ -28,11 +28,13 @@ struct ChatView: View { } else { Text("unexpected: chat not found...") } - - Spacer() + + Spacer(minLength: 0) SendMessageView(sendMessage: sendMessage, inProgress: inProgress) } + .edgesIgnoringSafeArea(.all) + .navigationBarHidden(true) } func sendMessage(_ msg: String) { @@ -53,7 +55,15 @@ struct ChatView_Previews: PreviewProvider { chatModel.chats = [ "@1": Chat( chatInfo: sampleDirectChatInfo, - chatItems: [] + chatItems: [ + chatItemSample(1, .directSnd, Date.now, "hello"), + chatItemSample(2, .directRcv, Date.now, "hi"), + chatItemSample(3, .directRcv, Date.now, "hi there"), + chatItemSample(4, .directRcv, Date.now, "hello again"), + chatItemSample(5, .directSnd, Date.now, "hi there!!!"), + chatItemSample(6, .directSnd, Date.now, "how are you?"), + chatItemSample(7, .directSnd, Date.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.") + ] ) ] return ChatView(chatInfo: sampleDirectChatInfo) diff --git a/apps/ios/Shared/Views/Helpers/ChatHeaderView.swift b/apps/ios/Shared/Views/Helpers/ChatHeaderView.swift index f618be8d2..a515a1700 100644 --- a/apps/ios/Shared/Views/Helpers/ChatHeaderView.swift +++ b/apps/ios/Shared/Views/Helpers/ChatHeaderView.swift @@ -9,79 +9,47 @@ import SwiftUI struct ChatHeaderView: View { - @State private var showAddChat = false - @State private var addContact = false - @State private var addContactAlert = false - @State private var addContactError: Error? - @State private var connReqInvitation: String = "" - @State private var connectContact = false - @State private var connectAlert = false - @State private var connectError: Error? - @State private var createGroup = false + @Binding var chatId: String? + @EnvironmentObject var chatModel: ChatModel var body: some View { HStack { - Button("Edit", action: {}) - Spacer() - Text("Your chats") - Spacer() - Button { showAddChat = true } label: { - Image(systemName: "square.and.pencil") + if let cId = chatId { + Button { chatId = nil } label: { Image(systemName: "chevron.backward") } + Spacer() + Text(chatModel.chats[cId]?.chatInfo.localDisplayName ?? "") + .font(.title3) + Spacer() + EmptyView() + } else { + SettingsButton() + Spacer() + Text("Your chats") + .font(.title3) + Spacer() + NewChatButton() } - .confirmationDialog("Start new chat", isPresented: $showAddChat, titleVisibility: .visible) { - Button("Add contact") { addContactAction() } - Button("Scan QR code") { connectContact = true } - Button("Create group") { createGroup = true } - } - .sheet(isPresented: $addContact, content: { - AddContactView(connReqInvitation: connReqInvitation) - }) - .alert(isPresented: $addContactAlert) { - connectionError(addContactError) - } - .sheet(isPresented: $connectContact, content: { - connectContactSheet() - }) - .alert(isPresented: $connectAlert) { - connectionError(connectError) - } - .sheet(isPresented: $createGroup, content: { CreateGroupView() }) } - .padding(.horizontal) - .padding(.top) - } - - func addContactAction() { - do { - connReqInvitation = try apiAddContact() - addContact = true - } catch { - addContactAlert = true - addContactError = error - print(error) - } - } - - func connectContactSheet() -> some View { - ConnectContactView(completed: { err in - connectContact = false - if err != nil { - connectAlert = true - connectError = err - } - }) - } - - func connectionError(_ error: Error?) -> Alert { - Alert( - title: Text("Connection error"), - message: Text(error?.localizedDescription ?? "") - ) + .padding([.horizontal, .top]) } } struct ChatHeaderView_Previews: PreviewProvider { static var previews: some View { - ChatHeaderView() + @State var chatId1: String? = "@1" + @State var chatId2: String? + let chatModel = ChatModel() + chatModel.chats = [ + "@1": Chat( + chatInfo: sampleDirectChatInfo, + chatItems: [chatItemSample(1, .directSnd, Date.now, "hello")] + ) + ] + return Group { + ChatHeaderView(chatId: $chatId1) + ChatHeaderView(chatId: $chatId2) + } + .previewLayout(.fixed(width: 300, height: 70)) + .environmentObject(chatModel) } } diff --git a/apps/ios/Shared/Views/Helpers/ChatItemView.swift b/apps/ios/Shared/Views/Helpers/ChatItemView.swift new file mode 100644 index 000000000..71e6144f7 --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/ChatItemView.swift @@ -0,0 +1,63 @@ +// +// ChatItemView.swift +// SimpleX +// +// Created by Evgeny Poberezkin on 30/01/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +private var dateFormatter: DateFormatter? + +struct ChatItemView: View { + var chatItem: ChatItem + + var body: some View { + let sent = chatItem.chatDir.sent + + return VStack { + Group { + Text(chatItem.content.text) + .padding(.top, 8) + .padding(.horizontal, 12) + .frame(minWidth: 200, maxWidth: 300, alignment: .leading) + .foregroundColor(sent ? .white : .primary) + Text(getDateFormatter().string(from: chatItem.meta.itemTs)) + .font(.subheadline) + .foregroundColor(sent ? .white : .secondary) + .padding(.bottom, 8) + .padding(.horizontal, 12) + .frame(minWidth: 200, maxWidth: 300, alignment: .trailing) + } + } + .background(sent ? .blue : Color(uiColor: .tertiarySystemGroupedBackground)) + .cornerRadius(10) + .padding(.horizontal) + .frame( + minWidth: 200, + maxWidth: .infinity, + minHeight: 0, + maxHeight: .infinity, + alignment: sent ? .trailing : .leading + ) + } +} + +func getDateFormatter() -> DateFormatter { + if let df = dateFormatter { return df } + let df = DateFormatter() + df.dateFormat = "HH:mm" + dateFormatter = df + return df +} + +struct ChatItemView_Previews: PreviewProvider { + static var previews: some View { + Group{ + ChatItemView(chatItem: chatItemSample(1, .directSnd, Date.now, "hello")) + ChatItemView(chatItem: chatItemSample(2, .directRcv, Date.now, "hello there too")) + } + .previewLayout(.fixed(width: 300, height: 70)) + } +} diff --git a/apps/ios/Shared/Views/Helpers/AddContactView.swift b/apps/ios/Shared/Views/Helpers/NewChat/AddContactView.swift similarity index 100% rename from apps/ios/Shared/Views/Helpers/AddContactView.swift rename to apps/ios/Shared/Views/Helpers/NewChat/AddContactView.swift diff --git a/apps/ios/Shared/Views/Helpers/ConnectContactView.swift b/apps/ios/Shared/Views/Helpers/NewChat/ConnectContactView.swift similarity index 100% rename from apps/ios/Shared/Views/Helpers/ConnectContactView.swift rename to apps/ios/Shared/Views/Helpers/NewChat/ConnectContactView.swift diff --git a/apps/ios/Shared/Views/Helpers/CreateGroupView.swift b/apps/ios/Shared/Views/Helpers/NewChat/CreateGroupView.swift similarity index 100% rename from apps/ios/Shared/Views/Helpers/CreateGroupView.swift rename to apps/ios/Shared/Views/Helpers/NewChat/CreateGroupView.swift diff --git a/apps/ios/Shared/Views/Helpers/NewChat/NewChatButton.swift b/apps/ios/Shared/Views/Helpers/NewChat/NewChatButton.swift new file mode 100644 index 000000000..db65dd0dc --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/NewChat/NewChatButton.swift @@ -0,0 +1,80 @@ +// +// NewChatButton.swift +// SimpleX +// +// Created by Evgeny Poberezkin on 31/01/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct NewChatButton: View { + @State private var showAddChat = false + @State private var addContact = false + @State private var addContactAlert = false + @State private var addContactError: Error? + @State private var connReqInvitation: String = "" + @State private var connectContact = false + @State private var connectAlert = false + @State private var connectError: Error? + @State private var createGroup = false + + var body: some View { + Button { showAddChat = true } label: { + Image(systemName: "square.and.pencil") + } + .confirmationDialog("Start new chat", isPresented: $showAddChat, titleVisibility: .visible) { + Button("Add contact") { addContactAction() } + Button("Scan QR code") { connectContact = true } + Button("Create group") { createGroup = true } + .disabled(true) + } + .sheet(isPresented: $addContact, content: { + AddContactView(connReqInvitation: connReqInvitation) + }) + .alert(isPresented: $addContactAlert) { + connectionError(addContactError) + } + .sheet(isPresented: $connectContact, content: { + connectContactSheet() + }) + .alert(isPresented: $connectAlert) { + connectionError(connectError) + } + .sheet(isPresented: $createGroup, content: { CreateGroupView() }) + } + + func addContactAction() { + do { + connReqInvitation = try apiAddContact() + addContact = true + } catch { + addContactAlert = true + addContactError = error + print(error) + } + } + + func connectContactSheet() -> some View { + ConnectContactView(completed: { err in + connectContact = false + if err != nil { + connectAlert = true + connectError = err + } + }) + } + + func connectionError(_ error: Error?) -> Alert { + Alert( + title: Text("Connection error"), + message: Text(error?.localizedDescription ?? "") + ) + } +} + +struct NewChatButton_Previews: PreviewProvider { + static var previews: some View { + NewChatButton() + } +} diff --git a/apps/ios/Shared/Views/Helpers/QRCode.swift b/apps/ios/Shared/Views/Helpers/NewChat/QRCode.swift similarity index 100% rename from apps/ios/Shared/Views/Helpers/QRCode.swift rename to apps/ios/Shared/Views/Helpers/NewChat/QRCode.swift diff --git a/apps/ios/Shared/Views/Helpers/ShareSheet.swift b/apps/ios/Shared/Views/Helpers/NewChat/ShareSheet.swift similarity index 100% rename from apps/ios/Shared/Views/Helpers/ShareSheet.swift rename to apps/ios/Shared/Views/Helpers/NewChat/ShareSheet.swift diff --git a/apps/ios/Shared/Views/Helpers/SendMessageView.swift b/apps/ios/Shared/Views/Helpers/SendMessageView.swift index dc9fe1bfb..21e69ab84 100644 --- a/apps/ios/Shared/Views/Helpers/SendMessageView.swift +++ b/apps/ios/Shared/Views/Helpers/SendMessageView.swift @@ -16,11 +16,10 @@ struct SendMessageView: View { var body: some View { HStack { TextField("Message...", text: $command) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .textInputAutocapitalization(.never) - .disableAutocorrection(true) - .frame(minHeight: 30) - .onSubmit(submit) + .textFieldStyle(.roundedBorder) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + .onSubmit(submit) if (inProgress) { ProgressView() @@ -31,7 +30,7 @@ struct SendMessageView: View { } } .frame(minHeight: 30) - .padding() + .padding(12) } func submit() { diff --git a/apps/ios/Shared/Views/Helpers/Settings/ProfileHeader.swift b/apps/ios/Shared/Views/Helpers/Settings/ProfileHeader.swift new file mode 100644 index 000000000..3592feb16 --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/Settings/ProfileHeader.swift @@ -0,0 +1,21 @@ +// +// ProfileHeader.swift +// SimpleX +// +// Created by Evgeny Poberezkin on 31/01/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct ProfileHeader: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +struct ProfileHeader_Previews: PreviewProvider { + static var previews: some View { + ProfileHeader() + } +} diff --git a/apps/ios/Shared/Views/Helpers/UserSettings/SettingsButton.swift b/apps/ios/Shared/Views/Helpers/UserSettings/SettingsButton.swift new file mode 100644 index 000000000..840751aeb --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/UserSettings/SettingsButton.swift @@ -0,0 +1,28 @@ +// +// SettingsButton.swift +// SimpleX +// +// Created by Evgeny Poberezkin on 31/01/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct SettingsButton: View { + @State private var showSettings = false + + var body: some View { + Button { showSettings = true } label: { + Image(systemName: "gearshape") + } + .sheet(isPresented: $showSettings, content: { + SettingsView() + }) + } +} + +struct SettingsButton_Previews: PreviewProvider { + static var previews: some View { + SettingsButton() + } +} diff --git a/apps/ios/Shared/Views/Helpers/UserSettings/SettingsProfile.swift b/apps/ios/Shared/Views/Helpers/UserSettings/SettingsProfile.swift new file mode 100644 index 000000000..fc3eb7d1c --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/UserSettings/SettingsProfile.swift @@ -0,0 +1,86 @@ +// +// SettingsProfile.swift +// SimpleX +// +// Created by Evgeny Poberezkin on 31/01/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct SettingsProfile: View { + @EnvironmentObject var chatModel: ChatModel + @State private var profile = Profile(displayName: "", fullName: "") + @State private var editProfile: Bool = false + + var body: some View { + let user: User = chatModel.currentUser! + + return VStack(alignment: .leading) { + Text("Your chat profile") + .font(.title) + .padding(.bottom) + Text("Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile.") + .padding(.bottom) + if editProfile { + VStack(alignment: .leading) { + TextField("Display name", text: $profile.displayName) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + .padding(.bottom) + TextField("Full name (optional)", text: $profile.fullName) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + .padding(.bottom) + HStack(spacing: 20) { + Button("Cancel") { editProfile = false } + Button("Save (and notify contacts)") { saveProfile(user) } + } + } + .frame(maxWidth: .infinity, minHeight: 120, alignment: .leading) + } else { + VStack(alignment: .leading) { + HStack { + Text("Display name:") + Text(user.profile.displayName) + .fontWeight(.bold) + } + .padding(.bottom) + HStack { + Text("Full name:") + Text(user.profile.fullName) + .fontWeight(.bold) + } + .padding(.bottom) + Button("Edit") { + profile = user.profile + editProfile = true + } + } + .frame(maxWidth: .infinity, minHeight: 120, alignment: .leading) + } + } + .padding() + } + + func saveProfile(_ user: User) { + do { + if let newProfile = try apiUpdateProfile(profile: profile) { + user.profile = newProfile + profile = newProfile + } + } catch { + print(error) + } + editProfile = false + } +} + +struct SettingsProfile_Previews: PreviewProvider { + static var previews: some View { + let chatModel = ChatModel() + chatModel.currentUser = sampleUser + return SettingsProfile() + .environmentObject(chatModel) + } +} diff --git a/apps/ios/Shared/Views/Helpers/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/Helpers/UserSettings/SettingsView.swift new file mode 100644 index 000000000..dd93ce90a --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/UserSettings/SettingsView.swift @@ -0,0 +1,27 @@ +// +// SettingsView.swift +// SimpleX +// +// Created by Evgeny Poberezkin on 31/01/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct SettingsView: View { + @EnvironmentObject var chatModel: ChatModel + + var body: some View { + SettingsProfile() + UserAddress() + } +} + +struct SettingsView_Previews: PreviewProvider { + static var previews: some View { + let chatModel = ChatModel() + chatModel.currentUser = sampleUser + return SettingsView() + .environmentObject(chatModel) + } +} diff --git a/apps/ios/Shared/Views/Helpers/UserSettings/UserAddress.swift b/apps/ios/Shared/Views/Helpers/UserSettings/UserAddress.swift new file mode 100644 index 000000000..9cbb63fb9 --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/UserSettings/UserAddress.swift @@ -0,0 +1,21 @@ +// +// UserAddress.swift +// SimpleX +// +// Created by Evgeny Poberezkin on 31/01/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct UserAddress: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +struct UserAddress_Previews: PreviewProvider { + static var previews: some View { + UserAddress() + } +} diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 79d2bbc2f..a54706da7 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* Begin PBXBuildFile section */ 5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */; }; 5C063D2827A4564100AEC577 /* ChatPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */; }; + 5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */; }; + 5C1A4C1F27A715B700EAD5AD /* ChatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */; }; 5C1AEB86279F4A6400247F08 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C1AEB7F279F4A6400247F08 /* libffi.a */; }; 5C1AEB87279F4A6400247F08 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C1AEB7F279F4A6400247F08 /* libffi.a */; }; 5C1AEB88279F4A6400247F08 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C1AEB80279F4A6400247F08 /* libgmp.a */; }; @@ -27,6 +29,8 @@ 5C44B6A127A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C44B69E27A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w.a */; }; 5C44B6A227A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C44B69F27A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w-ghc8.10.7.a */; }; 5C44B6A327A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C44B69F27A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w-ghc8.10.7.a */; }; + 5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6AD81227A834E300348BD7 /* NewChatButton.swift */; }; + 5C6AD81427A834E300348BD7 /* NewChatButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6AD81227A834E300348BD7 /* NewChatButton.swift */; }; 5C764E80279C7276000C6508 /* dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C764E7F279C7276000C6508 /* dummy.m */; }; 5C764E81279C7276000C6508 /* dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C764E7F279C7276000C6508 /* dummy.m */; }; 5C764E82279C748B000C6508 /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C764E7B279C71D4000C6508 /* libiconv.tbd */; }; @@ -54,6 +58,14 @@ 5CA05A4D27974EB60002BEB4 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA05A4B27974EB60002BEB4 /* WelcomeView.swift */; }; 5CA05A4F279752D00002BEB4 /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA05A4E279752D00002BEB4 /* MessageView.swift */; }; 5CA05A50279752D00002BEB4 /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA05A4E279752D00002BEB4 /* MessageView.swift */; }; + 5CB924D427A853F100ACCCDD /* SettingsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924D327A853F100ACCCDD /* SettingsButton.swift */; }; + 5CB924D527A853F100ACCCDD /* SettingsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924D327A853F100ACCCDD /* SettingsButton.swift */; }; + 5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924D627A8563F00ACCCDD /* SettingsView.swift */; }; + 5CB924D827A8563F00ACCCDD /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924D627A8563F00ACCCDD /* SettingsView.swift */; }; + 5CB924E127A867BA00ACCCDD /* SettingsProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E027A867BA00ACCCDD /* SettingsProfile.swift */; }; + 5CB924E227A867BA00ACCCDD /* SettingsProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E027A867BA00ACCCDD /* SettingsProfile.swift */; }; + 5CB924E427A8683A00ACCCDD /* UserAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E327A8683A00ACCCDD /* UserAddress.swift */; }; + 5CB924E527A8683A00ACCCDD /* UserAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E327A8683A00ACCCDD /* UserAddress.swift */; }; 5CC1C99227A6C7F5000D9FF6 /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */; }; 5CC1C99327A6C7F5000D9FF6 /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */; }; 5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */; }; @@ -87,6 +99,7 @@ /* Begin PBXFileReference section */ 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreviewView.swift; sourceTree = ""; }; + 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemView.swift; sourceTree = ""; }; 5C1AEB7F279F4A6400247F08 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; 5C1AEB80279F4A6400247F08 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 5C1AEB81279F4A6400247F08 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; @@ -97,6 +110,7 @@ 5C2E261127A30FEA00F70299 /* TerminalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalView.swift; sourceTree = ""; }; 5C44B69E27A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w.a"; sourceTree = ""; }; 5C44B69F27A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w-ghc8.10.7.a"; sourceTree = ""; }; + 5C6AD81227A834E300348BD7 /* NewChatButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatButton.swift; sourceTree = ""; }; 5C764E7B279C71D4000C6508 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.2.sdk/usr/lib/libiconv.tbd; sourceTree = DEVELOPER_DIR; }; 5C764E7C279C71DB000C6508 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.2.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; }; 5C764E7D279C7275000C6508 /* SimpleX (iOS)-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SimpleX (iOS)-Bridging-Header.h"; sourceTree = ""; }; @@ -118,6 +132,10 @@ 5CA059E9279559F40002BEB4 /* Tests_macOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOSLaunchTests.swift; sourceTree = ""; }; 5CA05A4B27974EB60002BEB4 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; 5CA05A4E279752D00002BEB4 /* MessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = ""; }; + 5CB924D327A853F100ACCCDD /* SettingsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsButton.swift; sourceTree = ""; }; + 5CB924D627A8563F00ACCCDD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + 5CB924E027A867BA00ACCCDD /* SettingsProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsProfile.swift; sourceTree = ""; }; + 5CB924E327A8683A00ACCCDD /* UserAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddress.swift; sourceTree = ""; }; 5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCode.swift; sourceTree = ""; }; 5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = ""; }; 5CCD403027A5F1C600368C90 /* ChatHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHeaderView.swift; sourceTree = ""; }; @@ -191,12 +209,10 @@ children = ( 5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */, 5CA05A4E279752D00002BEB4 /* MessageView.swift */, + 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */, 5CCD403027A5F1C600368C90 /* ChatHeaderView.swift */, - 5CCD403327A5F6DF00368C90 /* AddContactView.swift */, - 5CCD403627A5F9A200368C90 /* ConnectContactView.swift */, - 5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */, - 5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */, - 5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */, + 5CB924DF27A8678B00ACCCDD /* UserSettings */, + 5CB924DD27A8622200ACCCDD /* NewChat */, ); path = Helpers; sourceTree = ""; @@ -297,6 +313,30 @@ path = "Tests macOS"; sourceTree = ""; }; + 5CB924DD27A8622200ACCCDD /* NewChat */ = { + isa = PBXGroup; + children = ( + 5C6AD81227A834E300348BD7 /* NewChatButton.swift */, + 5CCD403327A5F6DF00368C90 /* AddContactView.swift */, + 5CCD403627A5F9A200368C90 /* ConnectContactView.swift */, + 5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */, + 5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */, + 5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */, + ); + path = NewChat; + sourceTree = ""; + }; + 5CB924DF27A8678B00ACCCDD /* UserSettings */ = { + isa = PBXGroup; + children = ( + 5CB924D327A853F100ACCCDD /* SettingsButton.swift */, + 5CB924D627A8563F00ACCCDD /* SettingsView.swift */, + 5CB924E327A8683A00ACCCDD /* UserAddress.swift */, + 5CB924E027A867BA00ACCCDD /* SettingsProfile.swift */, + ); + path = UserSettings; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -464,7 +504,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */, + 5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */, + 5CB924E127A867BA00ACCCDD /* SettingsProfile.swift in Sources */, 5C764E80279C7276000C6508 /* dummy.m in Sources */, + 5CB924E427A8683A00ACCCDD /* UserAddress.swift in Sources */, 5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */, 5C2E261227A30FEA00F70299 /* TerminalView.swift in Sources */, 5C9FD96B27A56D4D0075386C /* JSON.swift in Sources */, @@ -483,6 +527,8 @@ 5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */, 5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */, 5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */, + 5CB924D427A853F100ACCCDD /* SettingsButton.swift in Sources */, + 5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -490,7 +536,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5C6AD81427A834E300348BD7 /* NewChatButton.swift in Sources */, + 5CB924D827A8563F00ACCCDD /* SettingsView.swift in Sources */, + 5CB924E227A867BA00ACCCDD /* SettingsProfile.swift in Sources */, 5C764E81279C7276000C6508 /* dummy.m in Sources */, + 5CB924E527A8683A00ACCCDD /* UserAddress.swift in Sources */, 5C063D2827A4564100AEC577 /* ChatPreviewView.swift in Sources */, 5C2E261327A30FEA00F70299 /* TerminalView.swift in Sources */, 5C9FD96C27A56D4D0075386C /* JSON.swift in Sources */, @@ -509,6 +559,8 @@ 5C764E8A279CBCB3000C6508 /* ChatModel.swift in Sources */, 5CC1C99627A6CF7F000D9FF6 /* ShareSheet.swift in Sources */, 5C2E260827A2941F00F70299 /* SimpleXAPI.swift in Sources */, + 5CB924D527A853F100ACCCDD /* SettingsButton.swift in Sources */, + 5C1A4C1F27A715B700EAD5AD /* ChatItemView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };