From a4a268310ba4b1f17f6d52f1d8d3ac15d47c3635 Mon Sep 17 00:00:00 2001 From: Avently <7953703+avently@users.noreply.github.com> Date: Fri, 20 Dec 2024 06:55:34 -0800 Subject: [PATCH] debug --- .../Shared/Views/Chat/ChatScrollHelpers.swift | 27 ++++++ apps/ios/Shared/Views/Chat/ChatView.swift | 7 ++ apps/ios/Shared/Views/Chat/ReverseList.swift | 93 ++++++++++++++++--- apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 + 4 files changed, 120 insertions(+), 11 deletions(-) create mode 100644 apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift diff --git a/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift b/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift new file mode 100644 index 0000000000..2d013ec3a8 --- /dev/null +++ b/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift @@ -0,0 +1,27 @@ +// +// ChatScrollHelpers.swift +// SimpleX (iOS) +// +// Created by me on 20.12.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +func loadLastItems(_ loadingMoreItems: Binding, _ chatInfo: ChatInfo) { + //return () + Task { + try? await Task.sleep(nanoseconds: 2500_000000) + if ItemsModel.shared.chatState.totalAfter == 0 || ChatModel.shared.chatId != chatInfo.id { + return + } + await MainActor.run { + loadingMoreItems.wrappedValue = true + } + await apiLoadMessages(chatInfo.chatType, chatInfo.apiId, ChatPagination.last(count: 50), ItemsModel.shared.chatState) + await MainActor.run { + loadingMoreItems.wrappedValue = false + } + } +} diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 5796eb980a..fa3f50b082 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -465,12 +465,14 @@ struct ChatView: View { mergedItems = MergedItems.create(ItemsModel.shared.reversedChatItems, chat.chatStats.unreadCount, revealedItems, ItemsModel.shared.chatState) } ) + .environmentObject(theme) // crashes without this line when scrolling to the first unread in ReverseList .id(ci.id) // Required to trigger `onAppear` on iOS15 } loadItems: { pagination, visibleItemIndexesNonReversed in loadChatItems(cInfo, pagination, visibleItemIndexesNonReversed) } .onAppear { mergedItems = MergedItems.create(im.reversedChatItems, chat.chatStats.unreadCount, revealedItems, ItemsModel.shared.chatState) + loadLastItems($loadingItems, chat.chatInfo) } .onChange(of: im.reversedChatItems) { items in mergedItems = MergedItems.create(items, chat.chatStats.unreadCount, revealedItems, ItemsModel.shared.chatState) @@ -481,6 +483,9 @@ struct ChatView: View { .onChange(of: chat.chatStats.unreadCount) { unreadCount in mergedItems = MergedItems.create(im.reversedChatItems, unreadCount, revealedItems, ItemsModel.shared.chatState) } + .onChange(of: chat.id) { _ in + loadLastItems($loadingItems, chat.chatInfo) + } .opacity(ItemsModel.shared.isLoading ? 0 : 1) .padding(.vertical, -InvertedTableView.inset) .onTapGesture { hideKeyboard() } @@ -1032,6 +1037,8 @@ struct ChatView: View { } } .onAppear { + // LALAL + return () if markedRead { return } else { diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift index 9cb07d8f3b..54eda91daf 100644 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ b/apps/ios/Shared/Views/Chat/ReverseList.swift @@ -60,6 +60,8 @@ struct ReverseList: UIViewControllerRepresentable { private let updateFloatingButtons = PassthroughSubject() private var bag = Set() + private var scrollToRowOnAppear = 0 + init(representer: ReverseList) { self.representer = representer super.init(style: .plain) @@ -156,6 +158,31 @@ struct ReverseList: UIViewControllerRepresentable { NotificationCenter.default.post(name: .chatViewWillBeginScrolling, object: nil) } + /// depending on tableView layout phase conditions, it can already have known size or not. Not possible to correctly scroll to required + /// item if the size is unknown + func scrollToRowWhenKnowSize(_ row: Int) { + // logger.debug("LALAL WILL SCROLL TO \(self.scrollToRowOnAppear)") + if row > 0 && tableView.visibleSize.height > 0 { + // logger.debug("LALAL OFFSET before \(self.tableView.contentOffset.y), visible \(self.tableView.visibleSize.height) \(self.tableView.frame.height) \(self.view.frame.height) \(self.tableView.indexPathsForVisibleRows!)") + //tableView.setContentOffset(CGPointMake(0, tableView.contentOffset.y - tableView.visibleSize.height), animated: false) + tableView.scrollToRow(at: IndexPath(item: min(tableView.numberOfRows(inSection: 0) - 1, row), section: 0), at: .bottom, animated: false) + // Without this small scroll position is not correct pixel-to-pixel. + // Only needed when viewDidAppear has not been called yet because in there clipsToBounds is applied + if tableView.clipsToBounds { + tableView.setContentOffset(CGPointMake(0, tableView.contentOffset.y - 5), animated: false) + } + // logger.debug("LALAL OFFSET after \(self.tableView.contentOffset.y) \(self.tableView.indexPathsForVisibleRows!)") + scrollToRowOnAppear = 0 + } else { + scrollToRowOnAppear = row + } + } + + override public func viewIsAppearing(_ animated: Bool) { + super.viewIsAppearing(animated) + scrollToRowWhenKnowSize(scrollToRowOnAppear) + } + override func viewDidAppear(_ animated: Bool) { tableView.clipsToBounds = false parent?.viewIfLoaded?.clipsToBounds = false @@ -196,23 +223,67 @@ struct ReverseList: UIViewControllerRepresentable { } func update(items: [ChatItem]) { - let wasCount = dataSource.snapshot().numberOfItems + var prevSnapshot = dataSource.snapshot() + let wasCount = prevSnapshot.numberOfItems + let insertedOneNewestItem = wasCount != 0 && representer.mergedItems.items.count - wasCount == 1 && prevSnapshot.itemIdentifiers.first! == self.representer.mergedItems.items[1] logger.debug("LALAL WAS \(wasCount) will be \(self.representer.mergedItems.items.count)") //self.representer.mergedItems = MergedItems.create(items, representer.unreadCount, representer.revealedItems, ItemsModel.shared.chatState) - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.main]) - snapshot.appendItems(representer.mergedItems.items) + let snapshot: NSDiffableDataSourceSnapshot + if insertedOneNewestItem { + prevSnapshot.insertItems([representer.mergedItems.items.first!], beforeItem: prevSnapshot.itemIdentifiers.first!) + snapshot = prevSnapshot + } else { + var snap = NSDiffableDataSourceSnapshot() + snap.appendSections([.main]) + snap.appendItems(representer.mergedItems.items) + + if snap.itemIdentifiers == prevSnapshot.itemIdentifiers { + logger.debug("LALAL SAME ITEMS, not rebuilding the tableview") + return + } + + snapshot = snap + } dataSource.defaultRowAnimation = .none - dataSource.apply( - snapshot, - animatingDifferences: wasCount != 0 && abs(representer.mergedItems.items.count - wasCount) == 1 - ) + let wasContentHeight = tableView.contentSize.height + let wasOffset = tableView.contentOffset.y + let countDiff = max(0, snapshot.numberOfItems - prevSnapshot.numberOfItems) // Sets content offset on initial load if wasCount == 0 { - tableView.setContentOffset( - CGPoint(x: 0, y: -InvertedTableView.inset), - animated: false + dataSource.apply( + snapshot, + animatingDifferences: insertedOneNewestItem ) + if let firstUnreadItem = snapshot.itemIdentifiers.lastIndex(where: { merged in merged.hasUnread() }) { + scrollToRowWhenKnowSize(firstUnreadItem) + } else { + tableView.setContentOffset( + CGPoint(x: 0, y: -InvertedTableView.inset), + animated: false + ) + } + } else if wasCount != snapshot.numberOfItems { + tableView.beginUpdates() + dataSource.apply( + snapshot, + animatingDifferences: false + ) +// tableView.scrollToRow( +// at: IndexPath(row: countDiff, section: 0), +// at: .top, +// animated: false +// ) + tableView.endUpdates() +// if snapshot.itemIdentifiers[0].newest().item.id == prevSnapshot.itemIdentifiers[0].newest().item.id { +// // added new items to top +// } else { +// // added new items to bottom +// logger.debug("LALAL WAS HEIGHT \(wasContentHeight) now \(self.tableView.contentSize.height), offset was \(wasOffset), now \(self.tableView.contentOffset.y), will be \(self.tableView.contentOffset.y + (self.tableView.contentSize.height - wasContentHeight))") +// DispatchQueue.main.asyncAfter(deadline: .now() + 1) { +// self.tableView.setContentOffset(CGPointMake(0, self.tableView.contentOffset.y + (self.tableView.contentSize.height - wasContentHeight)), animated: false) +// } +// } + } updateFloatingButtons.send() } diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index e8404497f8..9f23639174 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -198,6 +198,7 @@ 8C8118722C220B5B00E6FC94 /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = 8C8118712C220B5B00E6FC94 /* Yams */; }; 8C81482C2BD91CD4002CBEC3 /* AudioDevicePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C81482B2BD91CD4002CBEC3 /* AudioDevicePicker.swift */; }; 8C9BC2652C240D5200875A27 /* ThemeModeEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */; }; + 8CAD466F2D15A8100078D18F /* ChatScrollHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CAD466E2D15A8100078D18F /* ChatScrollHelpers.swift */; }; 8CAEF1502D11A6A000240F00 /* ChatItemsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CAEF14F2D11A6A000240F00 /* ChatItemsLoader.swift */; }; 8CB15EA02CFDA30600C28209 /* ChatItemsMerger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CB15E9F2CFDA30600C28209 /* ChatItemsMerger.swift */; }; 8CB3476C2CF5CFFA006787A5 /* Ink in Frameworks */ = {isa = PBXBuildFile; productRef = 8CB3476B2CF5CFFA006787A5 /* Ink */; }; @@ -549,6 +550,7 @@ 8C852B072C1086D100BA61E8 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; }; 8C86EBE42C0DAE4F00E12243 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeModeEditor.swift; sourceTree = ""; }; + 8CAD466E2D15A8100078D18F /* ChatScrollHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatScrollHelpers.swift; sourceTree = ""; }; 8CAEF14F2D11A6A000240F00 /* ChatItemsLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemsLoader.swift; sourceTree = ""; }; 8CB15E9F2CFDA30600C28209 /* ChatItemsMerger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemsMerger.swift; sourceTree = ""; }; 8CB3476D2CF5F58B006787A5 /* ConditionsWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionsWebView.swift; sourceTree = ""; }; @@ -750,6 +752,7 @@ 8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */, 8CB15E9F2CFDA30600C28209 /* ChatItemsMerger.swift */, 8CAEF14F2D11A6A000240F00 /* ChatItemsLoader.swift */, + 8CAD466E2D15A8100078D18F /* ChatScrollHelpers.swift */, ); path = Chat; sourceTree = ""; @@ -1507,6 +1510,7 @@ 5C6BA667289BD954009B8ECC /* DismissSheets.swift in Sources */, 5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */, 6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */, + 8CAD466F2D15A8100078D18F /* ChatScrollHelpers.swift in Sources */, 644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */, 5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */, 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */,