This commit is contained in:
Avently
2025-01-21 18:23:40 -08:00
parent 2155d6c2ce
commit ed5ddc1884
4 changed files with 189 additions and 115 deletions
@@ -98,18 +98,19 @@ struct MergedItems {
// found item that is considered as a split
if let unclosedSplitIndex, let unclosedSplitIndexInParent {
// it was at least second split in the list
splitRanges.append(SplitRange(indexRangeInReversed: unclosedSplitIndex ... index - 1, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent - 1))
splitRanges.append(SplitRange(itemId: items[unclosedSplitIndex].id, indexRangeInReversed: unclosedSplitIndex ... index - 1, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent - 1))
}
unclosedSplitIndex = index
unclosedSplitIndexInParent = visibleItemIndexInParent
} else if index + 1 == items.count, let unclosedSplitIndex, let unclosedSplitIndexInParent {
// just one split for the whole list, there will be no more, it's the end
splitRanges.append(SplitRange(indexRangeInReversed: unclosedSplitIndex ... index, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent))
splitRanges.append(SplitRange(itemId: items[unclosedSplitIndex].id, indexRangeInReversed: unclosedSplitIndex ... index, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent))
}
indexInParentItems[item.id] = visibleItemIndexInParent
index += 1
}
logger.debug("LALAL STEP 7")
logger.debug("LALAL SPLITs IN MERGED \(String(describing: splitRanges)) in state \(chatState.splits)")
var snapshot = NSDiffableDataSourceSnapshot<ReverseListSection, MergedItem>()
snapshot.appendSections([ReverseListSection.main])
snapshot.appendItems(mergedItems)
@@ -224,6 +225,9 @@ enum MergedItem: Hashable, Equatable {
}
struct SplitRange {
// added in iOS only to check whether the state is synchronized.
// TODO: find a better way to synchronize MergedItems with ActiveChatState && reversedChatItems
let itemId: Int64
/** range of indexes inside reversedChatItems where the first element is the split (it's index is [indexRangeInReversed.first])
* so [0, 1, 2, -100-, 101] if the 3 is a split, SplitRange(indexRange = 3 .. 4) will be this SplitRange instance
* (3, 4 indexes of the splitRange with the split itself at index 3)
@@ -322,12 +326,14 @@ class BoxedValue<T: Hashable>: Equatable, Hashable {
extension ReverseList.Controller {
@MainActor
func visibleItemIndexesNonReversed(_ items: [MergedItem]) -> ClosedRange<Int> {
func visibleItemIndexesNonReversed(_ prevMergedItems: MergedItems) -> ClosedRange<Int> {
let zero = 0 ... 0
let items = prevMergedItems.items
let indexInParentItems = prevMergedItems.indexInParentItems
if items.isEmpty {
return zero
}
let listState = getListState(items) ?? ListState()
let listState = getListState() ?? ListState()
let newest = items.count > listState.firstVisibleItemIndex ? items[listState.firstVisibleItemIndex].startIndexInReversedItems : nil
let oldest = items.count > listState.firstVisibleItemIndex ? items[listState.lastVisibleItemIndex].lastIndexInReversed() : nil
guard let newest, let oldest else {
@@ -30,15 +30,16 @@ func loadLastItems(_ loadingMoreItems: Binding<Bool>, _ chatInfo: ChatInfo) {
}
}
func preloadItems(_ mergedItems: MergedItems, _ allowLoadMoreItems: Bool, _ listState: ListState, _ ignoreLoadingRequests: Binding<[Int64]>, _ loadItems: @escaping (ChatPagination) async -> Bool) async {
func preloadItems(_ mergedItems: MergedItems, _ allowLoadMoreItems: Bool, _ listState: ListState, _ ignoreLoadingRequests: Binding<Int64?>, _ loadItems: @escaping (ChatPagination) async -> Bool) async {
let allowLoad = allowLoadMoreItems || mergedItems.items.count == listState.lastVisibleItemIndex + 1
let remaining = ChatPagination.UNTIL_PRELOAD_COUNT
let firstVisibleIndex = listState.firstVisibleItemIndex
await preloadItemsBefore()
await preloadItemsAfter()
if !(await preloadItemsBefore()) {
await preloadItemsAfter()
}
func preloadItemsBefore() async {
func preloadItemsBefore() async -> Bool {
let splits = mergedItems.splits
let lastVisibleIndex = listState.lastVisibleItemIndex
var lastIndexToLoadFrom: Int? = findLastIndexToLoadFromInSplits(firstVisibleIndex, lastVisibleIndex, remaining, splits)
@@ -57,16 +58,17 @@ func preloadItems(_ mergedItems: MergedItems, _ allowLoadMoreItems: Bool, _ list
logger.debug("LALAL LOADFROMNIL")
loadFromItemId = nil
}
guard let loadFromItemId, !ignoreLoadingRequests.wrappedValue.contains(loadFromItemId) else {
return
guard let loadFromItemId, ignoreLoadingRequests.wrappedValue != loadFromItemId else {
return false
}
let sizeWas = items.count
let firstItemIdWas = items.first?.id
let triedToLoad = await loadItems(ChatPagination.before(chatItemId: loadFromItemId, count: ChatPagination.PRELOAD_COUNT))
logger.debug("LALAL PRELOAD BEFORE \(String(describing: splits)) \(firstVisibleIndex) \(sizeWas) \(triedToLoad) \(lastIndexToLoadFrom ?? -1)")
logger.debug("LALAL PRELOAD BEFORE \(String(describing: splits)) \(firstVisibleIndex) sizeWas \(sizeWas) now \(ItemsModel.shared.reversedChatItems.count) \(triedToLoad) \(lastIndexToLoadFrom ?? -1)")
if triedToLoad && sizeWas == ItemsModel.shared.reversedChatItems.count && firstItemIdWas == ItemsModel.shared.reversedChatItems.last?.id {
ignoreLoadingRequests.wrappedValue.append(loadFromItemId)
ignoreLoadingRequests.wrappedValue = loadFromItemId
}
return triedToLoad
}
func preloadItemsAfter() async {
@@ -78,6 +80,7 @@ func preloadItems(_ mergedItems: MergedItems, _ allowLoadMoreItems: Bool, _ list
if let split, split.indexRangeInParentItems.lowerBound + remaining > firstVisibleIndex {
let index = items.count - 1 - split.indexRangeInReversed.lowerBound
if index >= 0 {
logger.debug("LALAL SPLITS \(String(describing: splits)) \(index) \(split.indexRangeInParentItems.debugDescription) \(items.firstIndex(where: { item in item.id == ItemsModel.shared.chatState.splits.first! }) ?? -1) \(ItemsModel.shared.chatState.splits.first!) \(items[index].id) \(String(describing: items.map({ item in item.id })))")
let loadFromItemId = items[index].id
_ = await loadItems(ChatPagination.after(chatItemId: loadFromItemId, count: ChatPagination.PRELOAD_COUNT))
}
+3 -3
View File
@@ -47,7 +47,7 @@ struct ChatView: View {
@State private var showDeleteSelectedMessages: Bool = false
@State private var allowToDeleteSelectedMessagesForAll: Bool = false
@State private var allowLoadMoreItems: Bool = false
@State private var ignoreLoadingRequests: [Int64] = []
@State private var ignoreLoadingRequests: Int64? = nil
@State private var updateMergedItemsTask: Task<Void, Never>? = nil
@AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial
@@ -446,7 +446,7 @@ struct ChatView: View {
return GeometryReader { g in
let _ = logger.debug("LALAL RELOAD \(im.reversedChatItems.count)")
// LALAL CAN I CHANGE BINDING LIKE THIS IN ignoreLoadingRequests?
ReverseList(mergedItems: $mergedItems, revealedItems: $revealedItems, unreadCount: Binding.constant(chat.chatStats.unreadCount), scrollState: $scrollModel.state, loadingMoreItems: $loadingMoreItems, allowLoadMoreItems: $allowLoadMoreItems, ignoreLoadingRequests: searchValueIsEmpty ? $ignoreLoadingRequests : Binding.constant([])) { index, mergedItem in
ReverseList(mergedItems: $mergedItems, revealedItems: $revealedItems, unreadCount: Binding.constant(chat.chatStats.unreadCount), scrollState: $scrollModel.state, loadingMoreItems: $loadingMoreItems, allowLoadMoreItems: $allowLoadMoreItems, ignoreLoadingRequests: searchValueIsEmpty ? $ignoreLoadingRequests : Binding.constant(nil)) { index, mergedItem in
let ci = switch mergedItem {
case let .single(item, _, _): item.item
case let .grouped(items, _, _, _, _, _, _, _): items.boxedValue.last!.item
@@ -1099,7 +1099,7 @@ struct ChatView: View {
}
.onAppear {
// LALAL
//return ()
return ()
if markedRead {
return
+165 -100
View File
@@ -21,7 +21,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
@Binding var scrollState: ReverseListScrollModel.State
@Binding var loadingMoreItems: Bool
@Binding var allowLoadMoreItems: Bool
@Binding var ignoreLoadingRequests: [Int64]
@Binding var ignoreLoadingRequests: Int64?
/// Closure, that returns user interface for a given item
/// Index, merged item
@@ -51,47 +51,47 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
case let .row(row):
controller.scroll(to: row, position: .bottom)
case .bottom:
logger.debug("LALAL WANT TO SCROLL BOTTOM \(self.loadingMoreItems)")
//DispatchQueue.main.async {
controller.scroll(to: 0, position: .top)
logger.debug("LALAL FINISHED SCROLL BOTTOM0")
//}
//logger.debug("LALAL FINISHED SCROLL BOTTOM1")
controller.scroll(to: 0, position: .top)
}
} else {
// when tableView is dragging and new items are added, scroll position cannot be set correctly
// so it's better to just wait until dragging ends
if !controller.useSmoothScrolling && !mergedItems.splits.isEmpty && controller.tableView.isDragging/* && !controller.tableView.isDecelerating*/ {
if !controller.useSmoothScrolling && !mergedItems.splits.isEmpty && (controller.tableView.isDragging || controller.updatingInProgress)/* && !controller.tableView.isDecelerating*/ {
controller.runBlockOnEndDecelerating = {
controller.runBlockOnEndDecelerating = nil
Task {
let prevSnapshot = controller.dataSource.snapshot()
let mergedItems = $mergedItems.wrappedValue
let prevSnapshot = controller.prevSnapshot
if mergedItems.items == prevSnapshot.itemIdentifiers {
logger.debug("LALAL SAME ITEMS, not rebuilding the tableview")
// update counters because they are static, unbound to specific chat and will become outdated if a new empty chat was open after non-empty one with unread messages
controller.updateFloatingButtons.send()
return
}
//await MainActor.run {
controller.update(mergedItems.items, mergedItems.snapshot, prevSnapshot)
//}
logger.debug("LALAL SNAPSHOT SIZE0 \(prevSnapshot.numberOfItems)")
await MainActor.run {
controller.update(mergedItems)
}
}
}
} else {
controller.runBlockOnEndDecelerating = nil
let prevSnapshot = controller.dataSource.snapshot()
if prevSnapshot.numberOfItems == 0 {
controller.update(mergedItems.items, mergedItems.snapshot, prevSnapshot)
if controller.prevSnapshot.numberOfItems == 0 {
controller.update(mergedItems)
} else {
Task {
if mergedItems.items == prevSnapshot.itemIdentifiers {
let mergedItems = $mergedItems.wrappedValue
if mergedItems.items == controller.prevSnapshot.itemIdentifiers {
logger.debug("LALAL SAME ITEMS, not rebuilding the tableview")
// update counters because they are static, unbound to specific chat and will become outdated if a new empty chat was open after non-empty one with unread messages
controller.updateFloatingButtons.send()
return
}
//await MainActor.run {
controller.update(mergedItems.items, mergedItems.snapshot, prevSnapshot)
//}
await MainActor.run {
let prevSnapshot = controller.prevSnapshot
logger.debug("LALAL SNAPSHOT SIZE1 \(prevSnapshot.numberOfItems), new \(mergedItems.snapshot.numberOfItems)")
controller.update(mergedItems)
}
}
}
}
@@ -102,6 +102,10 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
public class Controller: UITableViewController {
var representer: ReverseList
var dataSource: UITableViewDiffableDataSource<ReverseListSection, MergedItem>!
//
var prevSnapshot: NSDiffableDataSourceSnapshot<ReverseListSection, MergedItem> = NSDiffableDataSourceSnapshot()
var prevMergedItems: MergedItems = MergedItems(items: [], splits: [], indexInParentItems: [:], snapshot: NSDiffableDataSourceSnapshot())
let updateFloatingButtons = PassthroughSubject<Void, Never>()
private var bag = Set<AnyCancellable>()
@@ -119,6 +123,8 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
let useSmoothScrolling = false
var preloading = false
var updatingInProgress = false
init(representer: ReverseList) {
self.representer = representer
super.init(style: .plain)
@@ -178,9 +184,8 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
.throttle(for: 0.2, scheduler: DispatchQueue.global(qos: .background), latest: true)
.sink {
let items = representer.mergedItems.items
let listState = DispatchQueue.main.sync(execute: { [weak self] in self?.getListState(items) }) ?? ListState()
let listState = DispatchQueue.main.sync(execute: { [weak self] in self?.getListState() }) ?? ListState()
ChatView.FloatingButtonModel.shared.updateOnListChange(items, listState)
//self.preloadIfNeeded(listState)
}
.store(in: &bag)
}
@@ -213,8 +218,10 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
}
override public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
runBlockOnEndDecelerating?()
runBlockOnEndDecelerating = nil
if !updatingInProgress {
runBlockOnEndDecelerating?()
runBlockOnEndDecelerating = nil
}
}
/// depending on tableView layout phase conditions, it can already have known size or not. Not possible to correctly scroll to required
@@ -273,6 +280,9 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
// in case of listener will not be called
await MainActor.run {
representer.scrollState = .atDestination
if let listState = self.getListState() {
self.preloadIfNeeded(listState)
}
}
} catch {}
}
@@ -280,16 +290,24 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
task.cancel()
self.representer.scrollState = .atDestination
self.runBlockOnEndScrolling = nil
if let listState = self.getListState() {
self.preloadIfNeeded(listState)
}
}
}
func update(
_ items: [MergedItem],
_ snapshot: NSDiffableDataSourceSnapshot<ReverseListSection, MergedItem>,
_ prevSnapshot: NSDiffableDataSourceSnapshot<ReverseListSection, MergedItem>
) {
func update(_ mergedItems: MergedItems) {
if updatingInProgress {
return
}
updatingInProgress = true
tableView.panGestureRecognizer.isEnabled = false
let items: [MergedItem] = mergedItems.items
let indexInParentItems = mergedItems.indexInParentItems
let snapshot = mergedItems.snapshot
logger.debug("LALAL STEP 1 \(items.count)")
let wasCount = prevSnapshot.numberOfItems
let wasCount = self.prevSnapshot.numberOfItems
let willBeCount = items.count
let c = 1
let insertedSeveralNewestItems = wasCount != 0 && willBeCount - wasCount == c && prevSnapshot.itemIdentifiers.first!.hashValue == items[c].hashValue
@@ -301,19 +319,15 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
logger.debug("LALAL STEP 3")
dataSource.defaultRowAnimation = .none
let wasContentHeight = tableView.contentSize.height
let wasOffset = tableView.contentOffset.y
let listState = getListState(items)
let wasFirstVisibleRow = listState?.firstVisibleItemIndex ?? 0//tableView.indexPathsForVisibleRows?.first?.row ?? 0
let wasFirstVisibleOffset = listState?.firstVisibleItemOffset ?? 0
// Sets content offset on initial load
logger.debug("LALAL STEP 4")
tableView.panGestureRecognizer.isEnabled = true
if wasCount == 0 {
dataSource.apply(
snapshot,
animatingDifferences: insertedSeveralNewestItems
)
prevSnapshot = snapshot
prevMergedItems = mergedItems
if let firstUnreadItem = snapshot.itemIdentifiers.lastIndex(where: { $0.hasUnread() }) {
scrollToRowWhenKnowSize(firstUnreadItem)
} else {
@@ -323,6 +337,8 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
)
}
logger.debug("LALAL STEP 5 0")
updatingInProgress = false
tableView.panGestureRecognizer.isEnabled = true
} else if wasCount != snapshot.numberOfItems {
logger.debug("LALAL drag \(self.tableView.isDragging), decel \(self.tableView.isDecelerating)")
if useSmoothScrolling && tableView.isDecelerating {
@@ -332,9 +348,12 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
snapshot,
animatingDifferences: false
)
prevSnapshot = snapshot
prevMergedItems = mergedItems
tableView.endUpdates()
tableView.panGestureRecognizer.isEnabled = true
logger.debug("LALAL STEP 5 1")
updatingInProgress = false
} else {
// remember current translation
var translationToApply: CGPoint? = nil
@@ -344,56 +363,91 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
translationToApply = t
}
}
let listState = getListState()
let wasFirstVisibleOffset = listState?.firstVisibleItemOffset ?? 0
let wasFirstIndex = listState?.firstVisibleItemIndex
let wasFirstId: Int64? = if let wasFirstIndex, prevSnapshot.itemIdentifiers.count > wasFirstIndex { prevSnapshot.itemIdentifiers[wasFirstIndex].newest().item.id
} else {
nil
}
let nowFirstIndex: Int? = if let wasFirstId { indexInParentItems[wasFirstId] } else { nil }
// let countDiff = if let wasFirstIndex, let nowFirstIndex {
// nowFirstIndex - wasFirstIndex
// } else {
// 0
// }
//if countDiff > 0 {
//self.stopScrolling()
//}
self.prevSnapshot = snapshot
self.prevMergedItems = mergedItems
dataSource.apply(
snapshot,
animatingDifferences: false
)
logger.debug("LALAL WAS LISTSTATE \(listState?.firstVisibleItemIndex ?? -3) now \(self.getListState(items)?.firstVisibleItemIndex ?? -4)")
let newListState = getListState(items)
if listState?.firstVisibleItemIndex == newListState?.firstVisibleItemIndex {
// added new items to top
} else {
stopScrolling()
// 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)), countDiff \(countDiff), wasVisibleRow \(wasFirstVisibleRow), wasFirstVisibleOffset \(wasFirstVisibleOffset)")
let countDiff = if let listState, let newListState {
newListState.firstVisibleItemIndex - listState.firstVisibleItemIndex
} else {
max(0, snapshot.numberOfItems - prevSnapshot.numberOfItems)
}
logger.debug("LALAL BEFORE SCROLLTOROW \(snapshot.numberOfItems - 1) \(countDiff) \(wasFirstVisibleRow) \(self.tableView.contentOffset.y) \(wasFirstVisibleOffset)")
getListState(items)
self.tableView.scrollToRow(
at: IndexPath(row: max(0, min(snapshot.numberOfItems - 1, countDiff + wasFirstVisibleRow)), section: 0),
at: .top,
animated: false
)
self.tableView.setContentOffset(
CGPoint(x: 0, y: self.tableView.contentOffset.y - wasFirstVisibleOffset),
animated: false
)
logger.debug("LALAL AFTER SCROLLTOROW")
let state = getListState(items)!
logger.debug("LALAL NOW FIRST VISIBLE \(state.firstVisibleItemIndex) \(state.firstVisibleItemOffset)")
// logger.debug("LALAL WAS0 \(self.prevSnapshot.itemIdentifiers.map({ item in item.newest().item.id}))")
// logger.debug("LALAL WAS1 \(snapshot.itemIdentifiers.map({ item in item.newest().item.id}))")
if let t = translationToApply {
// self.tableView.panGestureRecognizer.setTranslation(CGPointMake(t.x, t.y), in: self.tableView.superview!)
let o = self.tableView.contentOffset
// UIView.animate(withDuration: 0.5) {
// self.tableView.setContentOffset(CGPointMake(o.x + t.x, o.y + t.y), animated: false)
// self.tableView.layoutIfNeeded()
// }
self.tableView.setContentOffset(CGPointMake(o.x + t.x, o.y + t.y), animated: true)
// sometimes it moved position but there is no reason for it - dataset has same item indexes
let tableViewProducedDiff = if let wasFirst = listState?.firstVisibleItemIndex, let nowFirst = self.getListState()?.firstVisibleItemIndex {
0//wasFirst - nowFirst
} else {
0
}
let countDiff = if let wasFirstIndex, let nowFirstIndex {
nowFirstIndex - wasFirstIndex - tableViewProducedDiff
} else { 0 }
logger.debug("LALAL NEARSPLIT split lower WAS LISTSTATE \(listState?.firstVisibleItemIndex ?? -3) now \(self.getListState()?.firstVisibleItemIndex ?? -4) countDiff \(countDiff) (tableDiff \(tableViewProducedDiff)) wasFirstIndex \(wasFirstIndex ?? -5) nowIndex \(nowFirstIndex ?? -1) wasFirstId \(wasFirstId ?? -1) count \(self.prevSnapshot.itemIdentifiers.count)")
if countDiff <= 0 && tableViewProducedDiff == 0 {
// added new items to top, nothing to do, scrolling position is correct
} else {
logger.debug("LALAL NEARSPLIT split lower added on BOTTOM")
self.stopScrolling()
// 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)), countDiff \(countDiff), wasVisibleRow \(wasFirstVisibleRow), wasFirstVisibleOffset \(wasFirstVisibleOffset)")
logger.debug("LALAL BEFORE SCROLLTOROW \(snapshot.numberOfItems - 1) \(countDiff) \(wasFirstIndex ?? -5) \(self.tableView.contentOffset.y) \(wasFirstVisibleOffset) \(String(describing: self.representer.mergedItems.splits))")
self.getListState()
self.tableView.scrollToRow(
at: IndexPath(row: max(0, min(snapshot.numberOfItems - 1, countDiff + (wasFirstIndex ?? 0))), section: 0),
at: .top,
animated: false
)
self.tableView.setContentOffset(
CGPoint(x: 0, y: self.tableView.contentOffset.y - wasFirstVisibleOffset),
animated: false
)
logger.debug("LALAL AFTER SCROLLTOROW")
let state = self.getListState()!
logger.debug("LALAL NOW FIRST VISIBLE \(state.firstVisibleItemIndex) \(state.firstVisibleItemOffset)")
if let t = translationToApply {
// self.tableView.panGestureRecognizer.setTranslation(CGPointMake(t.x, t.y), in: self.tableView.superview!)
let o = self.tableView.contentOffset
// UIView.animate(withDuration: 0.5) {
// self.tableView.setContentOffset(CGPointMake(o.x + t.x, o.y + t.y), animated: false)
// self.tableView.layoutIfNeeded()
// }
self.tableView.setContentOffset(CGPointMake(o.x + t.x, o.y + t.y), animated: true)
}
}
logger.debug("LALAL STEP 5 2")
self.updatingInProgress = false
tableView.panGestureRecognizer.isEnabled = true
}
} else {
dataSource.apply(
snapshot,
animatingDifferences: false
)
prevSnapshot = snapshot
prevMergedItems = mergedItems
self.getListState()
logger.debug("LALAL STEP 5 3")
updatingInProgress = false
tableView.panGestureRecognizer.isEnabled = true
}
updateFloatingButtons.send()
logger.debug("LALAL STEP 6")
@@ -402,13 +456,24 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
updateFloatingButtons.send()
if let listState = self.getListState(representer.mergedItems.items) {
if let block = runBlockOnEndDecelerating, nearSplit(remaining: 40, listState, representer.mergedItems) {
tableView.panGestureRecognizer.isEnabled = false
block()
if representer.scrollState == .atDestination, let listState = self.getListState() {
if nearSplit(remaining: 30, ignoreTopOfTopSplit: true, listState, prevMergedItems) {
logger.debug("LALAL IN SPLIT OR NO YESSSSSS \(listState.firstVisibleItemIndex)..\(listState.lastVisibleItemIndex) \(String(describing: self.prevMergedItems.splits))")
stopScrolling(disable: false)
}
logger.debug("LALAL IN SPLIT OR NO \(self.nearSplit(remaining: 40, ignoreTopOfTopSplit: false, listState, self.prevMergedItems)) \(listState.firstVisibleItemIndex)..\(listState.lastVisibleItemIndex) \(String(describing: self.prevMergedItems.splits))")
if let block = runBlockOnEndDecelerating, nearSplit(remaining: 40, ignoreTopOfTopSplit: false, listState, prevMergedItems) {
if !updatingInProgress {
runBlockOnEndDecelerating = nil
// it's important to have it in DispatchQueue.main. Otherwise, it will be deadlock and jumping scroll
// without any visible reason
DispatchQueue.main.async {
block()
}
}
} else {
preloadIfNeeded(listState)
}
logger.debug("LALAL IN SPLIT OR NO \(self.nearSplit(remaining: 40, listState, self.representer.mergedItems)) \(String(describing: self.representer.mergedItems.splits))")
preloadIfNeeded(listState)
}
}
@@ -427,14 +492,15 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
}
}
func nearSplit(remaining: Int, _ listState: ListState, _ mergedItems: MergedItems) -> Bool {
if mergedItems.splits.isEmpty { return false }
func nearSplit(remaining: Int, ignoreTopOfTopSplit: Bool , _ listState: ListState, _ prevMergedItems: MergedItems) -> Bool {
if prevMergedItems.splits.isEmpty { return false }
let firstVisibleIndex = listState.firstVisibleItemIndex
let lastVisibleIndex = listState.lastVisibleItemIndex
for split in mergedItems.splits {
for split in prevMergedItems.splits {
// before any split
if split.indexRangeInParentItems.lowerBound > firstVisibleIndex {
logger.debug("LALAL NEARSPLIT split lower \(split.indexRangeInParentItems.lowerBound) last \(lastVisibleIndex) first \(firstVisibleIndex) remaining \(remaining)")
if split.indexRangeInParentItems.lowerBound > lastVisibleIndex {
if lastVisibleIndex > (split.indexRangeInParentItems.lowerBound - remaining) {
return true
}
@@ -444,7 +510,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
if containsInRange {
if lastVisibleIndex > (split.indexRangeInParentItems.upperBound - remaining) {
// situation when there is nothing to load from top
if self.representer.ignoreLoadingRequests.contains(where: { $0 == mergedItems.items.last?.oldest().item.id } ) {
if ignoreTopOfTopSplit && split.indexRangeInParentItems == prevMergedItems.splits.last?.indexRangeInParentItems {
return false
}
return true
@@ -458,8 +524,9 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
return false
}
func getListState(_ items: [MergedItem]) -> ListState? {
func getListState() -> ListState? {
logger.debug("LALAL VISIBLE ROWS \((self.tableView.indexPathsForVisibleRows ?? []).map({ $0.row }))")
let items = prevSnapshot.itemIdentifiers
if let visibleRows = tableView.indexPathsForVisibleRows,
visibleRows.last?.row ?? 0 < items.count {
let scrollOffset: Double = tableView.contentOffset.y + InvertedTableView.inset
@@ -469,19 +536,15 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
} else {
nil
}
let firstVisible = visibleRows.first(where: { isVisible(indexPath: $0) })
let firstVisibleOffset: CGFloat? = if let row = firstVisible?.row {
offsetForRow(row)
} else { nil }
let lastVisible = visibleRows.last(where: { isVisible(indexPath: $0) })
logger.debug("LALAL LAST \(lastVisible?.item ?? -2)")
let bottomItemId: ChatItem.ID? =
if let firstVisible {
items[firstVisible.row].newest().item.id
} else {
nil
guard let firstVisible = visibleRows.first(where: { isVisible(indexPath: $0) }),
let lastVisible = visibleRows.last(where: { isVisible(indexPath: $0) }) else {
return nil
}
return ListState(scrollOffset: scrollOffset, topItemDate: topItemDate, bottomItemId: bottomItemId, firstVisibleItemIndex: firstVisible?.row ?? 0, lastVisibleItemIndex: lastVisible?.row ?? 0, firstVisibleItemOffset: firstVisibleOffset ?? 0)
let firstVisibleOffset: CGFloat? = offsetForRow(firstVisible.row)
logger.debug("LALAL LAST \(lastVisible.item)")
let bottomItemId: ChatItem.ID = items[firstVisible.row].newest().item.id
return ListState(scrollOffset: scrollOffset, topItemDate: topItemDate, bottomItemId: bottomItemId, firstVisibleItemIndex: firstVisible.row, lastVisibleItemIndex: lastVisible.row, firstVisibleItemOffset: firstVisibleOffset ?? 0)
}
return nil
}
@@ -527,7 +590,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
if index == nil {
let pagination = ChatPagination.around(chatItemId: itemId, count: ChatPagination.PRELOAD_COUNT * 2)
let oldSize = ItemsModel.shared.reversedChatItems.count
_ = await self.representer.loadItems(true, pagination, { self.visibleItemIndexesNonReversed(self.representer.mergedItems.items)})
_ = await self.representer.loadItems(true, pagination, { self.visibleItemIndexesNonReversed(self.prevMergedItems)})
var repeatsLeft = 50
while oldSize == ItemsModel.shared.reversedChatItems.count && repeatsLeft > 0 {
try await Task.sleep(nanoseconds: 20_000000)
@@ -555,18 +618,20 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
}
func preloadIfNeeded(_ state: ListState) {
let mergedItems = self.representer.mergedItems
logger.debug("LALAL LOADING BEFORE ANYTHING \(state.firstVisibleItemIndex) \(self.prevSnapshot.itemIdentifiers[state.firstVisibleItemIndex].newest().item.id) \(self.representer.$mergedItems.wrappedValue.items[state.firstVisibleItemIndex].newest().item.id)")
if representer.scrollState == .atDestination, prevFirstVisible != state.firstVisibleItemIndex, !preloading {
logger.debug("LALAL LOADING before \(String(describing: self.representer.scrollState))")
logger.debug("LALAL LOADING before \(mergedItems.indexInParentItems.count) \(ItemsModel.shared.reversedChatItems.count)")
prevFirstVisible = state.firstVisibleItemIndex
preloading = true
Task {
preloading = true
defer {
preloading = false
}
let mergedItems = self.representer.mergedItems
logger.debug("LALAL LOADING BEFORE INSIDE \(state.firstVisibleItemIndex) \(self.prevSnapshot.itemIdentifiers[state.firstVisibleItemIndex].newest().item.id) \(mergedItems.items[state.firstVisibleItemIndex].newest().item.id) \(mergedItems.splits)")
await preloadItems(mergedItems, self.representer.allowLoadMoreItems, state, self.representer.$ignoreLoadingRequests) { pagination in
logger.debug("LALAL LOADING INSIDE")
let triedToLoad = await self.representer.loadItems(false, pagination, { self.visibleItemIndexesNonReversed(mergedItems.items) })
logger.debug("LALAL LOADING INSIDE \(mergedItems.items[state.firstVisibleItemIndex].newest().item.id) \(mergedItems.splits)")
let triedToLoad = await self.representer.loadItems(false, pagination, { self.visibleItemIndexesNonReversed(self.prevMergedItems) })
return triedToLoad
}
}