open on unread

This commit is contained in:
Diogo 2024-11-05 15:50:55 +00:00
parent e1f24e7588
commit 009968b7d4
6 changed files with 52 additions and 13 deletions

View file

@ -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()
}
}

View file

@ -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()

View file

@ -36,8 +36,11 @@ struct ReverseList<Content: View>: 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<Content: View>: UIViewControllerRepresentable {
self.dataSource = UITableViewDiffableDataSource<Section, ChatItem>(
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<Content: View>: 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<Content: View>: 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"

View file

@ -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" {

View file

@ -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?)

View file

@ -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