ios: open chat on first unread, "scroll" to quoted items that were not loaded (#5392)

* ios: open chat on first unread, "scroll" to quoted items that were not loaded

* more changes

* changes

* unused

* fix reveal logic

* debug

* changes

* test

* Revert "test"

This reverts commit 553be124d5.

* change

* change

* changes

* changes

* changes

* commented deceleration logic

* changes

* fixes

* optimized item identifiers to use merged item directly

* fixed counters

* encreased initial and preload counters

* fix initial loading and trimming items

* optimize

* allow marking read

* 10 instead of 5

* performance

* one more parameter in hash

* disable trimming

* performance

* performance - in background

* optimization

* next/prev

* changes

* markread

* finally

* less logs

* read

* change after merge

* trimming, edge cases

* wait until items loaded

* Revert "wait until items loaded"

This reverts commit 895218b978.

* progress indicator

* optimization

* disable scroll helper

* experiment

* Revert "experiment"

This reverts commit c952c9e623.

* jump

* no read

* layoutIfNeeded

* changes

* EndlessScrollView

* read

* changes

* changes

* changes

* reduce time to open a chat (by ~300ms)

* open from the first unread when clicking member chat

* refactored and removed unused code

* handling search emptiness to scroll to correct position

* changes

* read state maintain

* remove protocol

* avoid parsing chatId

* pass chat

* changes

* remove reveal

* refactor spaghetti

* remove ItemsScrollModel

* rename

* remove setUpdateListener

* unused

* optimization

* scrollToTop

* fix

* scrollbar working again

* scrollToBottom

* fix

* scrollBar hiding when not many items on screen

* small safer change

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
Stanislav Dmitrenko 2025-02-18 01:21:40 +07:00 committed by GitHub
parent 704bab171d
commit 9d1329498b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 2177 additions and 711 deletions

View file

@ -53,7 +53,11 @@ class ItemsModel: ObservableObject {
var itemAdded = false {
willSet { publisher.send() }
}
// set listener here that will be notified on every add/delete of a chat item
let chatState = ActiveChatState()
var chatItemsChangesListener: RecalculatePositions = RecalculatePositions()
// Publishes directly to `objectWillChange` publisher,
// this will cause reversedChatItems to be rendered without throttling
@Published var isLoading = false
@ -83,18 +87,16 @@ class ItemsModel: ObservableObject {
} catch {}
}
Task {
if let chat = ChatModel.shared.getChat(chatId) {
await MainActor.run { self.isLoading = true }
// try? await Task.sleep(nanoseconds: 5000_000000)
await loadChat(chat: chat)
navigationTimeout.cancel()
progressTimeout.cancel()
await MainActor.run {
self.isLoading = false
self.showLoadingProgress = false
willNavigate()
ChatModel.shared.chatId = chatId
}
await MainActor.run { self.isLoading = true }
// try? await Task.sleep(nanoseconds: 5000_000000)
await loadChat(chatId: chatId)
navigationTimeout.cancel()
progressTimeout.cancel()
await MainActor.run {
self.isLoading = false
self.showLoadingProgress = false
willNavigate()
// ChatModel.shared.chatId = id
}
}
}
@ -546,6 +548,7 @@ final class ChatModel: ObservableObject {
ci.meta.itemStatus = status
}
im.reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0)
im.chatItemsChangesListener.added((ci.id, ci.isRcvNew), hasLiveDummy ? 1 : 0)
im.itemAdded = true
ChatItemDummyModel.shared.sendUpdate()
return true
@ -591,8 +594,9 @@ final class ChatModel: ObservableObject {
// remove from current chat
if chatId == cInfo.id {
if let i = getChatItemIndex(cItem) {
_ = withAnimation {
im.reversedChatItems.remove(at: i)
withAnimation {
let item = im.reversedChatItems.remove(at: i)
im.chatItemsChangesListener.removed([(item.id, i, item.isRcvNew)], im.reversedChatItems.reversed())
}
}
}
@ -641,6 +645,7 @@ final class ChatModel: ObservableObject {
let cItem = ChatItem.liveDummy(chatInfo.chatType)
withAnimation {
im.reversedChatItems.insert(cItem, at: 0)
im.chatItemsChangesListener.added((cItem.id, cItem.isRcvNew), 0)
im.itemAdded = true
}
return cItem
@ -664,7 +669,6 @@ final class ChatModel: ObservableObject {
// update preview
_updateChat(cInfo.id) { chat in
self.decreaseUnreadCounter(user: self.currentUser!, chat: chat)
self.updateFloatingButtons(unreadCount: 0)
ChatTagsModel.shared.markChatTagRead(chat)
chat.chatStats = ChatStats()
}
@ -682,12 +686,6 @@ final class ChatModel: ObservableObject {
}
}
private func updateFloatingButtons(unreadCount: Int) {
let fbm = ChatView.FloatingButtonModel.shared
fbm.totalUnread = unreadCount
fbm.objectWillChange.send()
}
func markChatItemsRead(_ cInfo: ChatInfo, aboveItem: ChatItem? = nil) {
if let cItem = aboveItem {
if chatId == cInfo.id, let i = getChatItemIndex(cItem) {
@ -716,7 +714,6 @@ final class ChatModel: ObservableObject {
ChatTagsModel.shared.updateChatTagRead(chat, wasUnread: wasUnread)
let by = chat.chatInfo.chatSettings?.enableNtfs == .mentions ? markedMentionsCount : markedCount
self.decreaseUnreadCounter(user: self.currentUser!, by: by)
self.updateFloatingButtons(unreadCount: chat.chatStats.unreadCount)
}
}
}
@ -746,16 +743,22 @@ final class ChatModel: ObservableObject {
if chatId == cInfo.id {
chatItemStatuses = [:]
im.reversedChatItems = []
im.chatItemsChangesListener.cleared()
}
}
func markChatItemsRead(_ cInfo: ChatInfo, _ itemIds: [ChatItem.ID], _ mentionsRead: Int) {
if self.chatId == cInfo.id {
var unreadItemIds: Set<ChatItem.ID> = []
for itemId in itemIds {
if let i = im.reversedChatItems.firstIndex(where: { $0.id == itemId }) {
if im.reversedChatItems[i].isRcvNew {
unreadItemIds.insert(itemId)
}
markChatItemRead_(i)
}
}
im.chatItemsChangesListener.read(unreadItemIds, im.reversedChatItems.reversed())
}
self.unreadCollector.changeUnreadCounter(cInfo.id, by: -itemIds.count, unreadMentions: -mentionsRead)
}
@ -783,9 +786,6 @@ final class ChatModel: ObservableObject {
}
func changeUnreadCounter(_ chatId: ChatId, by count: Int, unreadMentions: Int) {
if chatId == ChatModel.shared.chatId {
ChatView.FloatingButtonModel.shared.totalUnread += count
}
let (unread, mentions) = self.unreadCounts[chatId] ?? (0, 0)
self.unreadCounts[chatId] = (unread + count, mentions + unreadMentions)
subject.send()
@ -979,12 +979,17 @@ final class ChatModel: ObservableObject {
// returns the previous member in the same merge group and the count of members in this group
func getPrevHiddenMember(_ member: GroupMember, _ range: ClosedRange<Int>) -> (GroupMember?, Int) {
let items = im.reversedChatItems
var prevMember: GroupMember? = nil
var memberIds: Set<Int64> = []
for i in range {
if case let .groupRcv(m) = im.reversedChatItems[i].chatDir {
if prevMember == nil && m.groupMemberId != member.groupMemberId { prevMember = m }
memberIds.insert(m.groupMemberId)
if i < items.count {
if case let .groupRcv(m) = items[i].chatDir {
if prevMember == nil && m.groupMemberId != member.groupMemberId { prevMember = m }
memberIds.insert(m.groupMemberId)
}
} else {
logger.error("getPrevHiddenMember: index >= count of reversed items: \(i) vs \(items.count)")
}
}
return (prevMember, memberIds.count)