From cb602dd377089caf74b12e8aeed13bd551194b14 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:37:02 +0000 Subject: [PATCH] show received messages in chat, send command on Enter, fix Date parsing (#237) * refactor UI and API, send command on Enter, fix Date parsing * UI sheets to create connection and groups * show received messages * readme --- apps/ios/README.md | 16 +- apps/ios/Shared/ContentView.swift | 9 +- apps/ios/Shared/Model/ChatModel.swift | 107 ++++++++++--- apps/ios/Shared/Model/JSON.swift | 38 +++++ apps/ios/Shared/Model/SimpleXAPI.swift | 149 +++++++++++------- .../MyPlayground.playground/Contents.swift | 37 ++++- .../timeline.xctimeline | 2 +- apps/ios/Shared/SimpleXApp.swift | 14 -- apps/ios/Shared/Views/ChatListView.swift | 31 +++- apps/ios/Shared/Views/ChatPreviewView.swift | 24 +-- apps/ios/Shared/Views/ChatView.swift | 28 +++- .../Shared/Views/Helpers/ChatHeaderView.swift | 43 +++++ .../Views/Helpers/CreateGroupView.swift | 21 +++ .../Views/Helpers/InviteContactView.swift | 21 +++ .../Views/{ => Helpers}/MessageView.swift | 0 .../Shared/Views/Helpers/ScanQRCodeView.swift | 21 +++ .../Views/Helpers/SendMessageView.swift | 47 ++++++ apps/ios/Shared/Views/TerminalView.swift | 40 +++-- apps/ios/Shared/Views/WelcomeView.swift | 4 + apps/ios/SimpleX.xcodeproj/project.pbxproj | 70 ++++++-- 20 files changed, 546 insertions(+), 176 deletions(-) create mode 100644 apps/ios/Shared/Model/JSON.swift create mode 100644 apps/ios/Shared/Views/Helpers/ChatHeaderView.swift create mode 100644 apps/ios/Shared/Views/Helpers/CreateGroupView.swift create mode 100644 apps/ios/Shared/Views/Helpers/InviteContactView.swift rename apps/ios/Shared/Views/{ => Helpers}/MessageView.swift (100%) create mode 100644 apps/ios/Shared/Views/Helpers/ScanQRCodeView.swift create mode 100644 apps/ios/Shared/Views/Helpers/SendMessageView.swift diff --git a/apps/ios/README.md b/apps/ios/README.md index 7e358e9c2d..3a637d7973 100644 --- a/apps/ios/README.md +++ b/apps/ios/README.md @@ -2,11 +2,7 @@ ## Prerequisites -- `mac2ios` executable and in PATH: - - https://github.com/zw3rk/mobile-core-tools - -- Folders: +- Prepare folders: ```sh mkdir -p ./apps/ios/Libraries/mac ./apps/ios/Libraries/ios ./apps/ios/Libraries/sim @@ -14,11 +10,9 @@ ## Update binaries -1. Download binary distribution from `aarch64-darwin:lib:simplex-chat.aarch64-darwin` job at -https://ci.zw3rk.com/jobset/zw3rk/simplex-chat -and extract binaries to `./apps/ios/Libraries/mac`. +1. Extract binaries to `./apps/ios/Libraries/mac`. -2. Prepare binaries for iOS and for Xcode simulator: +2. Prepare binaries: ```sh chmod +w ./apps/ios/Libraries/mac/* @@ -30,13 +24,11 @@ and extract binaries to `./apps/ios/Libraries/mac`. 3. Put binaries into `./apps/ios/Libraries`. - For simulator: - ```sh cp ./apps/ios/Libraries/sim/* ./apps/ios/Libraries ``` - For iOS: + or: ```sh cp ./apps/ios/Libraries/ios/* ./apps/ios/Libraries diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index 2cceaba497..5a107ce5a5 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -71,7 +71,14 @@ struct ContentView: View { var body: some View { if let user = chatModel.currentUser { ChatListView(user: user) - .onAppear { chatSendCmd(chatModel, .apiGetChats) } + .onAppear { + do { + let chats = try apiGetChats() + chatModel.chatPreviews = chats + } catch { + print(error) + } + } } else { WelcomeView() } diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 0d50bdb867..cd87d3e035 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -14,7 +14,7 @@ final class ChatModel: ObservableObject { @Published var chats: Dictionary = [:] @Published var chatPreviews: [ChatPreview] = [] @Published var chatItems: [ChatItem] = [] - @Published var apiResponses: [APIResponse] = [] + @Published var terminalItems: [TerminalItem] = [] } struct User: Codable { @@ -25,6 +25,14 @@ struct User: Codable { var activeUser: Bool } +let sampleUser = User( + userId: 1, + userContactId: 1, + localDisplayName: "alice", + profile: sampleProfile, + activeUser: true +) + typealias ContactName = String typealias GroupName = String @@ -34,7 +42,12 @@ struct Profile: Codable { var fullName: String } -struct ChatPreview: Identifiable, Codable { +let sampleProfile = Profile( + displayName: "alice", + fullName: "Alice" +) + +struct ChatPreview: Identifiable, Decodable { var chatInfo: ChatInfo var lastChatItem: ChatItem? @@ -43,6 +56,11 @@ struct ChatPreview: Identifiable, Codable { } } +enum ChatType: String { + case direct + case group +} + enum ChatInfo: Identifiable, Codable { case direct(contact: Contact) case group(groupInfo: GroupInfo) @@ -65,11 +83,11 @@ enum ChatInfo: Identifiable, Codable { } } - var apiType: String { + var chatType: ChatType { get { switch self { - case .direct(_): return "direct" - case .group(_): return "group" + case .direct: return .direct + case .group: return .group } } } @@ -84,9 +102,18 @@ enum ChatInfo: Identifiable, Codable { } } -class Chat: Codable { +let sampleDirectChatInfo = ChatInfo.direct(contact: sampleContact) + +let sampleGroupChatInfo = ChatInfo.group(groupInfo: sampleGroupInfo) + +class Chat: Decodable { var chatInfo: ChatInfo var chatItems: [ChatItem] + + init(chatInfo: ChatInfo, chatItems: [ChatItem]) { + self.chatInfo = chatInfo + self.chatItems = chatItems + } } struct Contact: Identifiable, Codable { @@ -98,6 +125,12 @@ struct Contact: Identifiable, Codable { var id: String { get { "@\(contactId)" } } } +let sampleContact = Contact( + contactId: 1, + localDisplayName: "alice", + profile: sampleProfile +) + struct GroupInfo: Identifiable, Codable { var groupId: Int64 var localDisplayName: GroupName @@ -106,16 +139,32 @@ struct GroupInfo: Identifiable, Codable { var id: String { get { "#\(groupId)" } } } +let sampleGroupInfo = GroupInfo( + groupId: 1, + localDisplayName: "team", + groupProfile: sampleGroupProfile +) + struct GroupProfile: Codable { var displayName: String var fullName: String } +let sampleGroupProfile = GroupProfile( + displayName: "team", + fullName: "My Team" +) + struct GroupMember: Codable { } -struct ChatItem: Identifiable, Codable { +struct AChatItem: Decodable { + var chatInfo: ChatInfo + var chatItem: ChatItem +} + +struct ChatItem: Identifiable, Decodable { var chatDir: CIDirection var meta: CIMeta var content: CIContent @@ -123,21 +172,21 @@ struct ChatItem: Identifiable, Codable { var id: Int64 { get { meta.itemId } } } -enum CIDirection: Codable { +enum CIDirection: Decodable { case directSnd case directRcv case groupSnd case groupRcv(GroupMember) } -struct CIMeta: Codable { +struct CIMeta: Decodable { var itemId: Int64 var itemTs: Date var itemText: String var createdAt: Date } -enum CIContent: Codable { +enum CIContent: Decodable { case sndMsgContent(msgContent: MsgContent) case rcvMsgContent(msgContent: MsgContent) // files etc. @@ -152,24 +201,44 @@ enum CIContent: Codable { } } -enum MsgContent: Codable { +enum MsgContent { case text(String) - case unknown(type: String, text: String, json: String) - case invalid(json: String) + case unknown(type: String, text: String) + case invalid(error: String) - init(from: Decoder) throws { - self = .invalid(json: "") - } - var string: String { get { switch self { - case let .text(str): return str - case .unknown: return "unknown" + case let .text(text): return text + case let .unknown(_, text): return text case .invalid: return "invalid" } } } + + enum CodingKeys: String, CodingKey { + case type + case text + } +} + +extension MsgContent: Decodable { + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + do { + let type = try container.decode(String.self, forKey: CodingKeys.type) + switch type { + case "text": + let text = try container.decode(String.self, forKey: CodingKeys.text) + self = .text(text) + default: + let text = try? container.decode(String.self, forKey: CodingKeys.text) + self = .unknown(type: type, text: text ?? "unknown message format") + } + } catch { + self = .invalid(error: String(describing: error)) + } + } } //func parseMsgContent(_ mc: SomeMsgContent) -> MsgContent { diff --git a/apps/ios/Shared/Model/JSON.swift b/apps/ios/Shared/Model/JSON.swift new file mode 100644 index 0000000000..2123c54811 --- /dev/null +++ b/apps/ios/Shared/Model/JSON.swift @@ -0,0 +1,38 @@ +// +// JSON.swift +// SimpleX +// +// Created by Evgeny Poberezkin on 29/01/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import Foundation + +func getJSONDecoder() -> JSONDecoder { + let jd = JSONDecoder() + let fracSeconds = getDateFormatter("yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ") + let noFracSeconds = getDateFormatter("yyyy-MM-dd'T'HH:mm:ssZZZZZ") + jd.dateDecodingStrategy = .custom { decoder in + let container = try decoder.singleValueContainer() + let string = try container.decode(String.self) + if let date = fracSeconds.date(from: string) ?? noFracSeconds.date(from: string) { + return date + } + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date: \(string)") + } + return jd +} + +func getJSONEncoder() -> JSONEncoder { + let je = JSONEncoder() + je.dateEncodingStrategy = .iso8601 + return je +} + +private func getDateFormatter(_ format: String) -> DateFormatter { + let df = DateFormatter() + df.locale = Locale(identifier: "en_US_POSIX") + df.dateFormat = format + df.timeZone = TimeZone(secondsFromGMT: 0) + return df +} diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index b2dd350fb0..fe022d8d55 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -7,15 +7,16 @@ // import Foundation +import UIKit private var chatStore: chat_store? private var chatController: chat_ctrl? -private let jsonDecoder = JSONDecoder() -private let jsonEncoder = JSONEncoder() +private let jsonDecoder = getJSONDecoder() +private let jsonEncoder = getJSONEncoder() enum ChatCommand { case apiGetChats - case apiGetChatItems(type: String, id: Int64) + case apiGetChatItems(type: ChatType, id: Int64) case string(String) case help @@ -25,7 +26,7 @@ enum ChatCommand { case .apiGetChats: return "/api/v1/chats" case let .apiGetChatItems(type, id): - return "/api/v1/chat/items/\(type)/\(id)" + return "/api/v1/chat/\(type)/\(id)" case let .string(str): return str case .help: return "/help" @@ -34,30 +35,26 @@ enum ChatCommand { } } -struct APIResponse: Identifiable { - var resp: ChatResponse - var id: Int64 -} - -struct APIResponseJSON: Decodable { +struct APIResponse: Decodable { var resp: ChatResponse } -enum ChatResponse: Codable { +enum ChatResponse: Decodable, Error { case response(type: String, json: String) case apiChats(chats: [ChatPreview]) case apiDirectChat(chat: Chat) // direct/ or group/, same as ChatPreview.id -// case chatHelp(String) // case newSentInvitation case contactConnected(contact: Contact) + case newChatItem(chatItem: AChatItem) var responseType: String { get { switch self { case let .response(type, _): return "* \(type)" - case .apiChats(_): return "apiChats" - case .apiDirectChat(_): return "apiDirectChat" - case .contactConnected(_): return "contactConnected" + case .apiChats: return "apiChats" + case .apiDirectChat: return "apiDirectChat" + case .contactConnected: return "contactConnected" + case .newChatItem: return "newChatItem" } } } @@ -69,6 +66,39 @@ enum ChatResponse: Codable { case let .apiChats(chats): return String(describing: chats) case let .apiDirectChat(chat): return String(describing: chat) case let .contactConnected(contact): return String(describing: contact) + case let .newChatItem(chatItem): return String(describing: chatItem) + } + } + } +} + +enum TerminalItem: Identifiable { + case cmd(Date, ChatCommand) + case resp(Date, ChatResponse) + + var id: Date { + get { + switch self { + case let .cmd(id, _): return id + case let .resp(id, _): return id + } + } + } + + var label: String { + get { + switch self { + case let .cmd(_, cmd): return "> \(cmd.cmdString.prefix(30))" + case let .resp(_, resp): return "< \(resp.responseType)" + } + } + } + + var details: String { + get { + switch self { + case let .cmd(_, cmd): return cmd.cmdString + case let .resp(_, resp): return resp.details } } } @@ -95,41 +125,52 @@ func chatCreateUser(_ p: Profile) -> User? { return user } -func chatSendCmd(_ chatModel: ChatModel, _ cmd: ChatCommand) { +func chatSendCmd(_ cmd: ChatCommand) throws -> ChatResponse { var c = cmd.cmdString.cString(using: .utf8)! - processAPIResponse(chatModel, - apiResponse( - chat_send_cmd(getChatCtrl(), &c)!)) +// TODO some mechanism to update model without passing it - maybe Publisher / Subscriber? +// DispatchQueue.main.async { +// termId += 1 +// chatModel.terminalItems.append(.cmd(termId, cmd)) +// } + return chatResponse(chat_send_cmd(getChatCtrl(), &c)!) } -func chatRecvMsg(_ chatModel: ChatModel) { - processAPIResponse(chatModel, - apiResponse( - chat_recv_msg(getChatCtrl())!)) +func chatRecvMsg() throws -> ChatResponse { + chatResponse(chat_recv_msg(getChatCtrl())!) } -private func processAPIResponse(_ chatModel: ChatModel, _ res: APIResponse?) { - if let r = res { - DispatchQueue.main.async { - chatModel.apiResponses.append(r) - switch r.resp { - case let .apiChats(chats): - chatModel.chatPreviews = chats - case let .apiDirectChat(chat): - chatModel.chats[chat.chatInfo.id] = chat - case let .contactConnected(contact): - chatModel.chatPreviews.insert( - ChatPreview(chatInfo: .direct(contact: contact)), - at: 0 - ) - default: return +func apiGetChats() throws -> [ChatPreview] { + let r = try chatSendCmd(.apiGetChats) + switch r { + case let .apiChats(chats): return chats + default: throw r + } +} -// case let .response(type, _): -// chatModel.chatItems.append(ChatItem( -// ts: Date.now, -// content: .text(type) -// )) - } +func apiGetChatItems(type: ChatType, id: Int64) throws -> Chat { + let r = try chatSendCmd(.apiGetChatItems(type: type, id: id)) + switch r { + case let .apiDirectChat(chat): return chat + 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)), + at: 0 + ) + case let .newChatItem(aChatItem): + let ci = aChatItem.chatInfo + let chat = chatModel.chats[ci.id] ?? Chat(chatInfo: ci, chatItems: []) + chatModel.chats[ci.id] = chat + chat.chatItems.append(aChatItem.chatItem) + default: + print("unsupported response: ", res) } } } @@ -139,19 +180,19 @@ private struct UserResponse: Decodable { var error: String? } -private var respId: Int64 = 0 - -private func apiResponse(_ cjson: UnsafePointer) -> APIResponse? { +private func chatResponse(_ cjson: UnsafePointer) -> ChatResponse { let s = String.init(cString: cjson) - print("apiResponse", s) + print("chatResponse", s) let d = s.data(using: .utf8)! -// TODO is there a way to do it without copying the data? e.g: +// TODO is there a way to do it without copying the data? e.g: // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) // let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) +// TODO some mechanism to update model without passing it - maybe Publisher / Subscriber? + do { - let r = try jsonDecoder.decode(APIResponseJSON.self, from: d) - return APIResponse(resp: r.resp, id: respId) + let r = try jsonDecoder.decode(APIResponse.self, from: d) + return r.resp } catch { print (error) } @@ -164,11 +205,7 @@ private func apiResponse(_ cjson: UnsafePointer) -> APIResponse? { } json = prettyJSON(j) } - respId += 1 - return APIResponse( - resp: ChatResponse.response(type: type ?? "invalid", json: json ?? s), - id: respId - ) + return ChatResponse.response(type: type ?? "invalid", json: json ?? s) } func prettyJSON(_ obj: NSDictionary) -> String? { diff --git a/apps/ios/Shared/MyPlayground.playground/Contents.swift b/apps/ios/Shared/MyPlayground.playground/Contents.swift index b81ba746c3..b46b3c1374 100644 --- a/apps/ios/Shared/MyPlayground.playground/Contents.swift +++ b/apps/ios/Shared/MyPlayground.playground/Contents.swift @@ -37,18 +37,45 @@ let ct = Contact( //,"agentConnId":"cTdFNkprSHhZZmZhdWFQVg==","createdAt":"2022-01-27T19:47:08.891646Z","connStatus":"ready"},"localDisplayName":"ep"}}}}] //""" // -//let data = str.data(using: .utf8)! + +//let str = """ +//{"resp":{"apiDirectChat":{"chat":{"chatInfo":{"direct":{"contact":{"contactId":2,"localDisplayName":"ep","profile":{"displayName":"ep","fullName":"Evgeny"},"activeConn":{"connId":1,"agentConnId":"bUk2OXZlN3lfNXFaVWRWMQ==","connLevel":0,"connType":"contact","connStatus":"ready","entityId":2,"createdAt":"2022-01-29T11:21:18.669786Z"}}}},"chatItems":[{"chatDir":{"directSnd":{}},"meta":{"itemId":1,"itemTs":"2022-01-29T11:21:47.947865Z","itemText":"hello","localItemTs":"2022-01-29T11:21:47.947865Z","createdAt":"2022-01-29T11:21:47.947865Z"},"content":{"sndMsgContent":{"msgContent":{"type":"text","text":"hello"}}}},{"chatDir":{"directRcv":{}},"meta":{"itemId":2,"itemTs":"2022-01-29T11:22:08Z","itemText":"hi","localItemTs":"2022-01-29T11:22:08Z","createdAt":"2022-01-29T11:22:08.563959Z"},"content":{"rcvMsgContent":{"msgContent":{"type":"text","text":"hi"}}}}]}}}} +//""" + +let str = "\"2022-01-29T11:21:47Z\"" + +let data = str.data(using: .utf8)! let jsonDecoder = JSONDecoder() -//let r: [ChatPreview] = try! jsonDecoder.decode([ChatPreview].self, from: data) -// -//print(r) +let df1 = DateFormatter() +df1.locale = Locale(identifier: "en_US_POSIX") +df1.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" +df1.timeZone = TimeZone(secondsFromGMT: 0) + +let df2 = DateFormatter() +df2.locale = Locale(identifier: "en_US_POSIX") +df2.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" +df2.timeZone = TimeZone(secondsFromGMT: 0) + +jsonDecoder.dateDecodingStrategy = .iso8601 // .custom { decoder in +// let container = try decoder.singleValueContainer() +// let string = try container.decode(String.self) +// if let date = df1.date(from: string) ?? df2.date(from: string) { +// return date +// } +// throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date: \(string)") +//} + + +let r: Date = try! jsonDecoder.decode(Date.self, from: data) + +print(r) struct Test: Decodable { var name: String var id: Int64 = 0 } -jsonDecoder.decode(Test.self, from: "{\"name\":\"hello\",\"id\":1}".data(using: .utf8)!) +//jsonDecoder.decode(Test.self, from: "{\"name\":\"hello\",\"id\":1}".data(using: .utf8)!) diff --git a/apps/ios/Shared/MyPlayground.playground/timeline.xctimeline b/apps/ios/Shared/MyPlayground.playground/timeline.xctimeline index 62084a5f42..7b48f2c7ec 100644 --- a/apps/ios/Shared/MyPlayground.playground/timeline.xctimeline +++ b/apps/ios/Shared/MyPlayground.playground/timeline.xctimeline @@ -3,7 +3,7 @@ version = "3.0"> diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift index 5af995644a..0666668196 100644 --- a/apps/ios/Shared/SimpleXApp.swift +++ b/apps/ios/Shared/SimpleXApp.swift @@ -10,23 +10,9 @@ import SwiftUI @main struct SimpleXApp: App { @StateObject private var chatModel = ChatModel() -// let store: chat_store init() { hs_init(0, nil) -// let dataDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.path + "/mobile_v1" -// var cstr = dataDir.cString(using: .utf8)! -// store = chat_init_store(&cstr) -// let user = String.init(cString: chat_get_user(store)) -// print(user) -// if user != "{}" { -// chatModel.currentUser = parseJSON(user) -// var data = "{ \"displayName\": \"test\", \"fullName\": \"ios test\" }".cString(using: .utf8)! -// chat_create_user(store, &data) -// } -// controller = chat_start(store) -// var cmd = "/help".cString(using: .utf8)! -// print(String.init(cString: chat_send_cmd(controller, &cmd))) } var body: some Scene { diff --git a/apps/ios/Shared/Views/ChatListView.swift b/apps/ios/Shared/Views/ChatListView.swift index d77d68a06d..5a27e2aa06 100644 --- a/apps/ios/Shared/Views/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatListView.swift @@ -14,16 +14,25 @@ struct ChatListView: View { var body: some View { DispatchQueue.global().async { - while(true) { chatRecvMsg(chatModel) } + while(true) { + do { + try processReceivedMsg(chatModel, chatRecvMsg()) + } catch { + print("error receiving message: ", error) + } + } } return VStack { // if chatModel.chats.isEmpty { - VStack { - Text("Hello chat") - Text("Active user: \(user.localDisplayName) (\(user.profile.fullName))") - } +// VStack { +// Text("Hello chat") +// Text("Active user: \(user.localDisplayName) (\(user.profile.fullName))") +// } // } + + ChatHeaderView() + NavigationView { List { NavigationLink { @@ -36,7 +45,12 @@ struct ChatListView: View { NavigationLink { ChatView(chatInfo: chatPreview.chatInfo) .onAppear { - chatSendCmd(chatModel, .apiGetChatItems(type: "direct", id: chatPreview.chatInfo.apiId)) + do { + let chat = try apiGetChatItems(type: .direct, id: chatPreview.chatInfo.apiId) + chatModel.chats[chat.chatInfo.id] = chat + } catch { + print("apiGetChatItems", error) + } } } label: { ChatPreviewView(chatPreview: chatPreview) @@ -51,6 +65,9 @@ struct ChatListView: View { //struct ChatListView_Previews: PreviewProvider { // static var previews: some View { -// ChatListView() +// let chatModel = ChatModel() +// chatModel.chatPreviews = [] +// return ChatListView(user: sampleUser) +// .environmentObject(chatModel) // } //} diff --git a/apps/ios/Shared/Views/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatPreviewView.swift index 0b49f45cb4..6dda250ba5 100644 --- a/apps/ios/Shared/Views/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatPreviewView.swift @@ -19,28 +19,8 @@ struct ChatPreviewView: View { struct ChatPreviewView_Previews: PreviewProvider { static var previews: some View { Group{ - ChatPreviewView(chatPreview: ChatPreview( - chatInfo: .direct(contact: Contact( - contactId: 123, - localDisplayName: "ep", - profile: Profile( - displayName: "ep", - fullName: "Ep" - ), - viaGroup: nil - )) - )) - - ChatPreviewView(chatPreview: ChatPreview( - chatInfo: .group(groupInfo: GroupInfo( - groupId: 123, - localDisplayName: "team", - groupProfile: GroupProfile( - displayName: "team", - fullName: "My Team" - ) - )) - )) + ChatPreviewView(chatPreview: ChatPreview(chatInfo: sampleDirectChatInfo)) + ChatPreviewView(chatPreview: ChatPreview(chatInfo: sampleGroupChatInfo)) } .previewLayout(.fixed(width: 300, height: 70)) } diff --git a/apps/ios/Shared/Views/ChatView.swift b/apps/ios/Shared/Views/ChatView.swift index eb3b89ee19..9e4a040057 100644 --- a/apps/ios/Shared/Views/ChatView.swift +++ b/apps/ios/Shared/Views/ChatView.swift @@ -10,6 +10,8 @@ import SwiftUI struct ChatView: View { @EnvironmentObject var chatModel: ChatModel + @State var inProgress: Bool = false + var chatInfo: ChatInfo var body: some View { VStack { @@ -26,12 +28,28 @@ struct ChatView: View { } else { Text("unexpected: chat not found...") } + + Spacer() + + SendMessageView(sendMessage: sendMessage, inProgress: inProgress) } } + + func sendMessage(_ msg: String) { + + } } -//struct ChatView_Previews: PreviewProvider { -// static var previews: some View { -// ChatView() -// } -//} +struct ChatView_Previews: PreviewProvider { + static var previews: some View { + let chatModel = ChatModel() + chatModel.chats = [ + "@1": Chat( + chatInfo: sampleDirectChatInfo, + chatItems: [] + ) + ] + return ChatView(chatInfo: sampleDirectChatInfo) + .environmentObject(chatModel) + } +} diff --git a/apps/ios/Shared/Views/Helpers/ChatHeaderView.swift b/apps/ios/Shared/Views/Helpers/ChatHeaderView.swift new file mode 100644 index 0000000000..d2cf7fa70e --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/ChatHeaderView.swift @@ -0,0 +1,43 @@ +// +// ChatHeaderView.swift +// SimpleX +// +// Created by Evgeny Poberezkin on 29/01/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct ChatHeaderView: View { + @State private var showAddChat = false + @State private var inviteContact = false + @State private var scanQRCode = false + @State private var createGroup = false + + var body: some View { + HStack { + Button("Edit", action: {}) + Spacer() + Text("Your chats") + Spacer() + Button { showAddChat = true } label: { + Image(systemName: "square.and.pencil") + } + .confirmationDialog("Start new chat", isPresented: $showAddChat, titleVisibility: .visible) { + Button("Invite contact") { inviteContact = true } + Button("Scan QR code") { scanQRCode = true } + Button("Create group") { createGroup = true } + } + .sheet(isPresented: $inviteContact, content: { InviteContactView() }) + .sheet(isPresented: $scanQRCode, content: { ScanQRCodeView() }) + .sheet(isPresented: $createGroup, content: { CreateGroupView() }) + } + .padding(.horizontal) + } +} + +struct ChatHeaderView_Previews: PreviewProvider { + static var previews: some View { + ChatHeaderView() + } +} diff --git a/apps/ios/Shared/Views/Helpers/CreateGroupView.swift b/apps/ios/Shared/Views/Helpers/CreateGroupView.swift new file mode 100644 index 0000000000..89a65f1ecd --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/CreateGroupView.swift @@ -0,0 +1,21 @@ +// +// CreateGroupView.swift +// SimpleX +// +// Created by Evgeny Poberezkin on 29/01/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct CreateGroupView: View { + var body: some View { + Text("CreateGroupView") + } +} + +struct CreateGroupView_Previews: PreviewProvider { + static var previews: some View { + CreateGroupView() + } +} diff --git a/apps/ios/Shared/Views/Helpers/InviteContactView.swift b/apps/ios/Shared/Views/Helpers/InviteContactView.swift new file mode 100644 index 0000000000..0c6355815a --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/InviteContactView.swift @@ -0,0 +1,21 @@ +// +// InviteContactView.swift +// SimpleX +// +// Created by Evgeny Poberezkin on 29/01/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct InviteContactView: View { + var body: some View { + Text("InviteContactView") + } +} + +struct InviteContactView_Previews: PreviewProvider { + static var previews: some View { + InviteContactView() + } +} diff --git a/apps/ios/Shared/Views/MessageView.swift b/apps/ios/Shared/Views/Helpers/MessageView.swift similarity index 100% rename from apps/ios/Shared/Views/MessageView.swift rename to apps/ios/Shared/Views/Helpers/MessageView.swift diff --git a/apps/ios/Shared/Views/Helpers/ScanQRCodeView.swift b/apps/ios/Shared/Views/Helpers/ScanQRCodeView.swift new file mode 100644 index 0000000000..1a21463ba3 --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/ScanQRCodeView.swift @@ -0,0 +1,21 @@ +// +// ScanQRCodeView.swift +// SimpleX +// +// Created by Evgeny Poberezkin on 29/01/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct ScanQRCodeView: View { + var body: some View { + Text("ScanQRCodeView") + } +} + +struct ScanQRCodeView_Previews: PreviewProvider { + static var previews: some View { + ScanQRCodeView() + } +} diff --git a/apps/ios/Shared/Views/Helpers/SendMessageView.swift b/apps/ios/Shared/Views/Helpers/SendMessageView.swift new file mode 100644 index 0000000000..dc9fe1bfb3 --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/SendMessageView.swift @@ -0,0 +1,47 @@ +// +// SendMessageView.swift +// SimpleX +// +// Created by Evgeny Poberezkin on 29/01/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct SendMessageView: View { + var sendMessage: (String) -> Void + var inProgress: Bool = false + @State var command: String = "" + + var body: some View { + HStack { + TextField("Message...", text: $command) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + .frame(minHeight: 30) + .onSubmit(submit) + + if (inProgress) { + ProgressView() + .frame(width: 40, height: 20, alignment: .center) + } else { + Button("Send", action :submit) + .disabled(command.isEmpty) + } + } + .frame(minHeight: 30) + .padding() + } + + func submit() { + sendMessage(command) + command = "" + } +} + +struct SendMessageView_Previews: PreviewProvider { + static var previews: some View { + SendMessageView(sendMessage: { print ($0) }) + } +} diff --git a/apps/ios/Shared/Views/TerminalView.swift b/apps/ios/Shared/Views/TerminalView.swift index 11521fb680..a58f1ea9ab 100644 --- a/apps/ios/Shared/Views/TerminalView.swift +++ b/apps/ios/Shared/Views/TerminalView.swift @@ -10,20 +10,19 @@ import SwiftUI struct TerminalView: View { @EnvironmentObject var chatModel: ChatModel - @State var command: String = "" @State var inProgress: Bool = false var body: some View { VStack { ScrollView { LazyVStack { - ForEach(chatModel.apiResponses) { r in + ForEach(chatModel.terminalItems) { item in NavigationLink { ScrollView { - Text(r.resp.details) + Text(item.details) } } label: { - Text(r.resp.responseType) + Text(item.label) .frame(width: 360, height: 30, alignment: .leading) } } @@ -33,25 +32,24 @@ struct TerminalView: View { Spacer() - HStack { - TextField("Message...", text: $command) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .frame(minHeight: 30) - Button(action: sendMessage) { - Text("Send") - }.disabled(command.isEmpty) - } - .frame(minHeight: 30) - .padding() + SendMessageView(sendMessage: sendMessage, inProgress: inProgress) } } - func sendMessage() { + func sendMessage(_ cmdStr: String) { + let cmd = ChatCommand.string(cmdStr) + chatModel.terminalItems.append(.cmd(Date.now, cmd)) + DispatchQueue.global().async { - let cmd: String = self.$command.wrappedValue inProgress = true - command = "" - chatSendCmd(chatModel, ChatCommand.string(cmd)) + do { + let r = try chatSendCmd(cmd) + DispatchQueue.main.async { + chatModel.terminalItems.append(.resp(Date.now, r)) + } + } catch { + print(error) + } inProgress = false } } @@ -60,9 +58,9 @@ struct TerminalView: View { struct TerminalView_Previews: PreviewProvider { static var previews: some View { let chatModel = ChatModel() - chatModel.apiResponses = [ - APIResponse(resp: ChatResponse.response(type: "contactSubscribed", json: "{}"), id: 1), - APIResponse(resp: ChatResponse.response(type: "newChatItem", json: "{}"), id: 2) + chatModel.terminalItems = [ + .resp(Date.now, ChatResponse.response(type: "contactSubscribed", json: "{}")), + .resp(Date.now, ChatResponse.response(type: "newChatItem", json: "{}")) ] return NavigationView { TerminalView() diff --git a/apps/ios/Shared/Views/WelcomeView.swift b/apps/ios/Shared/Views/WelcomeView.swift index 7db90b11b5..759090631c 100644 --- a/apps/ios/Shared/Views/WelcomeView.swift +++ b/apps/ios/Shared/Views/WelcomeView.swift @@ -20,8 +20,12 @@ struct WelcomeView: View { Text("Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile.") .padding(.bottom) TextField("Display name", text: $displayName) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) .padding(.bottom) TextField("Full name (optional)", text: $fullName) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) .padding(.bottom) Button("Create") { let profile = Profile( diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 085246786a..5a68218041 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -9,10 +9,6 @@ /* 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 */; }; - 5C1AEB82279F4A6400247F08 /* libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C1AEB7D279F4A6400247F08 /* libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD.a */; }; - 5C1AEB83279F4A6400247F08 /* libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C1AEB7D279F4A6400247F08 /* libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD.a */; }; - 5C1AEB84279F4A6400247F08 /* libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C1AEB7E279F4A6400247F08 /* libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD-ghc8.10.7.a */; }; - 5C1AEB85279F4A6400247F08 /* libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C1AEB7E279F4A6400247F08 /* libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD-ghc8.10.7.a */; }; 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 +23,10 @@ 5C2E261027A30FDC00F70299 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260E27A30FDC00F70299 /* ChatView.swift */; }; 5C2E261227A30FEA00F70299 /* TerminalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E261127A30FEA00F70299 /* TerminalView.swift */; }; 5C2E261327A30FEA00F70299 /* TerminalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E261127A30FEA00F70299 /* TerminalView.swift */; }; + 5C44B6A027A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C44B69E27A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w.a */; }; + 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 */; }; 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 */; }; @@ -35,6 +35,10 @@ 5C764E85279C748C000C6508 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C764E7C279C71DB000C6508 /* libz.tbd */; }; 5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C764E88279CBCB3000C6508 /* ChatModel.swift */; }; 5C764E8A279CBCB3000C6508 /* ChatModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C764E88279CBCB3000C6508 /* ChatModel.swift */; }; + 5C9FD96B27A56D4D0075386C /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9FD96A27A56D4D0075386C /* JSON.swift */; }; + 5C9FD96C27A56D4D0075386C /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9FD96A27A56D4D0075386C /* JSON.swift */; }; + 5C9FD96E27A5D6ED0075386C /* SendMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */; }; + 5C9FD96F27A5D6ED0075386C /* SendMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */; }; 5CA059DC279559F40002BEB4 /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059DB279559F40002BEB4 /* Tests_iOS.swift */; }; 5CA059DE279559F40002BEB4 /* Tests_iOSLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059DD279559F40002BEB4 /* Tests_iOSLaunchTests.swift */; }; 5CA059E8279559F40002BEB4 /* Tests_macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059E7279559F40002BEB4 /* Tests_macOS.swift */; }; @@ -49,6 +53,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 */; }; + 5CCD403127A5F1C600368C90 /* ChatHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403027A5F1C600368C90 /* ChatHeaderView.swift */; }; + 5CCD403227A5F1C600368C90 /* ChatHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403027A5F1C600368C90 /* ChatHeaderView.swift */; }; + 5CCD403427A5F6DF00368C90 /* InviteContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* InviteContactView.swift */; }; + 5CCD403527A5F6DF00368C90 /* InviteContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* InviteContactView.swift */; }; + 5CCD403727A5F9A200368C90 /* ScanQRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanQRCodeView.swift */; }; + 5CCD403827A5F9A200368C90 /* ScanQRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanQRCodeView.swift */; }; + 5CCD403A27A5F9BE00368C90 /* CreateGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */; }; + 5CCD403B27A5F9BE00368C90 /* CreateGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -70,8 +82,6 @@ /* Begin PBXFileReference section */ 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreviewView.swift; sourceTree = ""; }; - 5C1AEB7D279F4A6400247F08 /* libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD.a"; sourceTree = ""; }; - 5C1AEB7E279F4A6400247F08 /* libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD-ghc8.10.7.a"; 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 = ""; }; @@ -80,12 +90,16 @@ 5C2E260A27A30CFA00F70299 /* ChatListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListView.swift; sourceTree = ""; }; 5C2E260E27A30FDC00F70299 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; 5C764E7E279C7275000C6508 /* SimpleX (macOS)-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SimpleX (macOS)-Bridging-Header.h"; sourceTree = ""; }; 5C764E7F279C7276000C6508 /* dummy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = dummy.m; sourceTree = ""; }; 5C764E88279CBCB3000C6508 /* ChatModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatModel.swift; sourceTree = ""; }; + 5C9FD96A27A56D4D0075386C /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; + 5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageView.swift; sourceTree = ""; }; 5CA059C3279559F40002BEB4 /* SimpleXApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXApp.swift; sourceTree = ""; }; 5CA059C4279559F40002BEB4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 5CA059C5279559F40002BEB4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -99,6 +113,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 = ""; }; + 5CCD403027A5F1C600368C90 /* ChatHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHeaderView.swift; sourceTree = ""; }; + 5CCD403327A5F6DF00368C90 /* InviteContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteContactView.swift; sourceTree = ""; }; + 5CCD403627A5F9A200368C90 /* ScanQRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanQRCodeView.swift; sourceTree = ""; }; + 5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateGroupView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -107,11 +125,11 @@ buildActionMask = 2147483647; files = ( 5C764E83279C748B000C6508 /* libz.tbd in Frameworks */, - 5C1AEB84279F4A6400247F08 /* libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD-ghc8.10.7.a in Frameworks */, 5C764E82279C748B000C6508 /* libiconv.tbd in Frameworks */, 5C1AEB86279F4A6400247F08 /* libffi.a in Frameworks */, + 5C44B6A227A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w-ghc8.10.7.a in Frameworks */, 5C1AEB88279F4A6400247F08 /* libgmp.a in Frameworks */, - 5C1AEB82279F4A6400247F08 /* libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD.a in Frameworks */, + 5C44B6A027A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w.a in Frameworks */, 5C1AEB8A279F4A6400247F08 /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -121,11 +139,11 @@ buildActionMask = 2147483647; files = ( 5C764E85279C748C000C6508 /* libz.tbd in Frameworks */, - 5C1AEB85279F4A6400247F08 /* libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD-ghc8.10.7.a in Frameworks */, 5C764E84279C748C000C6508 /* libiconv.tbd in Frameworks */, 5C1AEB87279F4A6400247F08 /* libffi.a in Frameworks */, + 5C44B6A327A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w-ghc8.10.7.a in Frameworks */, 5C1AEB89279F4A6400247F08 /* libgmp.a in Frameworks */, - 5C1AEB83279F4A6400247F08 /* libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD.a in Frameworks */, + 5C44B6A127A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w.a in Frameworks */, 5C1AEB8B279F4A6400247F08 /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -150,9 +168,9 @@ 5C2E260D27A30E2400F70299 /* Views */ = { isa = PBXGroup; children = ( + 5C5F4AC227A5E9AF00B51EF1 /* Helpers */, 5CA05A4B27974EB60002BEB4 /* WelcomeView.swift */, 5C2E260A27A30CFA00F70299 /* ChatListView.swift */, - 5CA05A4E279752D00002BEB4 /* MessageView.swift */, 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */, 5C2E260E27A30FDC00F70299 /* ChatView.swift */, 5C2E261127A30FEA00F70299 /* TerminalView.swift */, @@ -160,14 +178,27 @@ path = Views; sourceTree = ""; }; + 5C5F4AC227A5E9AF00B51EF1 /* Helpers */ = { + isa = PBXGroup; + children = ( + 5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */, + 5CA05A4E279752D00002BEB4 /* MessageView.swift */, + 5CCD403027A5F1C600368C90 /* ChatHeaderView.swift */, + 5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */, + 5CCD403627A5F9A200368C90 /* ScanQRCodeView.swift */, + 5CCD403327A5F6DF00368C90 /* InviteContactView.swift */, + ); + path = Helpers; + sourceTree = ""; + }; 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( 5C1AEB7F279F4A6400247F08 /* libffi.a */, 5C1AEB80279F4A6400247F08 /* libgmp.a */, 5C1AEB81279F4A6400247F08 /* libgmpxx.a */, - 5C1AEB7E279F4A6400247F08 /* libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD-ghc8.10.7.a */, - 5C1AEB7D279F4A6400247F08 /* libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD.a */, + 5C44B69F27A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w-ghc8.10.7.a */, + 5C44B69E27A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w.a */, ); path = Libraries; sourceTree = ""; @@ -186,6 +217,7 @@ children = ( 5C764E88279CBCB3000C6508 /* ChatModel.swift */, 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */, + 5C9FD96A27A56D4D0075386C /* JSON.swift */, ); path = Model; sourceTree = ""; @@ -419,12 +451,18 @@ 5C764E80279C7276000C6508 /* dummy.m in Sources */, 5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */, 5C2E261227A30FEA00F70299 /* TerminalView.swift in Sources */, + 5C9FD96B27A56D4D0075386C /* JSON.swift in Sources */, + 5C9FD96E27A5D6ED0075386C /* SendMessageView.swift in Sources */, + 5CCD403127A5F1C600368C90 /* ChatHeaderView.swift in Sources */, 5CA05A4F279752D00002BEB4 /* MessageView.swift in Sources */, 5CA059ED279559F40002BEB4 /* ContentView.swift in Sources */, + 5CCD403427A5F6DF00368C90 /* InviteContactView.swift in Sources */, 5CA05A4C27974EB60002BEB4 /* WelcomeView.swift in Sources */, 5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */, 5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */, 5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */, + 5CCD403727A5F9A200368C90 /* ScanQRCodeView.swift in Sources */, + 5CCD403A27A5F9BE00368C90 /* CreateGroupView.swift in Sources */, 5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */, 5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */, ); @@ -437,12 +475,18 @@ 5C764E81279C7276000C6508 /* dummy.m in Sources */, 5C063D2827A4564100AEC577 /* ChatPreviewView.swift in Sources */, 5C2E261327A30FEA00F70299 /* TerminalView.swift in Sources */, + 5C9FD96C27A56D4D0075386C /* JSON.swift in Sources */, + 5C9FD96F27A5D6ED0075386C /* SendMessageView.swift in Sources */, + 5CCD403227A5F1C600368C90 /* ChatHeaderView.swift in Sources */, 5CA05A50279752D00002BEB4 /* MessageView.swift in Sources */, 5CA059EE279559F40002BEB4 /* ContentView.swift in Sources */, + 5CCD403527A5F6DF00368C90 /* InviteContactView.swift in Sources */, 5CA05A4D27974EB60002BEB4 /* WelcomeView.swift in Sources */, 5C2E261027A30FDC00F70299 /* ChatView.swift in Sources */, 5C2E260C27A30CFA00F70299 /* ChatListView.swift in Sources */, 5CA059EC279559F40002BEB4 /* SimpleXApp.swift in Sources */, + 5CCD403827A5F9A200368C90 /* ScanQRCodeView.swift in Sources */, + 5CCD403B27A5F9BE00368C90 /* CreateGroupView.swift in Sources */, 5C764E8A279CBCB3000C6508 /* ChatModel.swift in Sources */, 5C2E260827A2941F00F70299 /* SimpleXAPI.swift in Sources */, );