mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 12:19:54 +00:00
open on unread
This commit is contained in:
parent
e1f24e7588
commit
009968b7d4
6 changed files with 52 additions and 13 deletions
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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" {
|
||||
|
|
|
@ -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?)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue