mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
ListView: cap pin-to-edge item visible portion at half area
When a pinToEdgeWithInset item is taller than half of the visible area (visibleSize.height - insets.top - insets.bottom), cap its visible portion at halfArea. The remaining height extends past visibleSize - insets.bottom into the bottom-inset region (occluded by overlay UI like the chat input panel). A new private helper `pinToEdgeBottomExtension(forPinnedHeight:)` returns max(0, pinnedHeight - halfArea). Three sites consume it: - calculatePinToEdgeTopInset caps the pinned item's contribution to totalAboveAndPinned via `pinnedHeight - extension`. - replayOperations isPinToEdgeTarget anchors the pinned item's apparent maxY at visibleSize - insets.bottom + extension. - isStrictlyScrolledToPinToEdgeItem matches the new anchor. Both isPinToEdgeTarget and isStrictlyScrolledToPinToEdgeItem fire when either calculatePinToEdgeTopInset() > 0 OR extension > 0, so the cap also applies when items above the pinned item overflow the visible area (in which case calculatePinToEdgeTopInset returns 0 but extension is still positive). When the pinned item fits within halfArea, extension == 0 and behavior is identical to before this change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f25d0776c7
commit
999aac6eb3
3 changed files with 32 additions and 402 deletions
|
|
@ -1101,6 +1101,8 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur
|
|||
}
|
||||
guard lowestPinnedIndex != Int.max else { return 0.0 }
|
||||
|
||||
let visibleArea = self.visibleSize.height - self.insets.top - self.insets.bottom
|
||||
|
||||
var totalAboveAndPinned: CGFloat = 0.0
|
||||
var sawIndexZero = false
|
||||
for itemNode in self.itemNodes {
|
||||
|
|
@ -1108,16 +1110,25 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur
|
|||
if index == 0 {
|
||||
sawIndexZero = true
|
||||
}
|
||||
if index <= lowestPinnedIndex {
|
||||
if index < lowestPinnedIndex {
|
||||
totalAboveAndPinned += itemNode.apparentBounds.height
|
||||
} else if index == lowestPinnedIndex {
|
||||
let pinnedHeight = itemNode.apparentBounds.height
|
||||
totalAboveAndPinned += pinnedHeight - self.pinToEdgeBottomExtension(forPinnedHeight: pinnedHeight)
|
||||
}
|
||||
}
|
||||
guard sawIndexZero else { return 0.0 }
|
||||
|
||||
let visibleArea = self.visibleSize.height - self.insets.top - self.insets.bottom
|
||||
return max(0.0, visibleArea - totalAboveAndPinned)
|
||||
}
|
||||
|
||||
private func pinToEdgeBottomExtension(forPinnedHeight pinnedHeight: CGFloat) -> CGFloat {
|
||||
let visibleArea = self.visibleSize.height - self.insets.top - self.insets.bottom
|
||||
let halfArea = visibleArea * 0.5
|
||||
guard halfArea > 0.0 else { return 0.0 }
|
||||
return max(0.0, pinnedHeight - halfArea)
|
||||
}
|
||||
|
||||
private func areAllItemsOnScreen() -> Bool {
|
||||
if self.itemNodes.count == 0 {
|
||||
return true
|
||||
|
|
@ -2672,15 +2683,17 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur
|
|||
}
|
||||
|
||||
public func isStrictlyScrolledToPinToEdgeItem() -> Bool {
|
||||
if self.calculatePinToEdgeTopInset() <= 0.0 {
|
||||
return false
|
||||
}
|
||||
guard let targetIndex = self.items.firstIndex(where: { $0.pinToEdgeWithInset }) else {
|
||||
return false
|
||||
}
|
||||
let pinToEdgeTopInset = self.calculatePinToEdgeTopInset()
|
||||
for itemNode in self.itemNodes {
|
||||
if itemNode.index == targetIndex {
|
||||
let expectedMaxY = (self.visibleSize.height - self.insets.bottom) + itemNode.scrollPositioningInsets.bottom
|
||||
let extensionOffset = self.pinToEdgeBottomExtension(forPinnedHeight: itemNode.apparentBounds.height)
|
||||
guard pinToEdgeTopInset > 0.0 || extensionOffset > 0.0 else {
|
||||
return false
|
||||
}
|
||||
let expectedMaxY = (self.visibleSize.height - self.insets.bottom + extensionOffset) + itemNode.scrollPositioningInsets.bottom
|
||||
return abs(itemNode.apparentFrame.maxY - expectedMaxY) < 0.5
|
||||
}
|
||||
}
|
||||
|
|
@ -3108,23 +3121,25 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur
|
|||
let insets = self.insets// updateSizeAndInsets?.insets ?? self.insets
|
||||
|
||||
var isPinToEdgeTarget = false
|
||||
if self.calculatePinToEdgeTopInset() > 0.0,
|
||||
index >= 0, index < self.items.count,
|
||||
self.items[index].pinToEdgeWithInset {
|
||||
isPinToEdgeTarget = true
|
||||
for otherNode in self.itemNodes {
|
||||
guard let otherIndex = otherNode.index else { continue }
|
||||
guard otherIndex >= 0, otherIndex < self.items.count else { continue }
|
||||
if otherIndex < index, self.items[otherIndex].pinToEdgeWithInset {
|
||||
isPinToEdgeTarget = false
|
||||
break
|
||||
if index >= 0, index < self.items.count, self.items[index].pinToEdgeWithInset {
|
||||
let pinExtension = self.pinToEdgeBottomExtension(forPinnedHeight: itemNode.apparentBounds.height)
|
||||
if self.calculatePinToEdgeTopInset() > 0.0 || pinExtension > 0.0 {
|
||||
isPinToEdgeTarget = true
|
||||
for otherNode in self.itemNodes {
|
||||
guard let otherIndex = otherNode.index else { continue }
|
||||
guard otherIndex >= 0, otherIndex < self.items.count else { continue }
|
||||
if otherIndex < index, self.items[otherIndex].pinToEdgeWithInset {
|
||||
isPinToEdgeTarget = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var offset: CGFloat
|
||||
if isPinToEdgeTarget {
|
||||
offset = (self.visibleSize.height - insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom
|
||||
let extensionOffset = self.pinToEdgeBottomExtension(forPinnedHeight: itemNode.apparentBounds.height)
|
||||
offset = (self.visibleSize.height - insets.bottom + extensionOffset) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom
|
||||
} else {
|
||||
switch scrollToItem.position {
|
||||
case let .bottom(additionalOffset):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue