mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
Display/ListView: snap-scroll to pinned item + synchronous history transactions
Adds an experimentalSnapScrollToPinnedItem path that resnaps scroll to the first pinToEdgeWithInset item after inset/visible-size updates and on empty itemNodes. Promotes ListViewItem to AnyObject and adds a pinToEdgeWithInset protocol requirement with a default of false. Also runs chat history transactions synchronously when updating scroll around correlated messages, and tightens beganInteractiveDragging weak- self handling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
684796144c
commit
791bb62148
3 changed files with 41 additions and 12 deletions
|
|
@ -212,6 +212,7 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur
|
|||
|
||||
public final var rotated = false
|
||||
public final var experimentalSnapScrollToItem = false
|
||||
public final var experimentalSnapScrollToPinnedItem = false
|
||||
public final var useMainQueueTransactions = false
|
||||
|
||||
public final var scrollEnabled: Bool = true {
|
||||
|
|
@ -863,6 +864,7 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur
|
|||
self.snapToBottomInsetUntilFirstInteraction = false
|
||||
}
|
||||
self.scrolledToItem = nil
|
||||
self.experimentalSnapScrollToPinnedItem = false
|
||||
|
||||
self.scroller.forceDecelerating = false
|
||||
self.isDragging = true
|
||||
|
|
@ -2670,6 +2672,9 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur
|
|||
if self.experimentalSnapScrollToItem {
|
||||
self.scrolledToItem = (originalScrollToItem.index, originalScrollToItem.position)
|
||||
}
|
||||
if self.items[originalScrollToItem.index].pinToEdgeWithInset {
|
||||
self.experimentalSnapScrollToPinnedItem = true
|
||||
}
|
||||
} else if let scrolledToItem = self.scrolledToItem, self.experimentalSnapScrollToItem {
|
||||
var curve: ListViewAnimationCurve = .Default(duration: nil)
|
||||
var animated = false
|
||||
|
|
@ -2679,6 +2684,23 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur
|
|||
}
|
||||
scrollToItem = ListViewScrollToItem(index: scrolledToItem.0, position: scrolledToItem.1, animated: animated, curve: curve, directionHint: .Down)
|
||||
isExperimentalSnapToScrollToItem = true
|
||||
} else if self.experimentalSnapScrollToPinnedItem {
|
||||
if let index = self.items.firstIndex(where: { $0.pinToEdgeWithInset }) {
|
||||
isExperimentalSnapToScrollToItem = true
|
||||
var curve: ListViewAnimationCurve = .Default(duration: nil)
|
||||
var animated = false
|
||||
if let updateSizeAndInsets = updateSizeAndInsets {
|
||||
curve = updateSizeAndInsets.curve
|
||||
animated = !updateSizeAndInsets.duration.isZero
|
||||
}
|
||||
scrollToItem = ListViewScrollToItem(index: index, position: self.rotated ? .bottom(0.0) : .top(0.0), animated: animated, curve: curve, directionHint: .Down)
|
||||
}
|
||||
}
|
||||
|
||||
if scrollToItem == nil {
|
||||
if self.itemNodes.isEmpty, self.items.contains(where: { $0.pinToEdgeWithInset }) {
|
||||
scrollToItem = ListViewScrollToItem(index: 0, position: self.rotated ? .bottom(0.0) : .top(0.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down)
|
||||
}
|
||||
}
|
||||
|
||||
weak var highlightedItemNode: ListViewItemNode?
|
||||
|
|
@ -4851,7 +4873,7 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur
|
|||
}
|
||||
|
||||
if requestUpdateVisibleItems {
|
||||
self.enqueueUpdateVisibleItems(synchronous: false)
|
||||
self.enqueueUpdateVisibleItems(synchronous: self.experimentalSnapScrollToPinnedItem)
|
||||
}
|
||||
|
||||
if scrollingForReorder {
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ public final class ListViewItemApply {
|
|||
}
|
||||
}
|
||||
|
||||
public protocol ListViewItem {
|
||||
public protocol ListViewItem: AnyObject {
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void)
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void)
|
||||
|
||||
|
|
@ -77,6 +77,7 @@ public protocol ListViewItem {
|
|||
var headerAccessoryItem: ListViewAccessoryItem? { get }
|
||||
var selectable: Bool { get }
|
||||
var approximateHeight: CGFloat { get }
|
||||
var pinToEdgeWithInset: Bool { get }
|
||||
|
||||
func selected(listView: ListView)
|
||||
}
|
||||
|
|
@ -98,6 +99,10 @@ public extension ListViewItem {
|
|||
return 44.0
|
||||
}
|
||||
|
||||
var pinToEdgeWithInset: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func selected(listView: ListView) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1171,10 +1171,12 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH
|
|||
}).startStrict()
|
||||
|
||||
self.beganInteractiveDragging = { [weak self] _ in
|
||||
self?.isInteractivelyScrollingValue = true
|
||||
self?.isInteractivelyScrollingPromise.set(true)
|
||||
self?.beganDragging?()
|
||||
//self?.updateHistoryScrollingArea(transition: .immediate)
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.isInteractivelyScrollingValue = true
|
||||
self.isInteractivelyScrollingPromise.set(true)
|
||||
self.beganDragging?()
|
||||
}
|
||||
|
||||
self.endedInteractiveDragging = { [weak self] _ in
|
||||
|
|
@ -4349,16 +4351,16 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH
|
|||
if foundCorrelationMessage {
|
||||
self.layoutActionOnViewTransition = nil
|
||||
let (mappedTransition, updateSizeAndInsets) = layoutActionOnViewTransition(transition)
|
||||
self.transaction(deleteIndices: mappedTransition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: mappedTransition.options, scrollToItem: mappedTransition.scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: mappedTransition.stationaryItemRange, updateOpaqueState: ChatHistoryTransactionOpaqueState(historyView: transition.historyView), completion: { result in
|
||||
self.transaction(deleteIndices: mappedTransition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: mappedTransition.options.union(.Synchronous), scrollToItem: mappedTransition.scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: mappedTransition.stationaryItemRange, updateOpaqueState: ChatHistoryTransactionOpaqueState(historyView: transition.historyView), completion: { result in
|
||||
completion(true, result)
|
||||
})
|
||||
} else {
|
||||
self.transaction(deleteIndices: transition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, updateOpaqueState: ChatHistoryTransactionOpaqueState(historyView: transition.historyView), completion: { result in
|
||||
self.transaction(deleteIndices: transition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: transition.options.union(.Synchronous), scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, updateOpaqueState: ChatHistoryTransactionOpaqueState(historyView: transition.historyView), completion: { result in
|
||||
completion(false, result)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
self.transaction(deleteIndices: transition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, updateOpaqueState: ChatHistoryTransactionOpaqueState(historyView: transition.historyView), completion: { result in
|
||||
self.transaction(deleteIndices: transition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: transition.options.union(.Synchronous), scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, updateOpaqueState: ChatHistoryTransactionOpaqueState(historyView: transition.historyView), completion: { result in
|
||||
completion(false, result)
|
||||
})
|
||||
}
|
||||
|
|
@ -4740,7 +4742,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH
|
|||
scrollToItem = ListViewScrollToItem(index: index, position: .center(.top), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true)
|
||||
}
|
||||
|
||||
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: scrollToItem, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion, .Synchronous], scrollToItem: scrollToItem, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
break loop
|
||||
}
|
||||
case let .MessageGroupEntry(_, messages, presentationData):
|
||||
|
|
@ -4761,7 +4763,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH
|
|||
scrollToItem = ListViewScrollToItem(index: index, position: .center(.top), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true)
|
||||
}
|
||||
|
||||
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: scrollToItem, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion, .Synchronous], scrollToItem: scrollToItem, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
break loop
|
||||
}
|
||||
default:
|
||||
|
|
@ -4812,7 +4814,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH
|
|||
item = ListMessageItem(presentationData: presentationData, systemStyle: self.systemStyle, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch)
|
||||
}
|
||||
let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil)
|
||||
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion, .Synchronous], scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
break loop
|
||||
}
|
||||
default:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue