From 009968b7d4281e44f261443388432653804466bb Mon Sep 17 00:00:00 2001 From: Diogo Date: Tue, 5 Nov 2024 15:50:55 +0000 Subject: [PATCH] open on unread --- apps/ios/Shared/Model/SimpleXAPI.swift | 8 +++--- apps/ios/Shared/Views/Chat/ChatView.swift | 18 ++++++++++++ apps/ios/Shared/Views/Chat/ReverseList.swift | 30 ++++++++++++++++++-- apps/ios/SimpleXChat/API.swift | 2 +- apps/ios/SimpleXChat/APITypes.swift | 2 +- apps/ios/SimpleXChat/ChatTypes.swift | 5 ---- 6 files changed, 52 insertions(+), 13 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 5ff6d39c96..f0e64b2dce 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -320,19 +320,19 @@ private func apiChatsResponse(_ r: ChatResponse) throws -> [ChatData] { let loadItemsPerPage = 50 -func apiGetChat(type: ChatType, id: Int64, search: String = "") async throws -> (Chat, ChatGap?) { - let r = await chatSendCmd(.apiGetChat(type: type, id: id, pagination: .last(count: loadItemsPerPage), search: search)) +func apiGetChat(type: ChatType, id: Int64, search: String = "") async throws -> (Chat, Int?) { + 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) } throw r } -func apiGetChatItems(type: ChatType, id: Int64, pagination: ChatPagination, search: String = "") async throws -> ([ChatItem], ChatGap?) { +func apiGetChatItems(type: ChatType, id: Int64, pagination: ChatPagination, search: String = "") async throws -> ([ChatItem], Int?) { 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 .chatCmdError(_, _) = r { if case .chatError(_, let chatError) = r { if case .errorStore(let storeError) = chatError { - if case .chatItemNotFound(let itemId) = storeError { + if case .chatItemNotFound(_) = storeError { itemNotFoundAlert() } } diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index e1ea89e250..06ee173237 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -363,6 +363,11 @@ struct ChatView: View { } } ChatView.FloatingButtonModel.shared.totalUnread = chat.chatStats.unreadCount + Task { + if let firstunreadItem = self.getFirstUnreadItem() { + scrollModel.scrollToUnread(id: firstunreadItem.id) + } + } } private func searchToolbar() -> some View { @@ -471,6 +476,19 @@ struct ChatView: View { EmptyView() } } + + private func getFirstUnreadItem() -> ChatItem? { + logger.error("[scrolling] \(im.reversedChatItems.count)") + + for i in stride(from: im.reversedChatItems.count - 1, through: 0, by: -1) { + let item = im.reversedChatItems[i] + if item.isRcvNew { + logger.error("[scrolling] First unread item: \(item.text)") + return item + } + } + return nil + } class FloatingButtonModel: ObservableObject { static let shared = FloatingButtonModel() diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift index e33adcef58..eac881ebd4 100644 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ b/apps/ios/Shared/Views/Chat/ReverseList.swift @@ -36,8 +36,11 @@ struct ReverseList: UIViewControllerRepresentable { controller.scroll(to: items.firstIndex(where: { $0.id == id }), position: .bottom) case .bottom: controller.scroll(to: 0, position: .top) + case let .unread(id): + controller.scrollToUnread(to: items.firstIndex(where: { $0.id == id })) } } else { + logger.error("[scrolling] not scrolling") controller.update(items: items) } } @@ -78,7 +81,8 @@ struct ReverseList: UIViewControllerRepresentable { self.dataSource = UITableViewDiffableDataSource( tableView: tableView ) { (tableView, indexPath, item) -> UITableViewCell? in - if indexPath.item > self.itemCount - 8 { + if indexPath.item > self.itemCount - 8, self.representer.scrollState == .atDestination { + logger.error("[scrolling] requesting page") self.representer.loadPage() } let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath) @@ -161,6 +165,18 @@ struct ReverseList: UIViewControllerRepresentable { ) Task { representer.scrollState = .atDestination } } + + func scrollToUnread(to index: Int?) { + if let index = index { + if isVisible(indexPath: IndexPath(row: index, section: 0)) { + self.scroll(to: 0, position: .top) + } else { + self.scroll(to: index, position: .bottom) + } + } else { + self.scroll(to: index, position: .bottom) + } + } /// Scrolls to Item at index path /// - Parameter indexPath: Item to scroll to - will scroll to beginning of the list, if `nil` @@ -169,7 +185,7 @@ struct ReverseList: UIViewControllerRepresentable { if #available(iOS 16.0, *) { animated = true } - if let index, tableView.numberOfRows(inSection: 0) != 0 { + if let index, tableView.numberOfRows(inSection: 0) != 0, !isVisible(indexPath: IndexPath(row: index, section: 0)) { tableView.scrollToRow( at: IndexPath(row: index, section: 0), at: position, @@ -294,6 +310,7 @@ class ReverseListScrollModel: ObservableObject { case nextPage case item(ChatItem.ID) case bottom + case unread(ChatItem.ID) } case scrollingTo(Destination) @@ -303,16 +320,25 @@ class ReverseListScrollModel: ObservableObject { @Published var state: State = .atDestination func scrollToNextPage() { + logger.error("[scrolling] to next page") + state = .scrollingTo(.nextPage) } func scrollToBottom() { + logger.error("[scrolling] to bottom") state = .scrollingTo(.bottom) } func scrollToItem(id: ChatItem.ID) { + logger.error("[scrolling] to item \(id)") state = .scrollingTo(.item(id)) } + + func scrollToUnread(id: ChatItem.ID) { + logger.error("[scrolling] to unread") + state = .scrollingTo(.unread(id)) + } } fileprivate let cellReuseId = "hostingCell" diff --git a/apps/ios/SimpleXChat/API.swift b/apps/ios/SimpleXChat/API.swift index 169078e1c7..1d0105f1af 100644 --- a/apps/ios/SimpleXChat/API.swift +++ b/apps/ios/SimpleXChat/API.swift @@ -214,7 +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: ChatGap? = try? decodeObject(jApiChat["gap"] as Any) + let gap = jApiChat["gap"] as? Int return .apiChat(user: user, chat: chat, gap: gap) } } else if type == "chatCmdError" { diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 3f44649f9e..39c46c1adf 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: ChatGap?) + case apiChat(user: UserRef, chat: ChatData, gap: Int?) case chatItemInfo(user: UserRef, chatItem: AChatItem, chatItemInfo: ChatItemInfo) case userProtoServers(user: UserRef, servers: UserProtoServers) case serverTestResult(user: UserRef, testServer: String, testFailure: ProtocolTestFailure?) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index b11a4127e6..1bd5673f01 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1529,11 +1529,6 @@ public struct ChatStats: Decodable, Hashable { public var unreadChat: Bool = false } -public struct ChatGap: Decodable, Hashable { - public var index: Int? - public var size: Int -} - public struct Contact: Identifiable, Decodable, NamedChat, Hashable { public var contactId: Int64 var localDisplayName: ContactName