From bba2783aa40cdc81195c5d4e45c457f429a5c785 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Fri, 18 Feb 2022 14:33:55 +0000 Subject: [PATCH] update model when messages arrive (#333) * update model when messages arrive * update chat in the list when message is added * copy methods with optional parameters * use data classes to have pre-defined copy methods --- .../java/chat/simplex/app/model/ChatModel.kt | 151 +++++++++++++++++- .../java/chat/simplex/app/model/SimpleXAPI.kt | 72 ++++++++- .../chat/simplex/app/views/chat/ChatView.kt | 2 +- apps/ios/Shared/Model/SimpleXAPI.swift | 3 + 4 files changed, 219 insertions(+), 9 deletions(-) diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt index 306a4e8b69..0cd97da752 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt @@ -12,6 +12,148 @@ class ChatModel(val controller: ChatController) { var terminalItems = mutableStateListOf() + fun hasChat(id: String): Boolean = chats.firstOrNull() { it.id == id } != null + fun getChat(id: String): Chat? = chats.firstOrNull { it.id == id } + private fun getChatIndex(id: String): Int = chats.indexOfFirst { it.id == id } + fun addChat(chat: Chat) = chats.add(index = 0, chat) + +// func updateChatInfo(_ cInfo: ChatInfo) { +// if let i = getChatIndex(cInfo.id) { +// chats[i].chatInfo = cInfo +// } +// } +// +// func updateContact(_ contact: Contact) { +// let cInfo = ChatInfo.direct(contact: contact) +// if hasChat(contact.id) { +// updateChatInfo(cInfo) +// } else { +// addChat(Chat(chatInfo: cInfo, chatItems: [])) +// } +// } +// +// func updateNetworkStatus(_ contact: Contact, _ status: Chat.NetworkStatus) { +// if let ix = getChatIndex(contact.id) { +// chats[ix].serverInfo.networkStatus = status +// } +// } +// +// func replaceChat(_ id: String, _ chat: Chat) { +// if let i = getChatIndex(id) { +// chats[i] = chat +// } else { +// // invalid state, correcting +// chats.insert(chat, at: 0) +// } +// } + + fun addChatItem(cInfo: ChatInfo, cItem: ChatItem) { + // update previews + val i = getChatIndex(cInfo.id) + if (i >= 0) { + val chat = chats[i] + val updatedChat = chat.copy( + chatItems = arrayListOf(cItem), + chatStats = + if (cItem.meta.itemStatus is CIStatus.RcvNew) + chat.chatStats.copy(unreadCount = chat.chatStats.unreadCount + 1) + else + chat.chatStats + ) + chats.set(i, updatedChat) + if (i > 0) { + popChat_(i) + } + } else { + addChat(Chat(chatInfo = cInfo, chatItems = arrayListOf(cItem))) + } + // add to current chat + if (chatId.value == cInfo.id) { + chatItems.add(cItem) + if (cItem.meta.itemStatus is CIStatus.RcvNew) { + // TODO mark item read via api and model +// DispatchQueue.main.asyncAfter(deadline: .now() + 1) { +// if self.chatId == cInfo.id { +// SimpleX.markChatItemRead(cInfo, cItem) +// } +// } + } + } + } +// +// 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 +// } +// } +// +// 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 +// } +// } +// } +// +// 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 +// } +// } +// +// func popChat(_ id: String) { +// if let i = getChatIndex(id) { +// popChat_(i) +// } +// } +// + private fun popChat_(i: Int) { + val chat = chats.removeAt(i) + chats.add(index = 0, chat) + } +// +// func removeChat(_ id: String) { +// withAnimation { +// chats.removeAll(where: { $0.id == id }) +// } +// } + companion object { val sampleData: ChatModel get() { val m = ChatModel(ChatController.Mock()) @@ -70,17 +212,16 @@ interface SomeChat { } @Serializable -class Chat ( +data class Chat ( val chatInfo: ChatInfo, val chatItems: List, - val chatStats: ChatStats, + val chatStats: ChatStats = ChatStats(), val serverInfo: ServerInfo = ServerInfo(NetworkStatus.Unknown()) ) { + val id: String get() = chatInfo.id @Serializable - class ChatStats { - - } + data class ChatStats(val unreadCount: Int = 0, val minUnreadItemId: Long = 0) @Serializable class ServerInfo(val networkStatus: NetworkStatus) diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index d7ff0be221..5a5b74b4b4 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -43,9 +43,10 @@ open class ChatController(val ctrl: ChatCtrl) { // val chatlog = FifoQueue(500) while(true) { val json = chatRecvMsg(ctrl) - Log.d("SIMPLEX chatRecvMsg: ", json) - val r = APIResponse.decodeStr(json) - chatModel.terminalItems.add(TerminalItem.resp(r.resp)) + val r = APIResponse.decodeStr(json).resp + Log.d("SIMPLEX", "chatRecvMsg: ${r.responseType}") + if (r is CR.Response || r is CR.Invalid) Log.d("SIMPLEX", "chatRecvMsg json: $json") + processReceivedMsg(r) } } } @@ -106,6 +107,71 @@ open class ChatController(val ctrl: ChatCtrl) { return null } + fun processReceivedMsg(r: CR) { + chatModel.terminalItems.add(TerminalItem.resp(r)) + when { +// r is CR.ContactConnected -> return +// r is CR.UpdateNetworkStatus -> return +// r is CR.ReceivedContactRequest -> return +// r is CR.ContactUpdated -> return +// r is CR.ContactSubscribed -> return +// r is CR.ContactSubError -> return +// r is CR.UpdateContact -> return + r is CR.NewChatItem -> { + val cInfo = r.chatItem.chatInfo + val cItem = r.chatItem.chatItem + chatModel.addChatItem(cInfo, cItem) + } +// NtfManager.shared.notifyMessageReceived(cInfo, cItem) + +// switch res { +// case let .contactConnected(contact): +// chatModel.updateContact(contact) +// chatModel.updateNetworkStatus(contact, .connected) +// NtfManager.shared.notifyContactConnected(contact) +// case let .receivedContactRequest(contactRequest): + // chatModel.addChat(Chat( + // chatInfo: ChatInfo.contactRequest(contactRequest: contactRequest), + // chatItems: [] + // )) +// NtfManager.shared.notifyContactRequest(contactRequest) +// case let .contactUpdated(toContact): + // let cInfo = ChatInfo.direct(contact: toContact) + // if chatModel.hasChat(toContact.id) { + // chatModel.updateChatInfo(cInfo) + // } +// case let .contactSubscribed(contact): + // chatModel.updateContact(contact) + // chatModel.updateNetworkStatus(contact, .connected) + // case let .contactDisconnected(contact): + // chatModel.updateContact(contact) + // chatModel.updateNetworkStatus(contact, .disconnected) +// case let .contactSubError(contact, chatError): +// chatModel.updateContact(contact) +//// var err: String +//// switch chatError { +//// case .errorAgent(agentError: .BROKER(brokerErr: .NETWORK)): err = "network" +//// case .errorAgent(agentError: .SMP(smpErr: .AUTH)): err = "contact deleted" +//// default: err = String(describing: chatError) +//// } +//// chatModel.updateNetworkStatus(contact, .error(err)) +// case let .newChatItem(aChatItem): + // let cInfo = aChatItem.chatInfo + // let cItem = aChatItem.chatItem + // chatModel.addChatItem(cInfo, cItem) + // NtfManager.shared.notifyMessageReceived(cInfo, cItem) +// case let .chatItemUpdated(aChatItem): + // let cInfo = aChatItem.chatInfo + // let cItem = aChatItem.chatItem + // if chatModel.upsertChatItem(cInfo, cItem) { + // NtfManager.shared.notifyMessageReceived(cInfo, cItem) + // } +// default: +// logger.debug("unsupported event: \(res.responseType)") +// } + } + } + class Mock: ChatController(0) {} } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt index 312e0b84bc..cf4cea564b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt @@ -35,7 +35,7 @@ fun ChatView(chatModel: ChatModel, nav: NavController) { mc = MsgContent.MCText(msg) ) // hide "in progress" - // TODO add new item + if (newItem != null) chatModel.addChatItem(cInfo, newItem.chatItem) } } }) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 051f6df905..7f5f50876c 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -237,6 +237,9 @@ func chatSendCmd(_ cmd: ChatCommand) throws -> ChatResponse { logger.debug("chatSendCmd \(cmd.cmdType)") let resp = chatResponse(chat_send_cmd(getChatCtrl(), &c)!) logger.debug("chatSendCmd \(cmd.cmdType): \(resp.responseType)") + if case let .response(_, json) = resp { + logger.debug("chatSendCmd \(cmd.cmdType) response: \(json)") + } DispatchQueue.main.async { ChatModel.shared.terminalItems.append(.cmd(.now, cmd)) ChatModel.shared.terminalItems.append(.resp(.now, resp))