diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 2abe59b3bb..4917319fd7 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -111,11 +111,7 @@ class ItemsModel: ObservableObject { if let chat = ChatModel.shared.getChat(chatId) { await MainActor.run { self.isLoading = true } // try? await Task.sleep(nanoseconds: 5000_000000) - if let gap = await loadChat(chat: chat) { - self.gaps = [gap] - } else { - self.gaps = [] - } + await loadChat(chat: chat) navigationTimeout.cancel() progressTimeout.cancel() await MainActor.run { diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 75bafbe539..b8d0882da6 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -322,15 +322,15 @@ let loadItemsPerPage = 100 let preloadItem = 25 let idealChatListSize = 300 -func apiGetChat(type: ChatType, id: Int64, search: String = "") async throws -> (Chat, Int?) { +func apiGetChat(type: ChatType, id: Int64, search: String = "") async throws -> Chat { let r = await chatSendCmd(.apiGetChat(type: type, id: id, pagination: .initial(count: loadItemsPerPage), search: search)) - if case let .apiChat(_, chat, gap) = r { return (Chat.init(chat), gap) } + if case let .apiChat(_, chat) = r { return Chat.init(chat) } throw r } -func apiGetChatItems(type: ChatType, id: Int64, pagination: ChatPagination, search: String = "") async throws -> ([ChatItem], Int?) { +func apiGetChatItems(type: ChatType, id: Int64, pagination: ChatPagination, search: String = "") async throws -> [ChatItem] { let r = await chatSendCmd(.apiGetChat(type: type, id: id, pagination: pagination, search: search)) - if case let .apiChat(_, chat, gap) = r { return (chat.chatItems, gap) } + if case let .apiChat(_, chat) = r { return chat.chatItems } if case .chatCmdError(_, _) = r { if case .chatError(_, let chatError) = r { if case .errorStore(let storeError) = chatError { @@ -343,7 +343,7 @@ func apiGetChatItems(type: ChatType, id: Int64, pagination: ChatPagination, sear throw r } -func loadChat(chat: Chat, search: String = "", clearItems: Bool = true) async -> ChatItem.ID? { +func loadChat(chat: Chat, search: String = "", clearItems: Bool = true) async { do { let cInfo = chat.chatInfo let m = ChatModel.shared @@ -354,21 +354,14 @@ func loadChat(chat: Chat, search: String = "", clearItems: Bool = true) async -> im.reversedChatItems = [] } } - let (chat, gap) = try await apiGetChat(type: cInfo.chatType, id: cInfo.apiId, search: search) + let chat = try await apiGetChat(type: cInfo.chatType, id: cInfo.apiId, search: search) await MainActor.run { im.reversedChatItems = chat.chatItems.reversed() m.updateChatInfo(chat.chatInfo) } - - return if gap != nil { - chat.chatItems[loadItemsPerPage].id - } else { - nil - } } catch let error { logger.error("loadChat error: \(responseError(error))") } - return nil } func apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) async throws -> ChatItemInfo { diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index ee8e53ca2f..ba74169320 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -364,6 +364,10 @@ struct ChatView: View { await markChatUnread(chat, unreadChat: false) } } + if im.reversedChatItems.count == loadItemsPerPage { + loadChatItems(chat.chatInfo, .last(count: loadItemsPerPage)) + } + ChatView.FloatingButtonModel.shared.totalUnread = chat.chatStats.unreadCount Task { DispatchQueue.main.async { @@ -879,7 +883,7 @@ struct ChatView: View { loadingItems = true do { - let (chatItems, gap) = try await apiGetChatItems( + let chatItems = try await apiGetChatItems( type: cInfo.chatType, id: cInfo.apiId, pagination: pagination, @@ -897,20 +901,28 @@ struct ChatView: View { switch pagination { case .last: await MainActor.run { - im.reversedChatItems = chatItems.reversed() - im.gaps = [] + let newItemIds = Set(chatItems.map { $0.id }) + var duplicateFound = false + newItems.removeAll { + let isDuplicate = newItemIds.contains($0.id) + duplicateFound = duplicateFound || isDuplicate + return isDuplicate + } + + if !duplicateFound { + if let existingItem = im.reversedChatItems.first { + im.gaps = [existingItem.id] + } + } + + newItems.insert(contentsOf: chatItems.reversed(), at: 0) + im.reversedChatItems = newItems loadingItems = false } - case let .initial(count): + case .initial: await MainActor.run { im.reversedChatItems = chatItems.reversed() - - if gap != nil { - let firstBottomItem = chatItems[count] - im.gaps = [firstBottomItem.id] - } else { - im.gaps = [] - } + im.gaps = [] loadingItems = false } case let .after(chatItemId, _): diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index 67a045ab9a..fd72b5b515 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -340,7 +340,7 @@ struct GroupMemberInfoView: View { InfoViewButton(image: "message.fill", title: "message", width: width) { Task { do { - let (chat, _) = try await apiGetChat(type: .direct, id: contactId) + let chat = try await apiGetChat(type: .direct, id: contactId) chatModel.addChat(chat) ItemsModel.shared.loadOpenChat(chat.id) { dismissAllSheets(animated: true) diff --git a/apps/ios/SimpleXChat/API.swift b/apps/ios/SimpleXChat/API.swift index 1d0105f1af..987f7f3d41 100644 --- a/apps/ios/SimpleXChat/API.swift +++ b/apps/ios/SimpleXChat/API.swift @@ -214,8 +214,7 @@ public func chatResponse(_ s: String) -> ChatResponse { let user: UserRef = try? decodeObject(jApiChat["user"] as Any), let jChat = jApiChat["chat"] as? NSDictionary, let chat = try? parseChatData(jChat) { - let gap = jApiChat["gap"] as? Int - return .apiChat(user: user, chat: chat, gap: gap) + return .apiChat(user: user, chat: chat) } } else if type == "chatCmdError" { if let jError = jResp["chatCmdError"] as? NSDictionary { diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 39c46c1adf..42325a1360 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -546,7 +546,7 @@ public enum ChatResponse: Decodable, Error { case chatStopped case chatSuspended case apiChats(user: UserRef, chats: [ChatData]) - case apiChat(user: UserRef, chat: ChatData, gap: Int?) + case apiChat(user: UserRef, chat: ChatData) case chatItemInfo(user: UserRef, chatItem: AChatItem, chatItemInfo: ChatItemInfo) case userProtoServers(user: UserRef, servers: UserProtoServers) case serverTestResult(user: UserRef, testServer: String, testFailure: ProtocolTestFailure?) @@ -888,7 +888,7 @@ public enum ChatResponse: Decodable, Error { case .chatStopped: return noDetails case .chatSuspended: return noDetails case let .apiChats(u, chats): return withUser(u, String(describing: chats)) - case let .apiChat(u, chat, gap): return withUser(u, "gap: \(String(describing: gap)) \(String(describing: chat))") + case let .apiChat(u, chat): return withUser(u, String(describing: chat)) case let .chatItemInfo(u, chatItem, chatItemInfo): return withUser(u, "chatItem: \(String(describing: chatItem))\nchatItemInfo: \(String(describing: chatItemInfo))") case let .userProtoServers(u, servers): return withUser(u, "servers: \(String(describing: servers))") case let .serverTestResult(u, server, testFailure): return withUser(u, "server: \(server)\nresult: \(String(describing: testFailure))")