From c441e2cb435debcefb5d9ad469b126fc244ed3be Mon Sep 17 00:00:00 2001 From: Benoit Martins Date: Thu, 13 Jun 2024 10:23:36 +0200 Subject: [PATCH] Fix chatRoom refresh in background --- Linphone/Core/CoreContext.swift | 1 + Linphone/LinphoneApp.swift | 6 + .../UI/Call/ViewModel/CallViewModel.swift | 10 +- .../Fragments/ConversationFragment.swift | 1 + .../ConversationsListBottomSheet.swift | 2 +- .../Fragments/ConversationsListFragment.swift | 36 +-- .../Main/Conversations/Fragments/UIList.swift | 260 +++++++----------- .../Model/ConversationModel.swift | 13 - .../ViewModel/ConversationViewModel.swift | 32 ++- .../ConversationsListViewModel.swift | 23 +- 10 files changed, 169 insertions(+), 215 deletions(-) diff --git a/Linphone/Core/CoreContext.swift b/Linphone/Core/CoreContext.swift index 7344dc17b..1617df7f9 100644 --- a/Linphone/Core/CoreContext.swift +++ b/Linphone/Core/CoreContext.swift @@ -120,6 +120,7 @@ final class CoreContext: ObservableObject { self.mCore.videoDisplayEnabled = true self.mCore.videoPreviewEnabled = false self.mCore.fecEnabled = true + self.mCore.friendListSubscriptionEnabled = true self.mCoreSuscriptions.insert(self.mCore.publisher?.onGlobalStateChanged?.postOnCoreQueue { (cbVal: (core: Core, state: GlobalState, message: String)) in if cbVal.state == GlobalState.On { diff --git a/Linphone/LinphoneApp.swift b/Linphone/LinphoneApp.swift index 0f8a322ee..95cb4d7f8 100644 --- a/Linphone/LinphoneApp.swift +++ b/Linphone/LinphoneApp.swift @@ -154,6 +154,12 @@ struct LinphoneApp: App { if newPhase == .active { Log.info("Entering foreground") coreContext.onEnterForeground() + + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + if conversationViewModel != nil && conversationViewModel!.displayedConversation != nil && conversationsListViewModel != nil { + conversationViewModel!.resetDisplayedChatRoom(conversationsList: conversationsListViewModel!.conversationsList) + } + } } else if newPhase == .inactive { } else if newPhase == .background { Log.info("Entering background") diff --git a/Linphone/UI/Call/ViewModel/CallViewModel.swift b/Linphone/UI/Call/ViewModel/CallViewModel.swift index 04c598d17..741d58cf4 100644 --- a/Linphone/UI/Call/ViewModel/CallViewModel.swift +++ b/Linphone/UI/Call/ViewModel/CallViewModel.swift @@ -881,10 +881,12 @@ class CallViewModel: ObservableObject { coreContext.doOnCoreQueue { core in if core.currentCall != nil { let tokens = core.currentCall!.remoteAuthenticationTokens - self.letters1 = tokens[0] - self.letters2 = tokens[1] - self.letters3 = tokens[2] - self.letters4 = tokens[3] + DispatchQueue.main.async { + self.letters1 = tokens[0] + self.letters2 = tokens[1] + self.letters3 = tokens[2] + self.letters4 = tokens[3] + } } } } diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift index 265637b1d..ec8fe4443 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift @@ -162,6 +162,7 @@ struct ConversationFragment: View { UIList(viewModel: viewModel, paginationState: paginationState, conversationViewModel: conversationViewModel, + conversationsListViewModel: conversationsListViewModel, isScrolledToBottom: $isScrolledToBottom, showMessageMenuOnLongPress: showMessageMenuOnLongPress, geometryProxy: geometry, diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationsListBottomSheet.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationsListBottomSheet.swift index 7e4a53022..384293762 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationsListBottomSheet.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationsListBottomSheet.swift @@ -57,7 +57,7 @@ struct ConversationsListBottomSheet: View { Button { if conversationsListViewModel.selectedConversation != nil { conversationsListViewModel.objectWillChange.send() - conversationsListViewModel.selectedConversation!.markAsRead() + conversationsListViewModel.markAsReadSelectedConversation() conversationsListViewModel.updateUnreadMessagesCount() } diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationsListFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationsListFragment.swift index 9ef536ca9..e7c33020b 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationsListFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationsListFragment.swift @@ -27,8 +27,6 @@ struct ConversationsListFragment: View { @Binding var showingSheet: Bool - @State var canChangeChatRoom: Bool = true - var body: some View { VStack { List { @@ -136,34 +134,16 @@ struct ConversationsListFragment: View { .listRowSeparator(.hidden) .background(.white) .onTapGesture { - if canChangeChatRoom { - if conversationViewModel.displayedConversation != nil { - conversationViewModel.displayedConversation = nil - conversationViewModel.resetMessage() + if conversationViewModel.displayedConversation != nil { + conversationViewModel.displayedConversation = nil + conversationViewModel.resetMessage() + conversationViewModel.changeDisplayedChatRoom(conversationModel: conversationsListViewModel.conversationsList[index]) + + conversationViewModel.getMessages() + } else { + withAnimation { conversationViewModel.changeDisplayedChatRoom(conversationModel: conversationsListViewModel.conversationsList[index]) - - let firstScene = UIApplication.shared.connectedScenes.first as? UIWindowScene - if firstScene != nil { - let firstWindow = firstScene!.windows.first - if firstWindow != nil { - firstWindow!.layer.speed = 20.0 - canChangeChatRoom = false - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { - firstWindow!.layer.speed = 1.0 - canChangeChatRoom = true - } - } - } - - conversationViewModel.getMessages() - } else { - withAnimation { - conversationViewModel.changeDisplayedChatRoom(conversationModel: conversationsListViewModel.conversationsList[index]) - } } - conversationsListViewModel.conversationsList[index].markAsRead() - conversationsListViewModel.updateUnreadMessagesCount() } } .onLongPressGesture(minimumDuration: 0.2) { diff --git a/Linphone/UI/Main/Conversations/Fragments/UIList.swift b/Linphone/UI/Main/Conversations/Fragments/UIList.swift index 6bae3da69..79d4451b7 100644 --- a/Linphone/UI/Main/Conversations/Fragments/UIList.swift +++ b/Linphone/UI/Main/Conversations/Fragments/UIList.swift @@ -29,19 +29,16 @@ struct UIList: UIViewRepresentable { @ObservedObject var viewModel: ChatViewModel @ObservedObject var paginationState: PaginationState @ObservedObject var conversationViewModel: ConversationViewModel - + @ObservedObject var conversationsListViewModel: ConversationsListViewModel + @Binding var isScrolledToBottom: Bool let showMessageMenuOnLongPress: Bool let geometryProxy: GeometryProxy let sections: [MessagesSection] - + @State private var isScrolledToTop = false - - private let updatesQueue = DispatchQueue(label: "updatesQueue", qos: .utility) - @State private var updateSemaphore = DispatchSemaphore(value: 1) - @State private var tableSemaphore = DispatchSemaphore(value: 0) - + func makeUIView(context: Context) -> UITableView { let tableView = UITableView(frame: .zero, style: .grouped) tableView.contentInset = UIEdgeInsets(top: -10, left: 0, bottom: -20, right: 0) @@ -57,7 +54,7 @@ struct UIList: UIViewRepresentable { tableView.estimatedSectionFooterHeight = UITableView.automaticDimension tableView.backgroundColor = UIColor(.white) tableView.scrollsToTop = true - + NotificationCenter.default.addObserver(forName: .onScrollToBottom, object: nil, queue: nil) { _ in DispatchQueue.main.async { if !context.coordinator.sections.isEmpty { @@ -65,127 +62,77 @@ struct UIList: UIViewRepresentable { } } } - + return tableView } - + func updateUIView(_ tableView: UITableView, context: Context) { if context.coordinator.sections == sections { return } - updatesQueue.async { - updateSemaphore.wait() - - if context.coordinator.sections == sections { - updateSemaphore.signal() - return - } - - let prevSections = context.coordinator.sections - let (appliedDeletes, appliedDeletesSwapsAndEdits, deleteOperations, swapOperations, editOperations, insertOperations) = operationsSplit(oldSections: prevSections, newSections: sections) - - // step 1 - // preapare intermediate sections and operations - //print("1 updateUIView sections:", "\n") - //print("whole previous:\n", formatSections(prevSections), "\n") - //print("whole appliedDeletes:\n", formatSections(appliedDeletes), "\n") - //print("whole appliedDeletesSwapsAndEdits:\n", formatSections(appliedDeletesSwapsAndEdits), "\n") - //print("whole final sections:\n", formatSections(sections), "\n") - - //print("operations delete:\n", deleteOperations) - //print("operations swap:\n", swapOperations) - //print("operations edit:\n", editOperations) - //print("operations insert:\n", insertOperations) - - DispatchQueue.main.async { - tableView.performBatchUpdates { - // step 2 - // delete sections and rows if necessary - //print("2 apply delete") - context.coordinator.sections = appliedDeletes - for operation in deleteOperations { - applyOperation(operation, tableView: tableView) - } - } completion: { _ in - tableSemaphore.signal() - //print("2 finished delete") - } - } - tableSemaphore.wait() - - DispatchQueue.main.async { - tableView.performBatchUpdates { - // step 3 - // swap places for rows that moved inside the table - // (example of how this happens. send two messages: first m1, then m2. if m2 is delivered to server faster, then it should jump above m1 even though it was sent later) - //print("3 apply swaps") - context.coordinator.sections = appliedDeletesSwapsAndEdits // NOTE: this array already contains necessary edits, but won't be a problem for appplying swaps - for operation in swapOperations { - applyOperation(operation, tableView: tableView) - } - } completion: { _ in - tableSemaphore.signal() - //print("3 finished swaps") - } - } - tableSemaphore.wait() - - DispatchQueue.main.async { - tableView.performBatchUpdates { - // step 4 - // check only sections that are already in the table for existing rows that changed and apply only them to table's dataSource without animation - //print("4 apply edits") - context.coordinator.sections = appliedDeletesSwapsAndEdits - for operation in editOperations { - applyOperation(operation, tableView: tableView) - } - } completion: { _ in - tableSemaphore.signal() - //print("4 finished edits") - } - } - tableSemaphore.wait() - - if isScrolledToBottom || isScrolledToTop { - DispatchQueue.main.sync { - // step 5 - // apply the rest of the changes to table's dataSource, i.e. inserts - //print("5 apply inserts") - context.coordinator.sections = sections - - tableView.beginUpdates() - for operation in insertOperations { - applyOperation(operation, tableView: tableView) - } - tableView.endUpdates() - - updateSemaphore.signal() - } - } else { - updateSemaphore.signal() + if context.coordinator.sections == sections { + return + } + + let prevSections = context.coordinator.sections + let (appliedDeletes, appliedDeletesSwapsAndEdits, deleteOperations, swapOperations, editOperations, insertOperations) = operationsSplit(oldSections: prevSections, newSections: sections) + + tableView.performBatchUpdates { + context.coordinator.sections = appliedDeletes + for operation in deleteOperations { + applyOperation(operation, tableView: tableView) } } + + tableView.performBatchUpdates { + context.coordinator.sections = appliedDeletesSwapsAndEdits // NOTE: this array already contains necessary edits, but won't be a problem for appplying swaps + for operation in swapOperations { + applyOperation(operation, tableView: tableView) + } + } + + tableView.performBatchUpdates { + context.coordinator.sections = appliedDeletesSwapsAndEdits + for operation in editOperations { + applyOperation(operation, tableView: tableView) + } + } + + if isScrolledToBottom || isScrolledToTop { + context.coordinator.sections = sections + + tableView.beginUpdates() + for operation in insertOperations { + applyOperation(operation, tableView: tableView) + } + tableView.endUpdates() + } + + if isScrolledToBottom { + conversationViewModel.markAsRead() + conversationsListViewModel.computeChatRoomsList(filter: "") + } } - + // MARK: - Operations - + enum Operation { case deleteSection(Int) case insertSection(Int) - + case delete(Int, Int) // delete with animation case insert(Int, Int) // insert with animation case swap(Int, Int, Int) // delete first with animation, then insert it into new position with animation. do not do anything with the second for now case edit(Int, Int) // reload the element without animation } - + func applyOperation(_ operation: Operation, tableView: UITableView) { switch operation { case .deleteSection(let section): tableView.deleteSections([section], with: .top) case .insertSection(let section): tableView.insertSections([section], with: .top) - + case .delete(let section, let row): tableView.deleteRows(at: [IndexPath(row: row, section: section)], with: .top) case .insert(let section, let row): @@ -197,19 +144,16 @@ struct UIList: UIViewRepresentable { tableView.insertRows(at: [IndexPath(row: rowTo, section: section)], with: .top) } } - + func operationsSplit(oldSections: [MessagesSection], newSections: [MessagesSection]) -> ([MessagesSection], [MessagesSection], [Operation], [Operation], [Operation], [Operation]) { - var appliedDeletes = oldSections // start with old sections, remove rows that need to be deleted - var appliedDeletesSwapsAndEdits = newSections // take new sections and remove rows that need to be inserted for now, then we'll get array with all the changes except for inserts - // appliedDeletesSwapsEditsAndInserts == newSection - + var appliedDeletes = oldSections + var appliedDeletesSwapsAndEdits = newSections + var deleteOperations = [Operation]() var swapOperations = [Operation]() var editOperations = [Operation]() var insertOperations = [Operation]() - - // 1 compare sections - + let oldDates = oldSections.map { $0.date } let newDates = newSections.map { $0.date } let commonDates = Array(Set(oldDates + newDates)).sorted(by: >) @@ -217,7 +161,6 @@ struct UIList: UIViewRepresentable { let oldIndex = appliedDeletes.firstIndex(where: { $0.date == date } ) let newIndex = appliedDeletesSwapsAndEdits.firstIndex(where: { $0.date == date } ) if oldIndex == nil, let newIndex { - // operationIndex is not the same as newIndex because appliedDeletesSwapsAndEdits is being changed as we go, but to apply changes to UITableView we should have initial index if let operationIndex = newSections.firstIndex(where: { $0.date == date } ) { appliedDeletesSwapsAndEdits.remove(at: newIndex) insertOperations.append(.insertSection(operationIndex)) @@ -232,63 +175,53 @@ struct UIList: UIViewRepresentable { continue } guard let newIndex, let oldIndex else { continue } - - // 2 compare section rows - // isolate deletes and inserts, and remove them from row arrays, leaving only rows that are in both arrays: 'duplicates' - // this will allow to compare relative position changes of rows - swaps - + var oldRows = appliedDeletes[oldIndex].rows var newRows = appliedDeletesSwapsAndEdits[newIndex].rows let oldRowIDs = Set(oldRows.map { $0.id }) let newRowIDs = Set(newRows.map { $0.id }) let rowIDsToDelete = oldRowIDs.subtracting(newRowIDs) - let rowIDsToInsert = newRowIDs.subtracting(oldRowIDs) // TODO is order important? + let rowIDsToInsert = newRowIDs.subtracting(oldRowIDs) for rowId in rowIDsToDelete { if let index = oldRows.firstIndex(where: { $0.id == rowId }) { oldRows.remove(at: index) - deleteOperations.append(.delete(oldIndex, index)) // this row was in old section, should not be in final result + deleteOperations.append(.delete(oldIndex, index)) } } for rowId in rowIDsToInsert { if let index = newRows.firstIndex(where: { $0.id == rowId }) { - // this row was not in old section, should add it to final result insertOperations.append(.insert(newIndex, index)) } } - + for rowId in rowIDsToInsert { if let index = newRows.firstIndex(where: { $0.id == rowId }) { - // remove for now, leaving only 'duplicates' newRows.remove(at: index) } } - - // 3 isolate swaps and edits - + for row in 0.. Bool { !swaps.filter { if case let .swap(section, rowFrom, rowTo) = $0 { @@ -297,12 +230,13 @@ struct UIList: UIViewRepresentable { return false }.isEmpty } - + // MARK: - Coordinator - + func makeCoordinator() -> Coordinator { Coordinator( conversationViewModel: conversationViewModel, + conversationsListViewModel: conversationsListViewModel, viewModel: viewModel, paginationState: paginationState, isScrolledToBottom: $isScrolledToBottom, @@ -314,20 +248,22 @@ struct UIList: UIViewRepresentable { } class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate { - + @ObservedObject var viewModel: ChatViewModel @ObservedObject var paginationState: PaginationState @ObservedObject var conversationViewModel: ConversationViewModel - + @ObservedObject var conversationsListViewModel: ConversationsListViewModel + @Binding var isScrolledToBottom: Bool @Binding var isScrolledToTop: Bool let showMessageMenuOnLongPress: Bool let geometryProxy: GeometryProxy var sections: [MessagesSection] - - init(conversationViewModel: ConversationViewModel, viewModel: ChatViewModel, paginationState: PaginationState, isScrolledToBottom: Binding, isScrolledToTop: Binding, showMessageMenuOnLongPress: Bool, geometryProxy: GeometryProxy, sections: [MessagesSection]) { + + init(conversationViewModel: ConversationViewModel, conversationsListViewModel: ConversationsListViewModel, viewModel: ChatViewModel, paginationState: PaginationState, isScrolledToBottom: Binding, isScrolledToTop: Binding, showMessageMenuOnLongPress: Bool, geometryProxy: GeometryProxy, sections: [MessagesSection]) { self.conversationViewModel = conversationViewModel + self.conversationsListViewModel = conversationsListViewModel self.viewModel = viewModel self.paginationState = paginationState self._isScrolledToBottom = isScrolledToBottom @@ -336,15 +272,15 @@ struct UIList: UIViewRepresentable { self.geometryProxy = geometryProxy self.sections = sections } - + func numberOfSections(in tableView: UITableView) -> Int { sections.count } - + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { sections[section].rows.count } - + func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return progressView(section) } @@ -361,13 +297,13 @@ struct UIList: UIViewRepresentable { } return nil } - + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - + let tableViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) tableViewCell.selectionStyle = .none tableViewCell.backgroundColor = UIColor(.white) - + let row = sections[indexPath.section].rows[indexPath.row] if #available(iOS 16.0, *) { tableViewCell.contentConfiguration = UIHostingConfiguration { @@ -381,20 +317,20 @@ struct UIList: UIViewRepresentable { } tableViewCell.transform = CGAffineTransformMakeScale(1, -1) - + return tableViewCell } - + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { let row = sections[indexPath.section].rows[indexPath.row] paginationState.handle(row) } - + func scrollViewDidScroll(_ scrollView: UIScrollView) { isScrolledToBottom = scrollView.contentOffset.y <= 10 - if isScrolledToBottom && conversationViewModel.displayedConversationUnreadMessagesCount > 0 { conversationViewModel.markAsRead() + conversationsListViewModel.computeChatRoomsList(filter: "") } if !isScrolledToTop && scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.frame.height - 200 { @@ -406,44 +342,44 @@ struct UIList: UIViewRepresentable { } struct MessagesSection: Equatable { - + let date: Date var rows: [Message] - + static var formatter = { let formatter = DateFormatter() formatter.dateFormat = "EEEE, MMMM d" return formatter }() - + init(date: Date, rows: [Message]) { self.date = date self.rows = rows } - + var formattedDate: String { MessagesSection.formatter.string(from: date) } - + static func == (lhs: MessagesSection, rhs: MessagesSection) -> Bool { lhs.date == rhs.date && lhs.rows == rhs.rows } - + } final class PaginationState: ObservableObject { var onEvent: ChatPaginationClosure? var offset: Int - + var shouldHandlePagination: Bool { onEvent != nil } - + init(onEvent: ChatPaginationClosure? = nil, offset: Int = 0) { self.onEvent = onEvent self.offset = offset } - + func handle(_ message: Message) { guard shouldHandlePagination else { return @@ -457,9 +393,9 @@ final class ChatViewModel: ObservableObject { @Published private(set) var fullscreenAttachmentItem: Optional = nil @Published var fullscreenAttachmentPresented = false - + @Published var messageMenuRow: Message? - + public var didSendMessage: (DraftMessage) -> Void = {_ in} func presentAttachmentFullScreen(_ attachment: Attachment) { @@ -471,7 +407,7 @@ final class ChatViewModel: ObservableObject { fullscreenAttachmentPresented = false fullscreenAttachmentItem = nil } - + func sendMessage(_ message: DraftMessage) { didSendMessage(message) } diff --git a/Linphone/UI/Main/Conversations/Model/ConversationModel.swift b/Linphone/UI/Main/Conversations/Model/ConversationModel.swift index cb50b7d8c..f2f3e27eb 100644 --- a/Linphone/UI/Main/Conversations/Model/ConversationModel.swift +++ b/Linphone/UI/Main/Conversations/Model/ConversationModel.swift @@ -94,19 +94,6 @@ class ConversationModel: ObservableObject { } } - func markAsRead() { - coreContext.doOnCoreQueue { _ in - let unreadMessagesCountTmp = self.chatRoom.unreadMessagesCount - if unreadMessagesCountTmp > 0 { - self.chatRoom.markAsRead() - - DispatchQueue.main.async { - self.unreadMessagesCount = 0 - } - } - } - } - func toggleMute() { coreContext.doOnCoreQueue { _ in self.chatRoom.muted.toggle() diff --git a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift index 50a952660..3cc07eec6 100644 --- a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift +++ b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift @@ -400,6 +400,8 @@ class ConversationViewModel: ObservableObject { let isFirstMessageTmp = (eventLog.chatMessage?.isOutgoing ?? false) ? isFirstMessageOutgoingTmp : isFirstMessageIncomingTmp + let unreadMessagesCount = self.displayedConversation!.chatRoom.unreadMessagesCount + var statusTmp: Message.Status? = .sending switch eventLog.chatMessage?.state { case .InProgress: @@ -441,14 +443,10 @@ class ConversationViewModel: ObservableObject { } if !message.isOutgoing { - self.displayedConversationUnreadMessagesCount += 1 + self.displayedConversationUnreadMessagesCount = unreadMessagesCount } } } - - if self.displayedConversation != nil { - self.displayedConversation!.markAsRead() - } } } @@ -581,13 +579,35 @@ class ConversationViewModel: ObservableObject { self.displayedConversation = conversationModel } + func resetDisplayedChatRoom(conversationsList: [ConversationModel]) { + removeConversationDelegate() + + if self.displayedConversation != nil { + conversationsList.forEach { conversation in + if conversation.id == self.displayedConversation!.id { + self.displayedConversation = conversation + + self.chatRoomSuscriptions.insert(conversation.chatRoom.publisher?.onChatMessageSending?.postOnCoreQueue { (cbValue: (chatRoom: ChatRoom, eventLog: EventLog)) in + self.getNewMessages(eventLogs: [cbValue.eventLog]) + }) + + self.chatRoomSuscriptions.insert(conversation.chatRoom.publisher?.onChatMessagesReceived?.postOnCoreQueue { (cbValue: (chatRoom: ChatRoom, eventLogs: [EventLog])) in + self.getNewMessages(eventLogs: cbValue.eventLogs) + }) + } + } + } + } + func downloadContent(chatMessage: ChatMessage, content: Content) { //Log.debug("[ConversationViewModel] Starting downloading content for file \(model.fileName)") if content.filePath == nil || content.filePath!.isEmpty { let contentName = content.name if contentName != nil { let isImage = FileUtil.isExtensionImage(path: contentName!) - let file = FileUtil.getFileStoragePath(fileName: contentName ?? "", isImage: isImage) + let groupName = "group.\(Bundle.main.bundleIdentifier ?? "").linphoneExtension" + let file = FileUtil.sharedContainerUrl().appendingPathComponent("Library/Images").absoluteString + (contentName!.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "") + //let file = FileUtil.getFileStoragePath(fileName: contentName ?? "", isImage: isImage) content.filePath = String(file.dropFirst(7)) Log.info( "[ConversationViewModel] File \(contentName) will be downloaded at \(content.filePath)" diff --git a/Linphone/UI/Main/Conversations/ViewModel/ConversationsListViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/ConversationsListViewModel.swift index 573a11bc3..71d92dac4 100644 --- a/Linphone/UI/Main/Conversations/ViewModel/ConversationsListViewModel.swift +++ b/Linphone/UI/Main/Conversations/ViewModel/ConversationsListViewModel.swift @@ -62,11 +62,21 @@ class ConversationsListViewModel: ObservableObject { func addConversationDelegate() { coreContext.doOnCoreQueue { core in + let account = core.defaultAccount + let chatRoomsCounter = account?.chatRooms != nil ? account!.chatRooms.count : core.chatRooms.count + var counter = 0 self.mCoreSuscriptions.insert(core.publisher?.onChatRoomStateChanged?.postOnCoreQueue { (cbValue: (core: Core, chatRoom: ChatRoom, state: ChatRoom.State)) in //Log.info("[ConversationsListViewModel] Conversation [${LinphoneUtils.getChatRoomId(chatRoom)}] state changed [$state]") switch cbValue.state { case ChatRoom.State.Created: - self.computeChatRoomsList(filter: "") + if !(cbValue.chatRoom.isEmpty && cbValue.chatRoom.hasCapability(mask: ChatRoom.Capabilities.OneToOne.rawValue)) { + counter += 1 + } + + if counter >= chatRoomsCounter { + self.computeChatRoomsList(filter: "") + counter = 0 + } case ChatRoom.State.Deleted: self.computeChatRoomsList(filter: "") //ToastViewModel.shared.toastMessage = "toast_conversation_deleted" @@ -177,4 +187,15 @@ class ConversationsListViewModel: ObservableObject { reorderChatRooms() } + + func markAsReadSelectedConversation() { + coreContext.doOnCoreQueue { _ in + let unreadMessagesCount = self.selectedConversation!.chatRoom.unreadMessagesCount + + if unreadMessagesCount > 0 { + self.selectedConversation!.chatRoom.markAsRead() + self.selectedConversation!.unreadMessagesCount = 0 + } + } + } }