2022-01-24 10:07:17 -06:00
|
|
|
//
|
|
|
|
// ChatModel.swift
|
|
|
|
// SimpleX
|
|
|
|
//
|
|
|
|
// Created by Evgeny Poberezkin on 22/01/2022.
|
|
|
|
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
import Combine
|
2022-01-31 15:28:07 -06:00
|
|
|
import SwiftUI
|
2022-01-24 10:07:17 -06:00
|
|
|
|
|
|
|
final class ChatModel: ObservableObject {
|
2022-05-09 03:52:09 -05:00
|
|
|
@Published var onboardingStage: OnboardingStage?
|
2022-01-24 10:07:17 -06:00
|
|
|
@Published var currentUser: User?
|
2022-02-02 06:51:39 -06:00
|
|
|
// list of chat "previews"
|
|
|
|
@Published var chats: [Chat] = []
|
|
|
|
// current chat
|
|
|
|
@Published var chatId: String?
|
2022-01-29 05:10:04 -06:00
|
|
|
@Published var chatItems: [ChatItem] = []
|
2022-02-12 09:59:43 -06:00
|
|
|
@Published var chatToTop: String?
|
2022-02-02 06:51:39 -06:00
|
|
|
// items in the terminal view
|
2022-01-29 17:37:02 -06:00
|
|
|
@Published var terminalItems: [TerminalItem] = []
|
2022-02-01 11:34:06 -06:00
|
|
|
@Published var userAddress: String?
|
2022-03-10 05:45:40 -06:00
|
|
|
@Published var userSMPServers: [String]?
|
2022-02-01 14:30:33 -06:00
|
|
|
@Published var appOpenUrl: URL?
|
2022-04-21 14:04:22 -05:00
|
|
|
@Published var deviceToken: String?
|
2022-04-23 00:32:16 -05:00
|
|
|
@Published var tokenStatus = NtfTknStatus.new
|
2022-05-07 00:40:46 -05:00
|
|
|
// current WebRTC call
|
|
|
|
@Published var callInvitations: Dictionary<String, CallInvitation> = [:]
|
|
|
|
@Published var activeCallInvitation: ContactRef?
|
|
|
|
@Published var activeCall: Call?
|
|
|
|
@Published var callCommand: WCallCommand?
|
2022-02-28 04:44:48 -06:00
|
|
|
|
|
|
|
var messageDelivery: Dictionary<Int64, () -> Void> = [:]
|
|
|
|
|
2022-02-09 16:53:06 -06:00
|
|
|
static let shared = ChatModel()
|
2022-02-02 06:51:39 -06:00
|
|
|
|
|
|
|
func hasChat(_ id: String) -> Bool {
|
|
|
|
chats.first(where: { $0.id == id }) != nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getChat(_ id: String) -> Chat? {
|
|
|
|
chats.first(where: { $0.id == id })
|
|
|
|
}
|
|
|
|
|
2022-02-05 14:10:47 -06:00
|
|
|
private func getChatIndex(_ id: String) -> Int? {
|
|
|
|
chats.firstIndex(where: { $0.id == id })
|
|
|
|
}
|
|
|
|
|
2022-02-02 06:51:39 -06:00
|
|
|
func addChat(_ chat: Chat) {
|
2022-02-02 10:46:05 -06:00
|
|
|
withAnimation {
|
|
|
|
chats.insert(chat, at: 0)
|
|
|
|
}
|
2022-02-02 06:51:39 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func updateChatInfo(_ cInfo: ChatInfo) {
|
2022-02-12 09:59:43 -06:00
|
|
|
if let i = getChatIndex(cInfo.id) {
|
|
|
|
chats[i].chatInfo = cInfo
|
2022-02-02 06:51:39 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-25 04:39:28 -05:00
|
|
|
func updateContactConnection(_ contactConnection: PendingContactConnection) {
|
|
|
|
updateChat(.contactConnection(contactConnection: contactConnection))
|
|
|
|
}
|
|
|
|
|
2022-02-05 14:10:47 -06:00
|
|
|
func updateContact(_ contact: Contact) {
|
2022-04-25 04:39:28 -05:00
|
|
|
updateChat(.direct(contact: contact))
|
|
|
|
}
|
|
|
|
|
|
|
|
private func updateChat(_ cInfo: ChatInfo) {
|
|
|
|
if hasChat(cInfo.id) {
|
2022-02-05 14:10:47 -06:00
|
|
|
updateChatInfo(cInfo)
|
|
|
|
} else {
|
|
|
|
addChat(Chat(chatInfo: cInfo, chatItems: []))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-26 01:51:06 -05:00
|
|
|
func updateNetworkStatus(_ id: ChatId, _ status: Chat.NetworkStatus) {
|
|
|
|
if let i = getChatIndex(id) {
|
|
|
|
chats[i].serverInfo.networkStatus = status
|
2022-02-05 14:10:47 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-02 06:51:39 -06:00
|
|
|
func replaceChat(_ id: String, _ chat: Chat) {
|
2022-02-12 09:59:43 -06:00
|
|
|
if let i = getChatIndex(id) {
|
|
|
|
chats[i] = chat
|
2022-02-02 06:51:39 -06:00
|
|
|
} else {
|
|
|
|
// invalid state, correcting
|
|
|
|
chats.insert(chat, at: 0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func addChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) {
|
2022-02-12 09:59:43 -06:00
|
|
|
// update previews
|
|
|
|
if let i = getChatIndex(cInfo.id) {
|
|
|
|
chats[i].chatItems = [cItem]
|
|
|
|
if case .rcvNew = cItem.meta.itemStatus {
|
|
|
|
chats[i].chatStats.unreadCount = chats[i].chatStats.unreadCount + 1
|
|
|
|
}
|
|
|
|
if i > 0 {
|
2022-02-05 08:24:23 -06:00
|
|
|
if chatId == nil {
|
2022-02-12 09:59:43 -06:00
|
|
|
withAnimation { popChat_(i) }
|
|
|
|
} else if chatId == cInfo.id {
|
|
|
|
chatToTop = cInfo.id
|
2022-02-05 08:24:23 -06:00
|
|
|
} else {
|
2022-02-12 09:59:43 -06:00
|
|
|
popChat_(i)
|
2022-02-05 08:24:23 -06:00
|
|
|
}
|
2022-02-02 06:51:39 -06:00
|
|
|
}
|
2022-02-12 09:59:43 -06:00
|
|
|
} else {
|
|
|
|
addChat(Chat(chatInfo: cInfo, chatItems: [cItem]))
|
2022-02-02 06:51:39 -06:00
|
|
|
}
|
2022-02-12 09:59:43 -06:00
|
|
|
// add to current chat
|
2022-02-02 06:51:39 -06:00
|
|
|
if chatId == cInfo.id {
|
2022-02-03 01:16:29 -06:00
|
|
|
withAnimation { chatItems.append(cItem) }
|
2022-02-12 09:59:43 -06:00
|
|
|
if case .rcvNew = cItem.meta.itemStatus {
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
|
|
|
if self.chatId == cInfo.id {
|
2022-05-03 02:20:19 -05:00
|
|
|
Task { await apiMarkChatItemRead(cInfo, cItem) }
|
2022-02-12 09:59:43 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func upsertChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) -> Bool {
|
|
|
|
// update previews
|
|
|
|
var res: Bool
|
|
|
|
if let chat = getChat(cInfo.id) {
|
|
|
|
if let pItem = chat.chatItems.last, pItem.id == cItem.id {
|
|
|
|
chat.chatItems = [cItem]
|
|
|
|
}
|
|
|
|
res = false
|
|
|
|
} else {
|
|
|
|
addChat(Chat(chatInfo: cInfo, chatItems: [cItem]))
|
|
|
|
res = true
|
|
|
|
}
|
|
|
|
// update current chat
|
|
|
|
if chatId == cInfo.id {
|
|
|
|
if let i = chatItems.firstIndex(where: { $0.id == cItem.id }) {
|
|
|
|
withAnimation(.default) {
|
|
|
|
self.chatItems[i] = cItem
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
} else {
|
|
|
|
withAnimation { chatItems.append(cItem) }
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
}
|
2022-03-30 11:37:47 -05:00
|
|
|
|
|
|
|
func removeChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) {
|
|
|
|
// update previews
|
|
|
|
if let chat = getChat(cInfo.id) {
|
|
|
|
if let pItem = chat.chatItems.last, pItem.id == cItem.id {
|
|
|
|
chat.chatItems = [cItem]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// remove from current chat
|
|
|
|
if chatId == cInfo.id {
|
|
|
|
if let i = chatItems.firstIndex(where: { $0.id == cItem.id }) {
|
|
|
|
_ = withAnimation {
|
|
|
|
self.chatItems.remove(at: i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-12 09:59:43 -06:00
|
|
|
|
|
|
|
func markChatItemsRead(_ cInfo: ChatInfo) {
|
|
|
|
// update preview
|
|
|
|
if let chat = getChat(cInfo.id) {
|
|
|
|
chat.chatStats = ChatStats()
|
|
|
|
}
|
|
|
|
// update current chat
|
|
|
|
if chatId == cInfo.id {
|
|
|
|
var i = 0
|
|
|
|
while i < chatItems.count {
|
|
|
|
if case .rcvNew = chatItems[i].meta.itemStatus {
|
|
|
|
chatItems[i].meta.itemStatus = .rcvRead
|
|
|
|
}
|
|
|
|
i = i + 1
|
|
|
|
}
|
2022-02-02 06:51:39 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-17 13:48:54 -05:00
|
|
|
func clearChat(_ cInfo: ChatInfo) {
|
|
|
|
// clear preview
|
|
|
|
if let chat = getChat(cInfo.id) {
|
|
|
|
chat.chatItems = []
|
|
|
|
chat.chatStats = ChatStats()
|
|
|
|
}
|
|
|
|
// clear current chat
|
|
|
|
if chatId == cInfo.id {
|
|
|
|
chatItems = []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-12 09:59:43 -06:00
|
|
|
func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) {
|
|
|
|
// update preview
|
|
|
|
if let i = getChatIndex(cInfo.id) {
|
|
|
|
chats[i].chatStats.unreadCount = chats[i].chatStats.unreadCount - 1
|
|
|
|
}
|
|
|
|
// update current chat
|
|
|
|
if chatId == cInfo.id, let j = chatItems.firstIndex(where: { $0.id == cItem.id }) {
|
|
|
|
chatItems[j].meta.itemStatus = .rcvRead
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-30 02:57:42 -05:00
|
|
|
func getPrevChatItem(_ ci: ChatItem) -> ChatItem? {
|
|
|
|
if let i = chatItems.firstIndex(where: { $0.id == ci.id }), i > 0 {
|
|
|
|
return chatItems[i - 1]
|
|
|
|
} else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-12 09:59:43 -06:00
|
|
|
func popChat(_ id: String) {
|
|
|
|
if let i = getChatIndex(id) {
|
|
|
|
popChat_(i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func popChat_(_ i: Int) {
|
|
|
|
let chat = chats.remove(at: i)
|
2022-02-05 08:24:23 -06:00
|
|
|
chats.insert(chat, at: 0)
|
|
|
|
}
|
|
|
|
|
2022-02-02 06:51:39 -06:00
|
|
|
func removeChat(_ id: String) {
|
2022-02-02 10:46:05 -06:00
|
|
|
withAnimation {
|
|
|
|
chats.removeAll(where: { $0.id == id })
|
|
|
|
}
|
2022-02-02 06:51:39 -06:00
|
|
|
}
|
2022-01-24 10:07:17 -06:00
|
|
|
}
|
|
|
|
|
2022-02-02 06:51:39 -06:00
|
|
|
final class Chat: ObservableObject, Identifiable {
|
|
|
|
@Published var chatInfo: ChatInfo
|
|
|
|
@Published var chatItems: [ChatItem]
|
2022-02-12 09:59:43 -06:00
|
|
|
@Published var chatStats: ChatStats
|
2022-02-05 14:10:47 -06:00
|
|
|
@Published var serverInfo = ServerInfo(networkStatus: .unknown)
|
|
|
|
|
|
|
|
struct ServerInfo: Decodable {
|
|
|
|
var networkStatus: NetworkStatus
|
|
|
|
}
|
|
|
|
|
|
|
|
enum NetworkStatus: Decodable, Equatable {
|
|
|
|
case unknown
|
|
|
|
case connected
|
|
|
|
case disconnected
|
|
|
|
case error(String)
|
|
|
|
|
2022-04-16 03:37:01 -05:00
|
|
|
var statusString: LocalizedStringKey {
|
2022-02-05 14:10:47 -06:00
|
|
|
get {
|
|
|
|
switch self {
|
2022-02-07 04:36:11 -06:00
|
|
|
case .connected: return "Server connected"
|
|
|
|
case let .error(err): return "Connecting server… (error: \(err))"
|
|
|
|
default: return "Connecting server…"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-16 03:37:01 -05:00
|
|
|
var statusExplanation: LocalizedStringKey {
|
2022-02-07 04:36:11 -06:00
|
|
|
get {
|
|
|
|
switch self {
|
2022-03-14 15:58:19 -05:00
|
|
|
case .connected: return "You are connected to the server used to receive messages from this contact."
|
|
|
|
case let .error(err): return "Trying to connect to the server used to receive messages from this contact (error: \(err))."
|
|
|
|
default: return "Trying to connect to the server used to receive messages from this contact."
|
2022-02-05 14:10:47 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var imageName: String {
|
|
|
|
get {
|
|
|
|
switch self {
|
|
|
|
case .unknown: return "circle.dotted"
|
|
|
|
case .connected: return "circle.fill"
|
|
|
|
case .disconnected: return "ellipsis.circle.fill"
|
|
|
|
case .error: return "exclamationmark.circle.fill"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-02 06:51:39 -06:00
|
|
|
|
|
|
|
init(_ cData: ChatData) {
|
|
|
|
self.chatInfo = cData.chatInfo
|
|
|
|
self.chatItems = cData.chatItems
|
2022-02-12 09:59:43 -06:00
|
|
|
self.chatStats = cData.chatStats
|
2022-02-02 06:51:39 -06:00
|
|
|
}
|
2022-01-29 17:37:02 -06:00
|
|
|
|
2022-05-10 02:04:18 -05:00
|
|
|
init(chatInfo: ChatInfo, chatItems: [ChatItem] = [], chatStats: ChatStats = ChatStats(), serverInfo: ServerInfo = ServerInfo(networkStatus: .unknown)) {
|
2022-01-29 17:37:02 -06:00
|
|
|
self.chatInfo = chatInfo
|
|
|
|
self.chatItems = chatItems
|
2022-02-12 09:59:43 -06:00
|
|
|
self.chatStats = chatStats
|
2022-05-10 02:04:18 -05:00
|
|
|
self.serverInfo = serverInfo
|
2022-01-29 17:37:02 -06:00
|
|
|
}
|
2022-01-31 15:28:07 -06:00
|
|
|
|
2022-02-09 16:53:06 -06:00
|
|
|
var id: ChatId { get { chatInfo.id } }
|
2022-01-29 05:10:04 -06:00
|
|
|
}
|