mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-29 04:39:53 +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
|
let loadItemsPerPage = 50
|
||||||
|
|
||||||
func apiGetChat(type: ChatType, id: Int64, search: String = "") async throws -> (Chat, ChatGap?) {
|
func apiGetChat(type: ChatType, id: Int64, search: String = "") async throws -> (Chat, Int?) {
|
||||||
let r = await chatSendCmd(.apiGetChat(type: type, id: id, pagination: .last(count: loadItemsPerPage), search: search))
|
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, gap) = r { return (Chat.init(chat), gap) }
|
||||||
throw r
|
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))
|
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, gap) = r { return (chat.chatItems, gap) }
|
||||||
if case .chatCmdError(_, _) = r {
|
if case .chatCmdError(_, _) = r {
|
||||||
if case .chatError(_, let chatError) = r {
|
if case .chatError(_, let chatError) = r {
|
||||||
if case .errorStore(let storeError) = chatError {
|
if case .errorStore(let storeError) = chatError {
|
||||||
if case .chatItemNotFound(let itemId) = storeError {
|
if case .chatItemNotFound(_) = storeError {
|
||||||
itemNotFoundAlert()
|
itemNotFoundAlert()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -363,6 +363,11 @@ struct ChatView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ChatView.FloatingButtonModel.shared.totalUnread = chat.chatStats.unreadCount
|
ChatView.FloatingButtonModel.shared.totalUnread = chat.chatStats.unreadCount
|
||||||
|
Task {
|
||||||
|
if let firstunreadItem = self.getFirstUnreadItem() {
|
||||||
|
scrollModel.scrollToUnread(id: firstunreadItem.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func searchToolbar() -> some View {
|
private func searchToolbar() -> some View {
|
||||||
|
@ -471,6 +476,19 @@ struct ChatView: View {
|
||||||
EmptyView()
|
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 {
|
class FloatingButtonModel: ObservableObject {
|
||||||
static let shared = FloatingButtonModel()
|
static let shared = FloatingButtonModel()
|
||||||
|
|
|
@ -36,8 +36,11 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||||
controller.scroll(to: items.firstIndex(where: { $0.id == id }), position: .bottom)
|
controller.scroll(to: items.firstIndex(where: { $0.id == id }), position: .bottom)
|
||||||
case .bottom:
|
case .bottom:
|
||||||
controller.scroll(to: 0, position: .top)
|
controller.scroll(to: 0, position: .top)
|
||||||
|
case let .unread(id):
|
||||||
|
controller.scrollToUnread(to: items.firstIndex(where: { $0.id == id }))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
logger.error("[scrolling] not scrolling")
|
||||||
controller.update(items: items)
|
controller.update(items: items)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,7 +81,8 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||||
self.dataSource = UITableViewDiffableDataSource<Section, ChatItem>(
|
self.dataSource = UITableViewDiffableDataSource<Section, ChatItem>(
|
||||||
tableView: tableView
|
tableView: tableView
|
||||||
) { (tableView, indexPath, item) -> UITableViewCell? in
|
) { (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()
|
self.representer.loadPage()
|
||||||
}
|
}
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath)
|
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath)
|
||||||
|
@ -161,6 +165,18 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||||
)
|
)
|
||||||
Task { representer.scrollState = .atDestination }
|
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
|
/// Scrolls to Item at index path
|
||||||
/// - Parameter indexPath: Item to scroll to - will scroll to beginning of the list, if `nil`
|
/// - 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, *) {
|
if #available(iOS 16.0, *) {
|
||||||
animated = true
|
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(
|
tableView.scrollToRow(
|
||||||
at: IndexPath(row: index, section: 0),
|
at: IndexPath(row: index, section: 0),
|
||||||
at: position,
|
at: position,
|
||||||
|
@ -294,6 +310,7 @@ class ReverseListScrollModel: ObservableObject {
|
||||||
case nextPage
|
case nextPage
|
||||||
case item(ChatItem.ID)
|
case item(ChatItem.ID)
|
||||||
case bottom
|
case bottom
|
||||||
|
case unread(ChatItem.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
case scrollingTo(Destination)
|
case scrollingTo(Destination)
|
||||||
|
@ -303,16 +320,25 @@ class ReverseListScrollModel: ObservableObject {
|
||||||
@Published var state: State = .atDestination
|
@Published var state: State = .atDestination
|
||||||
|
|
||||||
func scrollToNextPage() {
|
func scrollToNextPage() {
|
||||||
|
logger.error("[scrolling] to next page")
|
||||||
|
|
||||||
state = .scrollingTo(.nextPage)
|
state = .scrollingTo(.nextPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollToBottom() {
|
func scrollToBottom() {
|
||||||
|
logger.error("[scrolling] to bottom")
|
||||||
state = .scrollingTo(.bottom)
|
state = .scrollingTo(.bottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollToItem(id: ChatItem.ID) {
|
func scrollToItem(id: ChatItem.ID) {
|
||||||
|
logger.error("[scrolling] to item \(id)")
|
||||||
state = .scrollingTo(.item(id))
|
state = .scrollingTo(.item(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func scrollToUnread(id: ChatItem.ID) {
|
||||||
|
logger.error("[scrolling] to unread")
|
||||||
|
state = .scrollingTo(.unread(id))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate let cellReuseId = "hostingCell"
|
fileprivate let cellReuseId = "hostingCell"
|
||||||
|
|
|
@ -214,7 +214,7 @@ public func chatResponse(_ s: String) -> ChatResponse {
|
||||||
let user: UserRef = try? decodeObject(jApiChat["user"] as Any),
|
let user: UserRef = try? decodeObject(jApiChat["user"] as Any),
|
||||||
let jChat = jApiChat["chat"] as? NSDictionary,
|
let jChat = jApiChat["chat"] as? NSDictionary,
|
||||||
let chat = try? parseChatData(jChat) {
|
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)
|
return .apiChat(user: user, chat: chat, gap: gap)
|
||||||
}
|
}
|
||||||
} else if type == "chatCmdError" {
|
} else if type == "chatCmdError" {
|
||||||
|
|
|
@ -546,7 +546,7 @@ public enum ChatResponse: Decodable, Error {
|
||||||
case chatStopped
|
case chatStopped
|
||||||
case chatSuspended
|
case chatSuspended
|
||||||
case apiChats(user: UserRef, chats: [ChatData])
|
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 chatItemInfo(user: UserRef, chatItem: AChatItem, chatItemInfo: ChatItemInfo)
|
||||||
case userProtoServers(user: UserRef, servers: UserProtoServers)
|
case userProtoServers(user: UserRef, servers: UserProtoServers)
|
||||||
case serverTestResult(user: UserRef, testServer: String, testFailure: ProtocolTestFailure?)
|
case serverTestResult(user: UserRef, testServer: String, testFailure: ProtocolTestFailure?)
|
||||||
|
|
|
@ -1529,11 +1529,6 @@ public struct ChatStats: Decodable, Hashable {
|
||||||
public var unreadChat: Bool = false
|
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 struct Contact: Identifiable, Decodable, NamedChat, Hashable {
|
||||||
public var contactId: Int64
|
public var contactId: Int64
|
||||||
var localDisplayName: ContactName
|
var localDisplayName: ContactName
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue