swift API for chat, started chat UI (#228)

* started swift API for chat

* skeleton UI

* show all chat responses in Terminal view

* show chat list in UI

* refactor swift API
This commit is contained in:
Evgeny Poberezkin 2022-01-29 11:10:04 +00:00 committed by GitHub
parent 55dde3531e
commit 7c36ee7955
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 845 additions and 99 deletions

View File

@ -46,62 +46,59 @@
import SwiftUI
struct ContentView: View {
var controller: controller
@EnvironmentObject var chatModel: ChatModel
init(controller: controller) {
self.controller = controller
}
// 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")
}
// @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 {
DispatchQueue.global().async {
while(true) {
let msg = String.init(cString: chat_recv_msg(controller))
DispatchQueue.main.async {
addLine(line: msg)
}
}
if let user = chatModel.currentUser {
ChatListView(user: user)
.onAppear { chatSendCmd(chatModel, .apiGetChats) }
} 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()
}
}
// 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()
// }
}
}

View File

@ -8,11 +8,13 @@
import Foundation
import Combine
import SwiftUI
final class ChatModel: ObservableObject {
@Published var currentUser: User?
@Published var channels: [ChatChannel] = []
@Published var chats: Dictionary<String, Chat> = [:]
@Published var chatPreviews: [ChatPreview] = []
@Published var chatItems: [ChatItem] = []
@Published var apiResponses: [APIResponse] = []
}
struct User: Codable {
@ -32,22 +34,76 @@ struct Profile: Codable {
var fullName: String
}
enum ChatChannel {
case contact(ContactInfo, [ChatMessage])
case group(GroupInfo, [ChatMessage])
struct ChatPreview: Identifiable, Codable {
var chatInfo: ChatInfo
var lastChatItem: ChatItem?
var id: String {
get { chatInfo.id }
}
}
struct ContactInfo: Codable {
enum ChatInfo: Identifiable, Codable {
case direct(contact: Contact)
// case group()
var displayName: String {
get {
switch self {
case let .direct(contact): return "@\(contact.localDisplayName)"
// case let .group(groupInfo, _): return "#\(groupInfo.localDisplayName)"
}
}
}
var id: String {
get {
switch self {
case let .direct(contact): return "@\(contact.contactId)"
// case let .group(contact): return group.id
}
}
}
var apiType: String {
get {
switch self {
case .direct(_): return "direct"
// case let .group(_): return "group"
}
}
}
var apiId: Int64 {
get {
switch self {
case let .direct(contact): return contact.contactId
// case let .group(contact): return group.id
}
}
}
}
class Chat: Codable {
var chatInfo: ChatInfo
var chatItems: [ChatItem]
}
struct Contact: Identifiable, Codable {
var contactId: Int64
var localDisplayName: ContactName
var profile: Profile
var viaGroup: Int64?
var id: String { get { "@\(contactId)" } }
}
struct GroupInfo: Codable {
struct GroupInfo: Identifiable, Codable {
var groupId: Int64
var localDisplayName: GroupName
var groupProfile: GroupProfile
var id: String { get { "#\(groupId)" } }
}
struct GroupProfile: Codable {
@ -55,13 +111,76 @@ struct GroupProfile: Codable {
var fullName: String
}
struct ChatMessage {
var from: ContactInfo?
var ts: Date
var content: MsgContent
struct GroupMember: Codable {
}
enum MsgContent {
case text(String)
case unknown
struct ChatItem: Identifiable, Codable {
var chatDir: CIDirection
var meta: CIMeta
var content: CIContent
var id: Int64 { get { meta.itemId } }
}
enum CIDirection: Codable {
case directSnd
case directRcv
case groupSnd
case groupRcv(GroupMember)
}
struct CIMeta: Codable {
var itemId: Int64
var itemTs: Date
var itemText: String
var createdAt: Date
}
enum CIContent: Codable {
case sndMsgContent(msgContent: MsgContent)
case rcvMsgContent(msgContent: MsgContent)
// files etc.
var text: String {
get {
switch self {
case let .sndMsgContent(mc): return mc.string
case let .rcvMsgContent(mc): return mc.string
}
}
}
}
enum MsgContent: Codable {
case text(String)
case unknown(type: String, text: String, json: String)
case invalid(json: 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 .invalid: return "invalid"
}
}
}
}
//func parseMsgContent(_ mc: SomeMsgContent) -> MsgContent {
// if let type = mc["type"] as? String {
// let text_ = mc["text"] as? String
// switch type {
// case "text":
// if let text = text_ { return .text(text) }
// case let t:
// return .unknown(type: t, text: text_ ?? "unknown item", json: prettyJSON(mc) ?? "error")
// }
// }
// return .invalid(json: prettyJSON(mc) ?? "error")
//}

View File

@ -0,0 +1,220 @@
//
// ChatAPI.swift
// SimpleX
//
// Created by Evgeny Poberezkin on 27/01/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import Foundation
private var chatStore: chat_store?
private var chatController: chat_ctrl?
private let jsonDecoder = JSONDecoder()
private let jsonEncoder = JSONEncoder()
enum ChatCommand {
case apiGetChats
case apiGetChatItems(type: String, id: Int64)
case string(String)
case help
var cmdString: String {
get {
switch self {
case .apiGetChats:
return "/api/v1/chats"
case let .apiGetChatItems(type, id):
return "/api/v1/chat/items/\(type)/\(id)"
case let .string(str):
return str
case .help: return "/help"
}
}
}
}
struct APIResponse: Identifiable {
var resp: ChatResponse
var id: Int64
}
struct APIResponseJSON: Decodable {
var resp: ChatResponse
}
enum ChatResponse: Codable {
case response(type: String, json: String)
case apiChats(chats: [ChatPreview])
case apiDirectChat(chat: Chat) // direct/<id> or group/<id>, same as ChatPreview.id
// case chatHelp(String)
// case newSentInvitation
case contactConnected(contact: Contact)
var responseType: String {
get {
switch self {
case let .response(type, _): return "* \(type)"
case .apiChats(_): return "apiChats"
case .apiDirectChat(_): return "apiDirectChat"
case .contactConnected(_): return "contactConnected"
}
}
}
var details: String {
get {
switch self {
case let .response(_, json): return json
case let .apiChats(chats): return String(describing: chats)
case let .apiDirectChat(chat): return String(describing: chat)
case let .contactConnected(contact): return String(describing: contact)
}
}
}
}
func chatGetUser() -> User? {
let store = getStore()
print("chatGetUser")
let r: UserResponse? = decodeCJSON(chat_get_user(store))
let user = r?.user
if user != nil { initChatCtrl(store) }
print("user", user as Any)
return user
}
func chatCreateUser(_ p: Profile) -> User? {
let store = getStore()
print("chatCreateUser")
var str = encodeCJSON(p)
chat_create_user(store, &str)
let user = chatGetUser()
if user != nil { initChatCtrl(store) }
print("user", user as Any)
return user
}
func chatSendCmd(_ chatModel: ChatModel, _ cmd: ChatCommand) {
var c = cmd.cmdString.cString(using: .utf8)!
processAPIResponse(chatModel,
apiResponse(
chat_send_cmd(getChatCtrl(), &c)!))
}
func chatRecvMsg(_ chatModel: ChatModel) {
processAPIResponse(chatModel,
apiResponse(
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
// case let .response(type, _):
// chatModel.chatItems.append(ChatItem(
// ts: Date.now,
// content: .text(type)
// ))
}
}
}
}
private struct UserResponse: Decodable {
var user: User?
var error: String?
}
private var respId: Int64 = 0
private func apiResponse(_ cjson: UnsafePointer<CChar>) -> APIResponse? {
let s = String.init(cString: cjson)
print("apiResponse", 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))
// let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free)
do {
let r = try jsonDecoder.decode(APIResponseJSON.self, from: d)
return APIResponse(resp: r.resp, id: respId)
} catch {
print (error)
}
var type: String?
var json: String?
if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary {
if let j1 = j["resp"] as? NSDictionary, j1.count == 1 {
type = j1.allKeys[0] as? String
}
json = prettyJSON(j)
}
respId += 1
return APIResponse(
resp: ChatResponse.response(type: type ?? "invalid", json: json ?? s),
id: respId
)
}
func prettyJSON(_ obj: NSDictionary) -> String? {
if let d = try? JSONSerialization.data(withJSONObject: obj, options: .prettyPrinted) {
return String(decoding: d, as: UTF8.self)
}
return nil
}
private func getStore() -> chat_store {
if let store = chatStore { return store }
let dataDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.path + "/mobile_v1"
var cstr = dataDir.cString(using: .utf8)!
chatStore = chat_init_store(&cstr)
return chatStore!
}
private func initChatCtrl(_ store: chat_store) {
if chatController == nil {
chatController = chat_start(store)
}
}
private func getChatCtrl() -> chat_ctrl {
if let controller = chatController { return controller }
fatalError("Chat controller was not started!")
}
private func decodeCJSON<T: Decodable>(_ cjson: UnsafePointer<CChar>) -> 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)
return try? jsonDecoder.decode(T.self, from: d)
}
private func getJSONObject(_ cjson: UnsafePointer<CChar>) -> NSDictionary? {
let s = String.init(cString: cjson)
let d = s.data(using: .utf8)!
return try? JSONSerialization.jsonObject(with: d) as? NSDictionary
}
private func encodeCJSON<T: Encodable>(_ 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)!
}

View File

@ -0,0 +1,54 @@
import Foundation
var greeting = "Hello, playground"
let jsonEncoder = JSONEncoder()
let ct = Contact(
contactId: 123,
localDisplayName: "ep",
profile: Profile(displayName: "ep", fullName: "")
)
//let data = try! jsonEncoder.encode(ChatResponse.contactConnected(contact: ct))
//print(String(decoding: data, as: UTF8.self))
//var str = """
//{"resp":{"apiChats":{"chats":
//[{"chatItem":null,"chatInfo":{"direct":{"contact":{"contactId":2,"profile":
//{"displayName":"simplex","fullName":""},"activeConn":
//{"connLevel":0,"entityId":2,"connType":"contact","connId":1
//,"agentConnId":"QTRteFhTR1dWQnpQZHE3NQ==","createdAt":"2022-01-27T19:43:44.015562Z","connStatus":"ready"},"localDisplayName":"simplex"}}}},
//{"chatItem":null,"chatInfo":{"direct":{"contact":{"contactId":3,"profile":
//{"displayName":"ep","fullName":"Evgeny"},"activeConn":
//{"connLevel":0,"entityId":3,"connType":"contact","connId":2
//,"agentConnId":"cTdFNkprSHhZZmZhdWFQVg==","createdAt":"2022-01-27T19:47:08.891646Z","connStatus":"ready"},"localDisplayName":"ep"}}}}]}}}
//"""
//var str = """
//[{"chatItem":null,"chatInfo":{"direct":{"contact":{"contactId":2,"profile":
//{"displayName":"simplex","fullName":""},"activeConn":
//{"connLevel":0,"entityId":2,"connType":"contact","connId":1
//,"agentConnId":"QTRteFhTR1dWQnpQZHE3NQ==","createdAt":"2022-01-27T19:43:44.015562Z","connStatus":"ready"},"localDisplayName":"simplex"}}}},
//{"chatItem":null,"chatInfo":{"direct":{"contact":{"contactId":3,"profile":
//{"displayName":"ep","fullName":"Evgeny"},"activeConn":
//{"connLevel":0,"entityId":3,"connType":"contact","connId":2
//,"agentConnId":"cTdFNkprSHhZZmZhdWFQVg==","createdAt":"2022-01-27T19:47:08.891646Z","connStatus":"ready"},"localDisplayName":"ep"}}}}]
//"""
//
//let data = str.data(using: .utf8)!
let jsonDecoder = JSONDecoder()
//let r: [ChatPreview] = try! jsonDecoder.decode([ChatPreview].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)!)

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='ios' buildActiveScheme='true' importAppTypes='true'>
<timeline fileName='timeline.xctimeline'/>
</playground>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
<LoggerValueHistoryTimelineItem
documentLocation = "file:///Users/evgeny/opensource/simplex-chat/simplex-chat/apps/ios/Shared/MyPlayground.playground#CharacterRangeLen=88&amp;CharacterRangeLoc=2005&amp;EndingColumnNumber=0&amp;EndingLineNumber=53&amp;StartingColumnNumber=1&amp;StartingLineNumber=52&amp;Timestamp=665139674.041539"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
</TimelineItems>
</Timeline>

View File

@ -2,14 +2,14 @@
// Use this file to import your target's public headers that you would like to expose to Swift.
//
extern void hs_init(int argc, char ** argv[]);
extern void hs_init(int argc, char **argv[]);
typedef void* chat_store;
typedef void* controller;
typedef void* chat_ctrl;
extern chat_store chat_init_store(char * path);
extern chat_store chat_init_store(char *path);
extern char *chat_get_user(chat_store store);
extern char *chat_create_user(chat_store store, char *data);
extern controller chat_start(chat_store store);
extern char *chat_send_cmd(controller ctl, char *cmd);
extern char *chat_recv_msg(controller ctl);
extern chat_ctrl chat_start(chat_store store);
extern char *chat_send_cmd(chat_ctrl ctl, char *cmd);
extern char *chat_recv_msg(chat_ctrl ctl);

View File

@ -5,11 +5,11 @@
extern void hs_init(int argc, char ** argv[]);
typedef void* chat_store;
typedef void* controller;
typedef void* chat_ctrl;
extern chat_store chat_init_store(char * path);
extern char *chat_get_user(chat_store store);
extern char *chat_create_user(chat_store store, char *data);
extern controller chat_start(chat_store store);
extern char *chat_send_cmd(controller ctl, char *cmd);
extern char *chat_recv_msg(controller ctl);
extern chat_ctrl chat_start(chat_store store);
extern char *chat_send_cmd(chat_ctrl ctl, char *cmd);
extern char *chat_recv_msg(chat_ctrl ctl);

View File

@ -9,26 +9,33 @@ import SwiftUI
@main
struct SimpleXApp: App {
private let controller: controller
@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)!
let store = chat_init_store(&cstr)
let user = String.init(cString: chat_get_user(store))
print(user)
if 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)))
// 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 {
WindowGroup {
ContentView(controller: controller)
ContentView()
.environmentObject(chatModel)
.onAppear() {
chatModel.currentUser = chatGetUser()
}
}
}
}

View File

@ -0,0 +1,56 @@
//
// ChatListView.swift
// SimpleX
//
// Created by Evgeny Poberezkin on 27/01/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
struct ChatListView: View {
@EnvironmentObject var chatModel: ChatModel
var user: User
var body: some View {
DispatchQueue.global().async {
while(true) { chatRecvMsg(chatModel) }
}
return VStack {
// if chatModel.chats.isEmpty {
VStack {
Text("Hello chat")
Text("Active user: \(user.localDisplayName) (\(user.profile.fullName))")
}
// }
NavigationView {
List {
NavigationLink {
TerminalView()
} label: {
Text("Terminal")
}
ForEach(chatModel.chatPreviews) { chatPreview in
NavigationLink {
ChatView(chatInfo: chatPreview.chatInfo)
.onAppear {
chatSendCmd(chatModel, .apiGetChatItems(type: "direct", id: chatPreview.chatInfo.apiId))
}
} label: {
ChatPreviewView(chatPreview: chatPreview)
}
}
}
}
.navigationViewStyle(.stack)
}
}
}
//struct ChatListView_Previews: PreviewProvider {
// static var previews: some View {
// ChatListView()
// }
//}

View File

@ -0,0 +1,33 @@
//
// ChatPreviewView.swift
// SimpleX
//
// Created by Evgeny Poberezkin on 28/01/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
struct ChatPreviewView: View {
var chatPreview: ChatPreview
var body: some View {
Text(chatPreview.chatInfo.displayName)
}
}
struct ChatPreviewView_Previews: PreviewProvider {
static var previews: some View {
ChatPreviewView(chatPreview: ChatPreview(
chatInfo: .direct(contact: Contact(
contactId: 123,
localDisplayName: "ep",
profile: Profile(
displayName: "ep",
fullName: "Ep"
),
viaGroup: nil
))
))
}
}

View File

@ -0,0 +1,37 @@
//
// ChatView.swift
// SimpleX
//
// Created by Evgeny Poberezkin on 27/01/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
struct ChatView: View {
@EnvironmentObject var chatModel: ChatModel
var chatInfo: ChatInfo
var body: some View {
VStack {
if let chat: Chat = chatModel.chats[chatInfo.id] {
VStack {
ScrollView {
LazyVStack {
ForEach(chat.chatItems) { chatItem in
Text(chatItem.content.text)
}
}
}
}
} else {
Text("unexpected: chat not found...")
}
}
}
}
//struct ChatView_Previews: PreviewProvider {
// static var previews: some View {
// ChatView()
// }
//}

View File

@ -0,0 +1,73 @@
//
// TerminalView.swift
// SimpleX
//
// Created by Evgeny Poberezkin on 27/01/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
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
NavigationLink {
ScrollView {
Text(r.resp.details)
}
} label: {
Text(r.resp.responseType)
.frame(width: 360, height: 30, alignment: .leading)
}
}
}
}
.navigationViewStyle(.stack)
Spacer()
HStack {
TextField("Message...", text: $command)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(minHeight: 30)
Button(action: sendMessage) {
Text("Send")
}.disabled(command.isEmpty)
}
.frame(minHeight: 30)
.padding()
}
}
func sendMessage() {
DispatchQueue.global().async {
let cmd: String = self.$command.wrappedValue
inProgress = true
command = ""
chatSendCmd(chatModel, ChatCommand.string(cmd))
inProgress = false
}
}
}
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)
]
return NavigationView {
TerminalView()
.environmentObject(chatModel)
}
}
}

View File

@ -0,0 +1,83 @@
//
// UserView.swift
// SimpleX
//
// Created by Evgeny Poberezkin on 27/01/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
struct UserView: View {
@EnvironmentObject var chatModel: ChatModel
var user: User
// private let controller: chat_ctrl
//
// var chatStore: chat_store
// @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 chatModel.userChats.isEmpty {
VStack {
Text("Hello chat")
Text("Active user: \(user.localDisplayName) (\(user.profile.fullName))")
}
} else {
ChatList()
}
// DispatchQueue.global().async {
// while(true) {
// let msg = String.init(cString: chat_recv_msg(controller))
//
// DispatchQueue.main.async {
// addLine(line: msg)
// }
// }
// }
// 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()
// }
}
}
//struct UserView_Previews: PreviewProvider {
// static var previews: some View {
// UserView()
// }
//}

View File

@ -1,5 +1,5 @@
//
// ProfileView.swift
// WelcomeView.swift
// SimpleX
//
// Created by Evgeny Poberezkin on 18/01/2022.
@ -7,9 +7,11 @@
import SwiftUI
struct ProfileView: View {
struct WelcomeView: View {
@EnvironmentObject var chatModel: ChatModel
@State var displayName: String = ""
@State var fullName: String = ""
var body: some View {
VStack(alignment: .leading) {
Text("Create profile")
@ -20,13 +22,23 @@ struct ProfileView: View {
TextField("Display name", text: $displayName)
.padding(.bottom)
TextField("Full name (optional)", text: $fullName)
.padding(.bottom)
Button("Create") {
let profile = Profile(
displayName: displayName,
fullName: fullName
)
if let user = chatCreateUser(profile) {
chatModel.currentUser = user
}
}
}
.padding()
}
}
struct ProfileView_Previews: PreviewProvider {
struct WelcomeView_Previews: PreviewProvider {
static var previews: some View {
ProfileView()
WelcomeView()
}
}

View File

@ -7,6 +7,8 @@
objects = {
/* 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 */; };
@ -17,6 +19,14 @@
5C1AEB89279F4A6400247F08 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C1AEB80279F4A6400247F08 /* libgmp.a */; };
5C1AEB8A279F4A6400247F08 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C1AEB81279F4A6400247F08 /* libgmpxx.a */; };
5C1AEB8B279F4A6400247F08 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C1AEB81279F4A6400247F08 /* libgmpxx.a */; };
5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */; };
5C2E260827A2941F00F70299 /* SimpleXAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */; };
5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260A27A30CFA00F70299 /* ChatListView.swift */; };
5C2E260C27A30CFA00F70299 /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260A27A30CFA00F70299 /* ChatListView.swift */; };
5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260E27A30FDC00F70299 /* ChatView.swift */; };
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 */; };
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,8 +45,8 @@
5CA059EE279559F40002BEB4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059C4279559F40002BEB4 /* ContentView.swift */; };
5CA059EF279559F40002BEB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5CA059C5279559F40002BEB4 /* Assets.xcassets */; };
5CA059F0279559F40002BEB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5CA059C5279559F40002BEB4 /* Assets.xcassets */; };
5CA05A4C27974EB60002BEB4 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA05A4B27974EB60002BEB4 /* ProfileView.swift */; };
5CA05A4D27974EB60002BEB4 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA05A4B27974EB60002BEB4 /* ProfileView.swift */; };
5CA05A4C27974EB60002BEB4 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA05A4B27974EB60002BEB4 /* WelcomeView.swift */; };
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 */; };
/* End PBXBuildFile section */
@ -59,11 +69,17 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
5C063D2627A4564100AEC577 /* ChatPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreviewView.swift; sourceTree = "<group>"; };
5C1AEB7D279F4A6400247F08 /* libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.0.2-FYXW0143sf06WiRQx9DgCD.a"; sourceTree = "<group>"; };
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 = "<group>"; };
5C1AEB7F279F4A6400247F08 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
5C1AEB80279F4A6400247F08 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5C1AEB81279F4A6400247F08 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5C2E260627A2941F00F70299 /* SimpleXAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXAPI.swift; sourceTree = "<group>"; };
5C2E260927A2C63500F70299 /* MyPlayground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = MyPlayground.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
5C2E260A27A30CFA00F70299 /* ChatListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListView.swift; sourceTree = "<group>"; };
5C2E260E27A30FDC00F70299 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; };
5C2E261127A30FEA00F70299 /* TerminalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalView.swift; sourceTree = "<group>"; };
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 = "<group>"; };
@ -81,7 +97,7 @@
5CA059E3279559F40002BEB4 /* Tests macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
5CA059E7279559F40002BEB4 /* Tests_macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOS.swift; sourceTree = "<group>"; };
5CA059E9279559F40002BEB4 /* Tests_macOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOSLaunchTests.swift; sourceTree = "<group>"; };
5CA05A4B27974EB60002BEB4 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
5CA05A4B27974EB60002BEB4 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; };
5CA05A4E279752D00002BEB4 /* MessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -131,6 +147,19 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
5C2E260D27A30E2400F70299 /* Views */ = {
isa = PBXGroup;
children = (
5CA05A4B27974EB60002BEB4 /* WelcomeView.swift */,
5C2E260A27A30CFA00F70299 /* ChatListView.swift */,
5CA05A4E279752D00002BEB4 /* MessageView.swift */,
5C063D2627A4564100AEC577 /* ChatPreviewView.swift */,
5C2E260E27A30FDC00F70299 /* ChatView.swift */,
5C2E261127A30FEA00F70299 /* TerminalView.swift */,
);
path = Views;
sourceTree = "<group>";
};
5C764E5C279C70B7000C6508 /* Libraries */ = {
isa = PBXGroup;
children = (
@ -156,6 +185,7 @@
isa = PBXGroup;
children = (
5C764E88279CBCB3000C6508 /* ChatModel.swift */,
5C2E260627A2941F00F70299 /* SimpleXAPI.swift */,
);
path = Model;
sourceTree = "<group>";
@ -178,10 +208,10 @@
children = (
5C764E87279CBC8E000C6508 /* Model */,
5CA059C3279559F40002BEB4 /* SimpleXApp.swift */,
5C2E260927A2C63500F70299 /* MyPlayground.playground */,
5C764E7F279C7276000C6508 /* dummy.m */,
5CA059C4279559F40002BEB4 /* ContentView.swift */,
5CA05A4B27974EB60002BEB4 /* ProfileView.swift */,
5CA05A4E279752D00002BEB4 /* MessageView.swift */,
5C2E260D27A30E2400F70299 /* Views */,
5CA059C5279559F40002BEB4 /* Assets.xcassets */,
5C764E7D279C7275000C6508 /* SimpleX (iOS)-Bridging-Header.h */,
5C764E7E279C7275000C6508 /* SimpleX (macOS)-Bridging-Header.h */,
@ -387,11 +417,16 @@
buildActionMask = 2147483647;
files = (
5C764E80279C7276000C6508 /* dummy.m in Sources */,
5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */,
5C2E261227A30FEA00F70299 /* TerminalView.swift in Sources */,
5CA05A4F279752D00002BEB4 /* MessageView.swift in Sources */,
5CA059ED279559F40002BEB4 /* ContentView.swift in Sources */,
5CA05A4C27974EB60002BEB4 /* ProfileView.swift in Sources */,
5CA05A4C27974EB60002BEB4 /* WelcomeView.swift in Sources */,
5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */,
5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */,
5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */,
5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */,
5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -400,11 +435,16 @@
buildActionMask = 2147483647;
files = (
5C764E81279C7276000C6508 /* dummy.m in Sources */,
5C063D2827A4564100AEC577 /* ChatPreviewView.swift in Sources */,
5C2E261327A30FEA00F70299 /* TerminalView.swift in Sources */,
5CA05A50279752D00002BEB4 /* MessageView.swift in Sources */,
5CA059EE279559F40002BEB4 /* ContentView.swift in Sources */,
5CA05A4D27974EB60002BEB4 /* ProfileView.swift in Sources */,
5CA05A4D27974EB60002BEB4 /* WelcomeView.swift in Sources */,
5C2E261027A30FDC00F70299 /* ChatView.swift in Sources */,
5C2E260C27A30CFA00F70299 /* ChatListView.swift in Sources */,
5CA059EC279559F40002BEB4 /* SimpleXApp.swift in Sources */,
5C764E8A279CBCB3000C6508 /* ChatModel.swift in Sources */,
5C2E260827A2941F00F70299 /* SimpleXAPI.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};