mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 20:29:53 +00:00
add support for user addresses (#246)
* add support for user addresses * started processing contact requests * update command syntax * fix: make Profile Codable * accept/reject contact requests * update API, accept/reject contact requests
This commit is contained in:
parent
a8a7bb3c99
commit
711207743b
22 changed files with 635 additions and 346 deletions
|
@ -16,9 +16,10 @@ final class ChatModel: ObservableObject {
|
|||
@Published var chatPreviews: [Chat] = []
|
||||
@Published var chatItems: [ChatItem] = []
|
||||
@Published var terminalItems: [TerminalItem] = []
|
||||
@Published var userAddress: String?
|
||||
}
|
||||
|
||||
class User: Codable {
|
||||
class User: Decodable {
|
||||
var userId: Int64
|
||||
var userContactId: Int64
|
||||
var localDisplayName: ContactName
|
||||
|
@ -59,17 +60,20 @@ let sampleProfile = Profile(
|
|||
enum ChatType: String {
|
||||
case direct = "@"
|
||||
case group = "#"
|
||||
case contactRequest = "<@"
|
||||
}
|
||||
|
||||
enum ChatInfo: Identifiable, Codable {
|
||||
enum ChatInfo: Identifiable, Decodable {
|
||||
case direct(contact: Contact)
|
||||
case group(groupInfo: GroupInfo)
|
||||
case contactRequest(contactRequest: UserContactRequest)
|
||||
|
||||
var localDisplayName: String {
|
||||
get {
|
||||
switch self {
|
||||
case let .direct(contact): return "@\(contact.localDisplayName)"
|
||||
case let .group(groupInfo): return "#\(groupInfo.localDisplayName)"
|
||||
case let .contactRequest(contactRequest): return "< @\(contactRequest.localDisplayName)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,8 +81,9 @@ enum ChatInfo: Identifiable, Codable {
|
|||
var id: String {
|
||||
get {
|
||||
switch self {
|
||||
case let .direct(contact): return "@\(contact.contactId)"
|
||||
case let .group(groupInfo): return "#\(groupInfo.groupId)"
|
||||
case let .direct(contact): return contact.id
|
||||
case let .group(groupInfo): return groupInfo.id
|
||||
case let .contactRequest(contactRequest): return contactRequest.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +93,7 @@ enum ChatInfo: Identifiable, Codable {
|
|||
switch self {
|
||||
case .direct: return .direct
|
||||
case .group: return .group
|
||||
case .contactRequest: return .contactRequest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,6 +103,7 @@ enum ChatInfo: Identifiable, Codable {
|
|||
switch self {
|
||||
case let .direct(contact): return contact.contactId
|
||||
case let .group(groupInfo): return groupInfo.groupId
|
||||
case let .contactRequest(contactRequest): return contactRequest.contactRequestId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -106,6 +113,8 @@ let sampleDirectChatInfo = ChatInfo.direct(contact: sampleContact)
|
|||
|
||||
let sampleGroupChatInfo = ChatInfo.group(groupInfo: sampleGroupInfo)
|
||||
|
||||
let sampleContactRequestChatInfo = ChatInfo.contactRequest(contactRequest: sampleContactRequest)
|
||||
|
||||
class Chat: Decodable, Identifiable {
|
||||
var chatInfo: ChatInfo
|
||||
var chatItems: [ChatItem]
|
||||
|
@ -118,22 +127,46 @@ class Chat: Decodable, Identifiable {
|
|||
var id: String { get { chatInfo.id } }
|
||||
}
|
||||
|
||||
struct Contact: Identifiable, Codable {
|
||||
struct Contact: Identifiable, Decodable {
|
||||
var contactId: Int64
|
||||
var localDisplayName: ContactName
|
||||
var profile: Profile
|
||||
var activeConn: Connection
|
||||
var viaGroup: Int64?
|
||||
|
||||
var id: String { get { "@\(contactId)" } }
|
||||
|
||||
var connected: Bool { get { activeConn.connStatus == "ready" || activeConn.connStatus == "snd-ready" } }
|
||||
}
|
||||
|
||||
let sampleContact = Contact(
|
||||
contactId: 1,
|
||||
localDisplayName: "alice",
|
||||
profile: sampleProfile,
|
||||
activeConn: sampleConnection
|
||||
)
|
||||
|
||||
struct Connection: Decodable {
|
||||
var connStatus: String
|
||||
}
|
||||
|
||||
let sampleConnection = Connection(connStatus: "ready")
|
||||
|
||||
struct UserContactRequest: Decodable {
|
||||
var contactRequestId: Int64
|
||||
var localDisplayName: ContactName
|
||||
var profile: Profile
|
||||
|
||||
var id: String { get { "<@\(contactRequestId)" } }
|
||||
}
|
||||
|
||||
let sampleContactRequest = UserContactRequest(
|
||||
contactRequestId: 1,
|
||||
localDisplayName: "alice",
|
||||
profile: sampleProfile
|
||||
)
|
||||
|
||||
struct GroupInfo: Identifiable, Codable {
|
||||
struct GroupInfo: Identifiable, Decodable {
|
||||
var groupId: Int64
|
||||
var localDisplayName: GroupName
|
||||
var groupProfile: GroupProfile
|
||||
|
@ -157,7 +190,7 @@ let sampleGroupProfile = GroupProfile(
|
|||
fullName: "My Team"
|
||||
)
|
||||
|
||||
struct GroupMember: Codable {
|
||||
struct GroupMember: Decodable {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -22,25 +22,40 @@ enum ChatCommand {
|
|||
case connect(connReq: String)
|
||||
case apiDeleteChat(type: ChatType, id: Int64)
|
||||
case apiUpdateProfile(profile: Profile)
|
||||
case createMyAddress
|
||||
case deleteMyAddress
|
||||
case showMyAddress
|
||||
case apiAcceptContact(contactReqId: Int64)
|
||||
case apiRejectContact(contactReqId: Int64)
|
||||
case string(String)
|
||||
|
||||
var cmdString: String {
|
||||
get {
|
||||
switch self {
|
||||
case .apiGetChats:
|
||||
return "/get chats"
|
||||
return "/_get chats"
|
||||
case let .apiGetChat(type, id):
|
||||
return "/get chat \(type.rawValue)\(id)"
|
||||
return "/_get chat \(type.rawValue)\(id) count=500"
|
||||
case let .apiSendMessage(type, id, mc):
|
||||
return "/send msg \(type.rawValue)\(id) \(mc.cmdString)"
|
||||
return "/_send \(type.rawValue)\(id) \(mc.cmdString)"
|
||||
case .addContact:
|
||||
return "/c"
|
||||
return "/connect"
|
||||
case let .connect(connReq):
|
||||
return "/c \(connReq)"
|
||||
return "/connect \(connReq)"
|
||||
case let .apiDeleteChat(type, id):
|
||||
return "/_del \(type.rawValue)\(id)"
|
||||
return "/_delete \(type.rawValue)\(id)"
|
||||
case let .apiUpdateProfile(profile):
|
||||
return "/p \(profile.displayName) \(profile.fullName)"
|
||||
return "/profile \(profile.displayName) \(profile.fullName)"
|
||||
case .createMyAddress:
|
||||
return "/address"
|
||||
case .deleteMyAddress:
|
||||
return "/delete_address"
|
||||
case .showMyAddress:
|
||||
return "/show_address"
|
||||
case let .apiAcceptContact(contactReqId):
|
||||
return "/_accept \(contactReqId)"
|
||||
case let .apiRejectContact(contactReqId):
|
||||
return "/_reject \(contactReqId)"
|
||||
case let .string(str):
|
||||
return str
|
||||
}
|
||||
|
@ -62,9 +77,15 @@ enum ChatResponse: Decodable, Error {
|
|||
case contactDeleted(contact: Contact)
|
||||
case userProfileNoChange
|
||||
case userProfileUpdated(fromProfile: Profile, toProfile: Profile)
|
||||
// case newSentInvitation
|
||||
case userContactLink(connReqContact: String)
|
||||
case userContactLinkCreated(connReqContact: String)
|
||||
case userContactLinkDeleted
|
||||
case contactConnected(contact: Contact)
|
||||
case receivedContactRequest(contactRequest: UserContactRequest)
|
||||
case acceptingContactRequest(contact: Contact)
|
||||
case contactRequestRejected
|
||||
case newChatItem(chatItem: AChatItem)
|
||||
case chatCmdError(chatError: ChatError)
|
||||
|
||||
var responseType: String {
|
||||
get {
|
||||
|
@ -78,8 +99,15 @@ enum ChatResponse: Decodable, Error {
|
|||
case .contactDeleted: return "contactDeleted"
|
||||
case .userProfileNoChange: return "userProfileNoChange"
|
||||
case .userProfileUpdated: return "userProfileNoChange"
|
||||
case .userContactLink: return "userContactLink"
|
||||
case .userContactLinkCreated: return "userContactLinkCreated"
|
||||
case .userContactLinkDeleted: return "userContactLinkDeleted"
|
||||
case .contactConnected: return "contactConnected"
|
||||
case .receivedContactRequest: return "receivedContactRequest"
|
||||
case .acceptingContactRequest: return "acceptingContactRequest"
|
||||
case .contactRequestRejected: return "contactRequestRejected"
|
||||
case .newChatItem: return "newChatItem"
|
||||
case .chatCmdError: return "chatCmdError"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -91,16 +119,25 @@ enum ChatResponse: Decodable, Error {
|
|||
case let .apiChats(chats): return String(describing: chats)
|
||||
case let .apiChat(chat): return String(describing: chat)
|
||||
case let .invitation(connReqInvitation): return connReqInvitation
|
||||
case .sentConfirmation: return "sentConfirmation: no details"
|
||||
case .sentInvitation: return "sentInvitation: no details"
|
||||
case .sentConfirmation: return noDetails
|
||||
case .sentInvitation: return noDetails
|
||||
case let .contactDeleted(contact): return String(describing: contact)
|
||||
case .userProfileNoChange: return "userProfileNoChange: no details"
|
||||
case .userProfileNoChange: return noDetails
|
||||
case let .userProfileUpdated(_, toProfile): return String(describing: toProfile)
|
||||
case let .userContactLink(connReq): return connReq
|
||||
case let .userContactLinkCreated(connReq): return connReq
|
||||
case .userContactLinkDeleted: return noDetails
|
||||
case let .contactConnected(contact): return String(describing: contact)
|
||||
case let .receivedContactRequest(contactRequest): return String(describing: contactRequest)
|
||||
case let .acceptingContactRequest(contact): return String(describing: contact)
|
||||
case .contactRequestRejected: return noDetails
|
||||
case let .newChatItem(chatItem): return String(describing: chatItem)
|
||||
case let .chatCmdError(chatError): return String(describing: chatError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var noDetails: String { get { "\(responseType): no details" } }
|
||||
}
|
||||
|
||||
enum TerminalItem: Identifiable {
|
||||
|
@ -219,20 +256,67 @@ func apiUpdateProfile(profile: Profile) throws -> Profile? {
|
|||
}
|
||||
}
|
||||
|
||||
func apiCreateUserAddress() throws -> String {
|
||||
let r = try chatSendCmd(.createMyAddress)
|
||||
if case let .userContactLinkCreated(connReq) = r { return connReq }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiDeleteUserAddress() throws {
|
||||
let r = try chatSendCmd(.deleteMyAddress)
|
||||
if case .userContactLinkDeleted = r { return }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiGetUserAddress() throws -> String? {
|
||||
let r = try chatSendCmd(.showMyAddress)
|
||||
switch r {
|
||||
case let .userContactLink(connReq):
|
||||
return connReq
|
||||
case .chatCmdError(chatError: .errorStore(storeError: .userContactLinkNotFound)):
|
||||
return nil
|
||||
default: throw r
|
||||
}
|
||||
}
|
||||
|
||||
func apiAcceptContactRequest(contactReqId: Int64) throws -> Contact {
|
||||
let r = try chatSendCmd(.apiAcceptContact(contactReqId: contactReqId))
|
||||
if case let .acceptingContactRequest(contact) = r { return contact }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiRejectContactRequest(contactReqId: Int64) throws {
|
||||
let r = try chatSendCmd(.apiRejectContact(contactReqId: contactReqId))
|
||||
if case .contactRequestRejected = r { return }
|
||||
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(
|
||||
Chat(chatInfo: .direct(contact: contact), chatItems: []),
|
||||
at: 0
|
||||
)
|
||||
if let chat = chatModel.chats[contact.id] {
|
||||
chat.chatInfo = ChatInfo.direct(contact: contact)
|
||||
} else {
|
||||
let chat = Chat(chatInfo: ChatInfo.direct(contact: contact), chatItems: [])
|
||||
chatModel.chats[contact.id] = chat
|
||||
chatModel.chatPreviews.insert(chat, at: 0)
|
||||
}
|
||||
case let .receivedContactRequest(contactRequest):
|
||||
let chat = Chat(chatInfo: ChatInfo.contactRequest(contactRequest: contactRequest), chatItems: [])
|
||||
chatModel.chats[contactRequest.id] = chat
|
||||
chatModel.chatPreviews.insert(chat, 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)
|
||||
if let cp = chatModel.chatPreviews.first(where: { $0.id == ci.id } ) {
|
||||
cp.chatItems = [aChatItem.chatItem]
|
||||
} else {
|
||||
chatModel.chatPreviews.insert(Chat(chatInfo: ci, chatItems: [aChatItem.chatItem]), at: 0)
|
||||
}
|
||||
default:
|
||||
print("unsupported response: ", res.responseType)
|
||||
}
|
||||
|
@ -316,3 +400,13 @@ private func encodeCJSON<T: Encodable>(_ value: T) -> [CChar] {
|
|||
let str = String(decoding: data, as: UTF8.self)
|
||||
return str.cString(using: .utf8)!
|
||||
}
|
||||
|
||||
enum ChatError: Decodable {
|
||||
case errorStore(storeError: StoreError)
|
||||
// TODO other error cases
|
||||
}
|
||||
|
||||
enum StoreError: Decodable {
|
||||
case userContactLinkNotFound
|
||||
// TODO other error cases
|
||||
}
|
||||
|
|
|
@ -4,79 +4,11 @@ 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 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 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)!)
|
||||
|
||||
"\(ChatType.direct)"
|
||||
|
||||
var a = [1, 2, 3]
|
||||
|
||||
a.removeAll(where: { $0 == 1} )
|
||||
|
||||
print(a)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
version = "3.0">
|
||||
<TimelineItems>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "file:///Users/evgeny/opensource/simplex-chat/simplex-chat/apps/ios/Shared/MyPlayground.playground#CharacterRangeLen=88&CharacterRangeLoc=3634&EndingColumnNumber=0&EndingLineNumber=80&StartingColumnNumber=3&StartingLineNumber=79&Timestamp=665235849.610096"
|
||||
documentLocation = "file:///Users/evgeny/opensource/simplex-chat/simplex-chat/apps/ios/Shared/MyPlayground.playground#CharacterRangeLen=88&CharacterRangeLoc=91&EndingColumnNumber=0&EndingLineNumber=7&StartingColumnNumber=3&StartingLineNumber=6&Timestamp=665423482.97412"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
|
|
207
apps/ios/Shared/Views/ChatList/ChatListNavLink.swift
Normal file
207
apps/ios/Shared/Views/ChatList/ChatListNavLink.swift
Normal file
|
@ -0,0 +1,207 @@
|
|||
//
|
||||
// ChatListNavLink.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Evgeny Poberezkin on 01/02/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ChatListNavLink: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
|
||||
@Binding var chatId: String?
|
||||
@State var chatPreview: Chat
|
||||
var width: CGFloat
|
||||
|
||||
@State private var showDeleteContactAlert = false
|
||||
@State private var showDeleteGroupAlert = false
|
||||
@State private var showContactRequestAlert = false
|
||||
@State private var showContactRequestDialog = false
|
||||
@State private var alertContact: Contact?
|
||||
@State private var alertGroupInfo: GroupInfo?
|
||||
@State private var alertContactRequest: UserContactRequest?
|
||||
|
||||
var body: some View {
|
||||
switch chatPreview.chatInfo {
|
||||
case let .direct(contact):
|
||||
contactNavLink(contact)
|
||||
case let .group(groupInfo):
|
||||
groupNavLink(groupInfo)
|
||||
case let .contactRequest(cReq):
|
||||
contactRequestNavLink(cReq)
|
||||
}
|
||||
}
|
||||
|
||||
private func chatView() -> some View {
|
||||
ChatView(
|
||||
chatId: $chatId,
|
||||
chatInfo: chatPreview.chatInfo,
|
||||
width: width
|
||||
)
|
||||
.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func contactNavLink(_ contact: Contact) -> some View {
|
||||
NavigationLink(
|
||||
tag: chatPreview.chatInfo.id,
|
||||
selection: $chatId,
|
||||
destination: { chatView() },
|
||||
label: { ChatPreviewView(chatPreview: chatPreview) }
|
||||
)
|
||||
.disabled(!contact.connected)
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button(role: .destructive) {
|
||||
alertContact = contact
|
||||
showDeleteContactAlert = true
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $showDeleteContactAlert) {
|
||||
deleteContactAlert(alertContact!)
|
||||
}
|
||||
.frame(height: 80)
|
||||
}
|
||||
|
||||
private func groupNavLink(_ groupInfo: GroupInfo) -> some View {
|
||||
NavigationLink(
|
||||
tag: chatPreview.chatInfo.id,
|
||||
selection: $chatId,
|
||||
destination: { chatView() },
|
||||
label: { ChatPreviewView(chatPreview: chatPreview) }
|
||||
)
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button(role: .destructive) {
|
||||
alertGroupInfo = groupInfo
|
||||
showDeleteGroupAlert = true
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $showDeleteGroupAlert) {
|
||||
deleteGroupAlert(alertGroupInfo!)
|
||||
}
|
||||
.frame(height: 80)
|
||||
}
|
||||
|
||||
private func contactRequestNavLink(_ contactRequest: UserContactRequest) -> some View {
|
||||
ChatPreviewView(chatPreview: chatPreview)
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button { acceptContactRequest(contactRequest) }
|
||||
label: { Label("Accept", systemImage: "checkmark") }
|
||||
.tint(.blue)
|
||||
Button(role: .destructive) {
|
||||
alertContactRequest = contactRequest
|
||||
showContactRequestAlert = true
|
||||
} label: {
|
||||
Label("Reject", systemImage: "multiply")
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $showContactRequestAlert) {
|
||||
contactRequestAlert(alertContactRequest!)
|
||||
}
|
||||
.background(Color(uiColor: .systemBackground))
|
||||
.frame(width: width, height: 80)
|
||||
.onTapGesture { showContactRequestDialog = true }
|
||||
.confirmationDialog("Connection request", isPresented: $showContactRequestDialog, titleVisibility: .visible) {
|
||||
Button("Accept contact") { acceptContactRequest(contactRequest) }
|
||||
Button("Reject contact (sender NOT notified)") { rejectContactRequest(contactRequest) }
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteContactAlert(_ contact: Contact) -> Alert {
|
||||
Alert(
|
||||
title: Text("Delete contact?"),
|
||||
message: Text("Contact and all messages will be deleted"),
|
||||
primaryButton: .destructive(Text("Delete")) {
|
||||
do {
|
||||
try apiDeleteChat(type: .direct, id: contact.contactId)
|
||||
chatModel.chats.removeValue(forKey: contact.id)
|
||||
chatModel.chatPreviews.removeAll(where: { $0.id == contact.id })
|
||||
} catch let error {
|
||||
print("Error: \(error)")
|
||||
}
|
||||
alertContact = nil
|
||||
}, secondaryButton: .cancel() {
|
||||
alertContact = nil
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func deleteGroupAlert(_ groupInfo: GroupInfo) -> Alert {
|
||||
Alert(
|
||||
title: Text("Delete group"),
|
||||
message: Text("Group deletion is not supported")
|
||||
)
|
||||
}
|
||||
|
||||
private func contactRequestAlert(_ contactRequest: UserContactRequest) -> Alert {
|
||||
Alert(
|
||||
title: Text("Reject contact request"),
|
||||
message: Text("The sender will NOT be notified"),
|
||||
primaryButton: .destructive(Text("Reject")) {
|
||||
rejectContactRequest(contactRequest)
|
||||
alertContactRequest = nil
|
||||
}, secondaryButton: .cancel {
|
||||
alertContactRequest = nil
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func acceptContactRequest(_ contactRequest: UserContactRequest) {
|
||||
do {
|
||||
let contact = try apiAcceptContactRequest(contactReqId: contactRequest.contactRequestId)
|
||||
chatModel.chats.removeValue(forKey: contactRequest.id)
|
||||
let chat = Chat(chatInfo: ChatInfo.direct(contact: contact), chatItems: [])
|
||||
chatModel.chats[contact.id] = chat
|
||||
if let i = chatModel.chatPreviews.firstIndex(where: { $0.id == contactRequest.id }) {
|
||||
chatModel.chatPreviews[i] = chat
|
||||
} else {
|
||||
chatModel.chatPreviews.insert(chat, at: 0)
|
||||
}
|
||||
} catch let error {
|
||||
print("Error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func rejectContactRequest(_ contactRequest: UserContactRequest) {
|
||||
do {
|
||||
try apiRejectContactRequest(contactReqId: contactRequest.contactRequestId)
|
||||
chatModel.chats.removeValue(forKey: contactRequest.id)
|
||||
chatModel.chatPreviews.removeAll(where: { $0.id == contactRequest.id })
|
||||
} catch let error {
|
||||
print("Error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatListNavLink_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
@State var chatId: String? = "@1"
|
||||
return Group {
|
||||
ChatListNavLink(chatId: $chatId, chatPreview: Chat(
|
||||
chatInfo: sampleDirectChatInfo,
|
||||
chatItems: [chatItemSample(1, .directSnd, Date.now, "hello")]
|
||||
), width: 300)
|
||||
ChatListNavLink(chatId: $chatId, chatPreview: Chat(
|
||||
chatInfo: sampleDirectChatInfo,
|
||||
chatItems: [chatItemSample(1, .directSnd, Date.now, "hello")]
|
||||
), width: 300)
|
||||
ChatListNavLink(chatId: $chatId, chatPreview: Chat(
|
||||
chatInfo: sampleContactRequestChatInfo,
|
||||
chatItems: []
|
||||
), width: 300)
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 80))
|
||||
}
|
||||
}
|
75
apps/ios/Shared/Views/ChatList/ChatListView.swift
Normal file
75
apps/ios/Shared/Views/ChatList/ChatListView.swift
Normal file
|
@ -0,0 +1,75 @@
|
|||
//
|
||||
// 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
|
||||
@State private var chatId: String?
|
||||
|
||||
var user: User
|
||||
|
||||
var body: some View {
|
||||
return VStack {
|
||||
// if chatModel.chats.isEmpty {
|
||||
// VStack {
|
||||
// Text("Hello chat")
|
||||
// Text("Active user: \(user.localDisplayName) (\(user.profile.fullName))")
|
||||
// }
|
||||
// }
|
||||
|
||||
NavigationView {
|
||||
GeometryReader { geometry in
|
||||
List {
|
||||
NavigationLink {
|
||||
TerminalView()
|
||||
} label: {
|
||||
Text("Terminal")
|
||||
}
|
||||
|
||||
ForEach(chatModel.chatPreviews) { chatPreview in
|
||||
ChatListNavLink(
|
||||
chatId: $chatId,
|
||||
chatPreview: chatPreview,
|
||||
width: geometry.size.width
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(0)
|
||||
.offset(x: -8)
|
||||
.listStyle(.plain)
|
||||
.toolbar { ChatListToolbar(width: geometry.size.width) }
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.")]
|
||||
),
|
||||
Chat(
|
||||
chatInfo: sampleContactRequestChatInfo,
|
||||
chatItems: []
|
||||
)
|
||||
|
||||
]
|
||||
return ChatListView(user: sampleUser)
|
||||
.environmentObject(chatModel)
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ struct TerminalView: View {
|
|||
NavigationLink {
|
||||
ScrollView {
|
||||
Text(item.details)
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
} label: {
|
||||
Text(item.label)
|
|
@ -1,119 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
@State private var chatId: String?
|
||||
@State private var chatsToBeDeleted: IndexSet?
|
||||
@State private var showDeleteAlert = false
|
||||
|
||||
var user: User
|
||||
|
||||
var body: some View {
|
||||
return VStack {
|
||||
// if chatModel.chats.isEmpty {
|
||||
// VStack {
|
||||
// Text("Hello chat")
|
||||
// Text("Active user: \(user.localDisplayName) (\(user.profile.fullName))")
|
||||
// }
|
||||
// }
|
||||
|
||||
ChatHeaderView(chatId: $chatId)
|
||||
|
||||
NavigationView {
|
||||
List {
|
||||
NavigationLink {
|
||||
TerminalView()
|
||||
} label: {
|
||||
Text("Terminal")
|
||||
}
|
||||
|
||||
ForEach(chatModel.chatPreviews) { chatPreview in
|
||||
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)
|
||||
.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = [
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -38,6 +38,13 @@ struct ChatPreviewView: View {
|
|||
.padding(.bottom, 4)
|
||||
.padding(.top, 1)
|
||||
}
|
||||
// else if case let .direct(contact) = chatPreview.chatInfo, !contact.connected {
|
||||
// Text("Connecting...")
|
||||
// .frame(minWidth: 0, maxWidth: .infinity, minHeight: 44, maxHeight: 44, alignment: .topLeading)
|
||||
// .padding([.leading, .trailing], 8)
|
||||
// .padding(.bottom, 4)
|
||||
// .padding(.top, 1)
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,22 +10,22 @@ import SwiftUI
|
|||
|
||||
struct ChatView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@State var inProgress: Bool = false
|
||||
|
||||
@Binding var chatId: String?
|
||||
var chatInfo: ChatInfo
|
||||
var width: CGFloat
|
||||
@State private var inProgress: Bool = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
if let chat: Chat = chatModel.chats[chatInfo.id] {
|
||||
VStack {
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 5) {
|
||||
ForEach(chat.chatItems) {
|
||||
ChatItemView(chatItem: $0)
|
||||
}
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 5) {
|
||||
ForEach(chat.chatItems) {
|
||||
ChatItemView(chatItem: $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else {
|
||||
Text("unexpected: chat not found...")
|
||||
}
|
||||
|
||||
|
@ -33,8 +33,20 @@ struct ChatView: View {
|
|||
|
||||
SendMessageView(sendMessage: sendMessage, inProgress: inProgress)
|
||||
}
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.navigationBarHidden(true)
|
||||
.toolbar {
|
||||
HStack {
|
||||
Button { chatId = nil } label: { Image(systemName: "chevron.backward") }
|
||||
Spacer()
|
||||
Text(chatInfo.localDisplayName)
|
||||
.font(.title3)
|
||||
Spacer()
|
||||
EmptyView()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.frame(minWidth: width, maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
.navigationBarBackButtonHidden(true)
|
||||
|
||||
}
|
||||
|
||||
func sendMessage(_ msg: String) {
|
||||
|
@ -51,6 +63,7 @@ struct ChatView: View {
|
|||
|
||||
struct ChatView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
@State var chatId: String? = "@1"
|
||||
let chatModel = ChatModel()
|
||||
chatModel.chats = [
|
||||
"@1": Chat(
|
||||
|
@ -66,7 +79,7 @@ struct ChatView_Previews: PreviewProvider {
|
|||
]
|
||||
)
|
||||
]
|
||||
return ChatView(chatInfo: sampleDirectChatInfo)
|
||||
return ChatView(chatId: $chatId, chatInfo: sampleDirectChatInfo, width: 300)
|
||||
.environmentObject(chatModel)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
//
|
||||
// ChatHeaderView.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Evgeny Poberezkin on 29/01/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ChatHeaderView: View {
|
||||
@Binding var chatId: String?
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
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()
|
||||
}
|
||||
}
|
||||
.padding([.horizontal, .top])
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatHeaderView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
@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)
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ struct ChatItemView: View {
|
|||
.padding(.horizontal, 12)
|
||||
.frame(minWidth: 200, maxWidth: 300, alignment: .leading)
|
||||
.foregroundColor(sent ? .white : .primary)
|
||||
.textSelection(.enabled)
|
||||
Text(getDateFormatter().string(from: chatItem.meta.itemTs))
|
||||
.font(.subheadline)
|
||||
.foregroundColor(sent ? .white : .secondary)
|
||||
|
|
21
apps/ios/Shared/Views/Helpers/ChatListNavLink.swift
Normal file
21
apps/ios/Shared/Views/Helpers/ChatListNavLink.swift
Normal file
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// ChatListNavLink.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Evgeny Poberezkin on 01/02/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ChatListNavLink: View {
|
||||
var body: some View {
|
||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatListNavLink_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ChatListNavLink()
|
||||
}
|
||||
}
|
42
apps/ios/Shared/Views/Helpers/ChatListToolbar.swift
Normal file
42
apps/ios/Shared/Views/Helpers/ChatListToolbar.swift
Normal file
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// ChatListToolbar.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Evgeny Poberezkin on 29/01/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ChatListToolbar: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
var width: CGFloat
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
SettingsButton()
|
||||
Spacer()
|
||||
Text("Your chats")
|
||||
.font(.title3)
|
||||
Spacer()
|
||||
NewChatButton()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.frame(minWidth: width, maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatListToolbar_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let chatModel = ChatModel()
|
||||
chatModel.chats = [
|
||||
"@1": Chat(
|
||||
chatInfo: sampleDirectChatInfo,
|
||||
chatItems: [chatItemSample(1, .directSnd, Date.now, "hello")]
|
||||
)
|
||||
]
|
||||
return ChatListToolbar(width: 300)
|
||||
.previewLayout(.fixed(width: 300, height: 70))
|
||||
.environmentObject(chatModel)
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
//
|
||||
// MessageView.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Evgeny Poberezkin on 18/01/2022.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MessageView: View {
|
||||
var message: String
|
||||
var sent: Bool
|
||||
let receivedColor: Color = Color(UIColor(red: 240/255, green: 240/255, blue: 240/255, alpha: 1.0))
|
||||
|
||||
var body: some View {
|
||||
Text(message)
|
||||
.padding(10)
|
||||
.foregroundColor(sent ? Color.white : Color.black)
|
||||
.background(sent ? Color.blue : receivedColor)
|
||||
.cornerRadius(10)
|
||||
.frame(minWidth: 100,
|
||||
maxWidth: .infinity,
|
||||
minHeight: 0,
|
||||
maxHeight: .infinity,
|
||||
alignment: .leading)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct MessageView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
MessageView(message: "> Send message: \"Hello world!\"\nSuccessful", sent: false)
|
||||
}
|
||||
}
|
|
@ -30,8 +30,8 @@ struct AddContactView: View {
|
|||
Button { shareInvitation = true } label: {
|
||||
Label("Share", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
.padding()
|
||||
.shareSheet(isPresented: $shareInvitation, items: [connReqInvitation])
|
||||
.padding()
|
||||
.shareSheet(isPresented: $shareInvitation, items: [connReqInvitation])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ struct QRCode: View {
|
|||
.resizable()
|
||||
.interpolation(.none)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import SwiftUI
|
||||
|
||||
struct SettingsButton: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@State private var showSettings = false
|
||||
|
||||
var body: some View {
|
||||
|
@ -17,6 +18,13 @@ struct SettingsButton: View {
|
|||
}
|
||||
.sheet(isPresented: $showSettings, content: {
|
||||
SettingsView()
|
||||
.onAppear {
|
||||
do {
|
||||
chatModel.userAddress = try apiGetUserAddress()
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ struct SettingsView: View {
|
|||
@EnvironmentObject var chatModel: ChatModel
|
||||
|
||||
var body: some View {
|
||||
SettingsProfile()
|
||||
UserProfile()
|
||||
UserAddress()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,13 +9,67 @@
|
|||
import SwiftUI
|
||||
|
||||
struct UserAddress: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@State private var shareAddressLink = false
|
||||
@State private var deleteAddressAlert = false
|
||||
|
||||
var body: some View {
|
||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
||||
VStack (alignment: .leading) {
|
||||
Text("Your chat address")
|
||||
.font(.title)
|
||||
.padding(.bottom)
|
||||
Text("Your can share your address as a link or as a QR code - anybody will be able to connect to you, and if you later delete it - you won't lose your contacts.")
|
||||
.padding(.bottom)
|
||||
if let userAdress = chatModel.userAddress {
|
||||
QRCode(uri: userAdress)
|
||||
HStack {
|
||||
Button { shareAddressLink = true } label: {
|
||||
Label("Share link", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
.padding()
|
||||
.shareSheet(isPresented: $shareAddressLink, items: [userAdress])
|
||||
|
||||
Button { deleteAddressAlert = true } label: {
|
||||
Label("Delete address", systemImage: "trash")
|
||||
}
|
||||
.padding()
|
||||
.alert(isPresented: $deleteAddressAlert) {
|
||||
Alert(
|
||||
title: Text("Delete address?"),
|
||||
message: Text("All your contacts will remain connected"),
|
||||
primaryButton: .destructive(Text("Delete")) {
|
||||
do {
|
||||
try apiDeleteUserAddress()
|
||||
chatModel.userAddress = nil
|
||||
} catch let error {
|
||||
print("Error: \(error)")
|
||||
}
|
||||
}, secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
.shareSheet(isPresented: $shareAddressLink, items: [userAdress])
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
} else {
|
||||
Button {
|
||||
do {
|
||||
chatModel.userAddress = try apiCreateUserAddress()
|
||||
} catch let error {
|
||||
print("Error: \(error)")
|
||||
}
|
||||
} label: { Label("Create address", systemImage: "qrcode") }
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
struct UserAddress_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
UserAddress()
|
||||
let chatModel = ChatModel()
|
||||
chatModel.userAddress = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D"
|
||||
return UserAddress()
|
||||
.environmentObject(chatModel)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// SettingsProfile.swift
|
||||
// UserProfile.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Evgeny Poberezkin on 31/01/2022.
|
||||
|
@ -8,7 +8,7 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsProfile: View {
|
||||
struct UserProfile: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@State private var profile = Profile(displayName: "", fullName: "")
|
||||
@State private var editProfile: Bool = false
|
||||
|
@ -76,11 +76,11 @@ struct SettingsProfile: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct SettingsProfile_Previews: PreviewProvider {
|
||||
struct UserProfile_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let chatModel = ChatModel()
|
||||
chatModel.currentUser = sampleUser
|
||||
return SettingsProfile()
|
||||
return UserProfile()
|
||||
.environmentObject(chatModel)
|
||||
}
|
||||
}
|
|
@ -56,22 +56,22 @@
|
|||
5CA059F0279559F40002BEB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5CA059C5279559F40002BEB4 /* Assets.xcassets */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
5CB924E127A867BA00ACCCDD /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E027A867BA00ACCCDD /* UserProfile.swift */; };
|
||||
5CB924E227A867BA00ACCCDD /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E027A867BA00ACCCDD /* UserProfile.swift */; };
|
||||
5CB924E427A8683A00ACCCDD /* UserAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E327A8683A00ACCCDD /* UserAddress.swift */; };
|
||||
5CB924E527A8683A00ACCCDD /* UserAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E327A8683A00ACCCDD /* UserAddress.swift */; };
|
||||
5CB9250D27A9432000ACCCDD /* ChatListNavLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB9250C27A9432000ACCCDD /* ChatListNavLink.swift */; };
|
||||
5CB9250E27A9432000ACCCDD /* ChatListNavLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB9250C27A9432000ACCCDD /* ChatListNavLink.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 */; };
|
||||
5CC1C99627A6CF7F000D9FF6 /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */; };
|
||||
5CCD403127A5F1C600368C90 /* ChatHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403027A5F1C600368C90 /* ChatHeaderView.swift */; };
|
||||
5CCD403227A5F1C600368C90 /* ChatHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403027A5F1C600368C90 /* ChatHeaderView.swift */; };
|
||||
5CCD403127A5F1C600368C90 /* ChatListToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403027A5F1C600368C90 /* ChatListToolbar.swift */; };
|
||||
5CCD403227A5F1C600368C90 /* ChatListToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403027A5F1C600368C90 /* ChatListToolbar.swift */; };
|
||||
5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; };
|
||||
5CCD403527A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; };
|
||||
5CCD403727A5F9A200368C90 /* ConnectContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ConnectContactView.swift */; };
|
||||
|
@ -131,14 +131,14 @@
|
|||
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 /* 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>"; };
|
||||
5CB924D327A853F100ACCCDD /* SettingsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsButton.swift; sourceTree = "<group>"; };
|
||||
5CB924D627A8563F00ACCCDD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||
5CB924E027A867BA00ACCCDD /* SettingsProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsProfile.swift; sourceTree = "<group>"; };
|
||||
5CB924E027A867BA00ACCCDD /* UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = "<group>"; };
|
||||
5CB924E327A8683A00ACCCDD /* UserAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddress.swift; sourceTree = "<group>"; };
|
||||
5CB9250C27A9432000ACCCDD /* ChatListNavLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListNavLink.swift; sourceTree = "<group>"; };
|
||||
5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCode.swift; sourceTree = "<group>"; };
|
||||
5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = "<group>"; };
|
||||
5CCD403027A5F1C600368C90 /* ChatHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHeaderView.swift; sourceTree = "<group>"; };
|
||||
5CCD403027A5F1C600368C90 /* ChatListToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListToolbar.swift; sourceTree = "<group>"; };
|
||||
5CCD403327A5F6DF00368C90 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = "<group>"; };
|
||||
5CCD403627A5F9A200368C90 /* ConnectContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectContactView.swift; sourceTree = "<group>"; };
|
||||
5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateGroupView.swift; sourceTree = "<group>"; };
|
||||
|
@ -196,10 +196,9 @@
|
|||
children = (
|
||||
5C5F4AC227A5E9AF00B51EF1 /* Helpers */,
|
||||
5CA05A4B27974EB60002BEB4 /* WelcomeView.swift */,
|
||||
5C2E260A27A30CFA00F70299 /* ChatListView.swift */,
|
||||
5CB9250B27A942F300ACCCDD /* ChatList */,
|
||||
5C063D2627A4564100AEC577 /* ChatPreviewView.swift */,
|
||||
5C2E260E27A30FDC00F70299 /* ChatView.swift */,
|
||||
5C2E261127A30FEA00F70299 /* TerminalView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
|
@ -208,9 +207,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */,
|
||||
5CA05A4E279752D00002BEB4 /* MessageView.swift */,
|
||||
5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */,
|
||||
5CCD403027A5F1C600368C90 /* ChatHeaderView.swift */,
|
||||
5CCD403027A5F1C600368C90 /* ChatListToolbar.swift */,
|
||||
5CB924DF27A8678B00ACCCDD /* UserSettings */,
|
||||
5CB924DD27A8622200ACCCDD /* NewChat */,
|
||||
);
|
||||
|
@ -332,11 +330,21 @@
|
|||
5CB924D327A853F100ACCCDD /* SettingsButton.swift */,
|
||||
5CB924D627A8563F00ACCCDD /* SettingsView.swift */,
|
||||
5CB924E327A8683A00ACCCDD /* UserAddress.swift */,
|
||||
5CB924E027A867BA00ACCCDD /* SettingsProfile.swift */,
|
||||
5CB924E027A867BA00ACCCDD /* UserProfile.swift */,
|
||||
);
|
||||
path = UserSettings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5CB9250B27A942F300ACCCDD /* ChatList */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C2E260A27A30CFA00F70299 /* ChatListView.swift */,
|
||||
5C2E261127A30FEA00F70299 /* TerminalView.swift */,
|
||||
5CB9250C27A9432000ACCCDD /* ChatListNavLink.swift */,
|
||||
);
|
||||
path = ChatList;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
@ -506,7 +514,7 @@
|
|||
files = (
|
||||
5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */,
|
||||
5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */,
|
||||
5CB924E127A867BA00ACCCDD /* SettingsProfile.swift in Sources */,
|
||||
5CB924E127A867BA00ACCCDD /* UserProfile.swift in Sources */,
|
||||
5C764E80279C7276000C6508 /* dummy.m in Sources */,
|
||||
5CB924E427A8683A00ACCCDD /* UserAddress.swift in Sources */,
|
||||
5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */,
|
||||
|
@ -514,8 +522,8 @@
|
|||
5C9FD96B27A56D4D0075386C /* JSON.swift in Sources */,
|
||||
5C9FD96E27A5D6ED0075386C /* SendMessageView.swift in Sources */,
|
||||
5CC1C99227A6C7F5000D9FF6 /* QRCode.swift in Sources */,
|
||||
5CCD403127A5F1C600368C90 /* ChatHeaderView.swift in Sources */,
|
||||
5CA05A4F279752D00002BEB4 /* MessageView.swift in Sources */,
|
||||
5CCD403127A5F1C600368C90 /* ChatListToolbar.swift in Sources */,
|
||||
5CB9250D27A9432000ACCCDD /* ChatListNavLink.swift in Sources */,
|
||||
5CA059ED279559F40002BEB4 /* ContentView.swift in Sources */,
|
||||
5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */,
|
||||
5CA05A4C27974EB60002BEB4 /* WelcomeView.swift in Sources */,
|
||||
|
@ -538,7 +546,7 @@
|
|||
files = (
|
||||
5C6AD81427A834E300348BD7 /* NewChatButton.swift in Sources */,
|
||||
5CB924D827A8563F00ACCCDD /* SettingsView.swift in Sources */,
|
||||
5CB924E227A867BA00ACCCDD /* SettingsProfile.swift in Sources */,
|
||||
5CB924E227A867BA00ACCCDD /* UserProfile.swift in Sources */,
|
||||
5C764E81279C7276000C6508 /* dummy.m in Sources */,
|
||||
5CB924E527A8683A00ACCCDD /* UserAddress.swift in Sources */,
|
||||
5C063D2827A4564100AEC577 /* ChatPreviewView.swift in Sources */,
|
||||
|
@ -546,8 +554,8 @@
|
|||
5C9FD96C27A56D4D0075386C /* JSON.swift in Sources */,
|
||||
5C9FD96F27A5D6ED0075386C /* SendMessageView.swift in Sources */,
|
||||
5CC1C99327A6C7F5000D9FF6 /* QRCode.swift in Sources */,
|
||||
5CCD403227A5F1C600368C90 /* ChatHeaderView.swift in Sources */,
|
||||
5CA05A50279752D00002BEB4 /* MessageView.swift in Sources */,
|
||||
5CCD403227A5F1C600368C90 /* ChatListToolbar.swift in Sources */,
|
||||
5CB9250E27A9432000ACCCDD /* ChatListNavLink.swift in Sources */,
|
||||
5CA059EE279559F40002BEB4 /* ContentView.swift in Sources */,
|
||||
5CCD403527A5F6DF00368C90 /* AddContactView.swift in Sources */,
|
||||
5CA05A4D27974EB60002BEB4 /* WelcomeView.swift in Sources */,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue