| ... | @@ -563,7 +563,7 @@ public final class T3ReviewDiffView: ExpoView, UIScrollViewDelegate { | ... | @@ -563,7 +563,7 @@ public final class T3ReviewDiffView: ExpoView, UIScrollViewDelegate { |
| 563 | ]) | 563 | ]) |
| 564 | } | 564 | } |
| 565 | | 565 | |
| 566 | // Source files fill the screen so UIKit can sample content below the glass bar. | 566 | // Code surfaces fill the screen so UIKit can sample content below the glass bar. |
| 567 | // Keep the same visible row when header or safe-area reservations change. | 567 | // Keep the same visible row when header or safe-area reservations change. |
| 568 | func setContentInsetTop(_ inset: CGFloat) { | 568 | func setContentInsetTop(_ inset: CGFloat) { |
| 569 | scrollView.automaticallyAdjustsScrollIndicatorInsets = false | 569 | scrollView.automaticallyAdjustsScrollIndicatorInsets = false |
| ... | @@ -571,6 +571,7 @@ public final class T3ReviewDiffView: ExpoView, UIScrollViewDelegate { | ... | @@ -571,6 +571,7 @@ public final class T3ReviewDiffView: ExpoView, UIScrollViewDelegate { |
| 571 | let delta = inset - scrollView.contentInset.top | 571 | let delta = inset - scrollView.contentInset.top |
| 572 | guard delta != 0 else { return } | 572 | guard delta != 0 else { return } |
| 573 | scrollView.contentInset.top = inset | 573 | scrollView.contentInset.top = inset |
| | 574 | contentView.topContentInset = inset |
| 574 | scrollView.verticalScrollIndicatorInsets.top = inset | 575 | scrollView.verticalScrollIndicatorInsets.top = inset |
| 575 | scrollView.contentOffset.y -= delta | 576 | scrollView.contentOffset.y -= delta |
| 576 | updateViewportFrame() | 577 | updateViewportFrame() |
| ... | @@ -618,8 +619,12 @@ public final class T3ReviewDiffView: ExpoView, UIScrollViewDelegate { | ... | @@ -618,8 +619,12 @@ public final class T3ReviewDiffView: ExpoView, UIScrollViewDelegate { |
| 618 | if let scrollAnchor, | 619 | if let scrollAnchor, |
| 619 | let headerOffset = contentView.fileHeaderOffset(forFileId: scrollAnchor.fileId) { | 620 | let headerOffset = contentView.fileHeaderOffset(forFileId: scrollAnchor.fileId) { |
| 620 | let targetOffset = headerOffset - scrollAnchor.screenY | 621 | let targetOffset = headerOffset - scrollAnchor.screenY |
| 621 | let maxOffset = max(scrollView.contentSize.height - scrollView.bounds.height, 0) | 622 | let minOffset = -scrollView.contentInset.top |
| 622 | let clampedOffset = min(max(targetOffset, 0), maxOffset) | 623 | let maxOffset = max( |
| | 624 | scrollView.contentSize.height - scrollView.bounds.height + scrollView.contentInset.bottom, |
| | 625 | minOffset |
| | 626 | ) |
| | 627 | let clampedOffset = min(max(targetOffset, minOffset), maxOffset) |
| 623 | scrollView.setContentOffset(CGPoint(x: 0, y: clampedOffset), animated: false) | 628 | scrollView.setContentOffset(CGPoint(x: 0, y: clampedOffset), animated: false) |
| 624 | updateViewportFrame() | 629 | updateViewportFrame() |
| 625 | } | 630 | } |
| ... | @@ -735,7 +740,8 @@ public final class T3ReviewDiffView: ExpoView, UIScrollViewDelegate { | ... | @@ -735,7 +740,8 @@ public final class T3ReviewDiffView: ExpoView, UIScrollViewDelegate { |
| 735 | // The top of the combined diff is the explicit "All files" destination. | 740 | // The top of the combined diff is the explicit "All files" destination. |
| 736 | // Treat it as a first-class selection instead of immediately resolving the | 741 | // Treat it as a first-class selection instead of immediately resolving the |
| 737 | // first file header and undoing the navigator's optimistic selection. | 742 | // first file header and undoing the navigator's optimistic selection. |
| 738 | if scrollView.contentOffset.y <= 0.5 { | 743 | let visibleTop = scrollView.contentOffset.y + scrollView.contentInset.top |
| | 744 | if visibleTop <= 0.5 { |
| 739 | guard lastVisibleFileId != nil else { | 745 | guard lastVisibleFileId != nil else { |
| 740 | return | 746 | return |
| 741 | } | 747 | } |
| ... | @@ -744,7 +750,7 @@ public final class T3ReviewDiffView: ExpoView, UIScrollViewDelegate { | ... | @@ -744,7 +750,7 @@ public final class T3ReviewDiffView: ExpoView, UIScrollViewDelegate { |
| 744 | return | 750 | return |
| 745 | } | 751 | } |
| 746 | | 752 | |
| 747 | guard let fileId = contentView.visibleFileId(atVerticalOffset: scrollView.contentOffset.y), | 753 | guard let fileId = contentView.visibleFileId(atVerticalOffset: visibleTop), |
| 748 | fileId != lastVisibleFileId else { | 754 | fileId != lastVisibleFileId else { |
| 749 | return | 755 | return |
| 750 | } | 756 | } |
| ... | @@ -1025,6 +1031,7 @@ private final class ReviewDiffContentView: UIView, UIGestureRecognizerDelegate { | ... | @@ -1025,6 +1031,7 @@ private final class ReviewDiffContentView: UIView, UIGestureRecognizerDelegate { |
| 1025 | } | 1031 | } |
| 1026 | } | 1032 | } |
| 1027 | var verticalOffset: CGFloat = 0 | 1033 | var verticalOffset: CGFloat = 0 |
| | 1034 | var topContentInset: CGFloat = 0 |
| 1028 | var theme = ReviewDiffNativeTheme.resolve("light") { | 1035 | var theme = ReviewDiffNativeTheme.resolve("light") { |
| 1029 | didSet { | 1036 | didSet { |
| 1030 | tokenColorsByHex.removeAll() | 1037 | tokenColorsByHex.removeAll() |
| ... | @@ -1495,7 +1502,7 @@ private final class ReviewDiffContentView: UIView, UIGestureRecognizerDelegate { | ... | @@ -1495,7 +1502,7 @@ private final class ReviewDiffContentView: UIView, UIGestureRecognizerDelegate { |
| 1495 | | 1502 | |
| 1496 | let rowIndex = stickyHeader.rowIndex | 1503 | let rowIndex = stickyHeader.rowIndex |
| 1497 | let headerTop = rowOffsets[rowIndex] | 1504 | let headerTop = rowOffsets[rowIndex] |
| 1498 | guard headerTop < verticalOffset else { | 1505 | guard headerTop < verticalOffset + topContentInset else { |
| 1499 | return nil | 1506 | return nil |
| 1500 | } | 1507 | } |
| 1501 | | 1508 | |
| ... | @@ -1505,12 +1512,12 @@ private final class ReviewDiffContentView: UIView, UIGestureRecognizerDelegate { | ... | @@ -1505,12 +1512,12 @@ private final class ReviewDiffContentView: UIView, UIGestureRecognizerDelegate { |
| 1505 | : nil | 1512 | : nil |
| 1506 | let pushedY: CGFloat | 1513 | let pushedY: CGFloat |
| 1507 | if let nextHeaderRowIndex { | 1514 | if let nextHeaderRowIndex { |
| 1508 | pushedY = min(0, rowOffsets[nextHeaderRowIndex] - verticalOffset - style.fileHeaderHeight) | 1515 | pushedY = min(topContentInset, rowOffsets[nextHeaderRowIndex] - verticalOffset - style.fileHeaderHeight) |
| 1509 | } else { | 1516 | } else { |
| 1510 | pushedY = 0 | 1517 | pushedY = topContentInset |
| 1511 | } | 1518 | } |
| 1512 | | 1519 | |
| 1513 | guard pushedY > -style.fileHeaderHeight else { | 1520 | guard pushedY > topContentInset - style.fileHeaderHeight else { |
| 1514 | return nil | 1521 | return nil |
| 1515 | } | 1522 | } |
| 1516 | | 1523 | |
| ... | @@ -1533,7 +1540,7 @@ private final class ReviewDiffContentView: UIView, UIGestureRecognizerDelegate { | ... | @@ -1533,7 +1540,7 @@ private final class ReviewDiffContentView: UIView, UIGestureRecognizerDelegate { |
| 1533 | while lowerBound < upperBound { | 1540 | while lowerBound < upperBound { |
| 1534 | let midpoint = (lowerBound + upperBound) / 2 | 1541 | let midpoint = (lowerBound + upperBound) / 2 |
| 1535 | let rowIndex = fileHeaderRowIndices[midpoint] | 1542 | let rowIndex = fileHeaderRowIndices[midpoint] |
| 1536 | if rowOffsets[rowIndex] <= verticalOffset { | 1543 | if rowOffsets[rowIndex] <= verticalOffset + topContentInset { |
| 1537 | lowerBound = midpoint + 1 | 1544 | lowerBound = midpoint + 1 |
| 1538 | } else { | 1545 | } else { |
| 1539 | upperBound = midpoint | 1546 | upperBound = midpoint |
| ... | @@ -1550,7 +1557,7 @@ private final class ReviewDiffContentView: UIView, UIGestureRecognizerDelegate { | ... | @@ -1550,7 +1557,7 @@ private final class ReviewDiffContentView: UIView, UIGestureRecognizerDelegate { |
| 1550 | func scrollAnchor(forFileId fileId: String) -> ReviewDiffScrollAnchor? { | 1557 | func scrollAnchor(forFileId fileId: String) -> ReviewDiffScrollAnchor? { |
| 1551 | if let stickyHeader = stickyFileHeaderTarget(), | 1558 | if let stickyHeader = stickyFileHeaderTarget(), |
| 1552 | resolvedFileId(for: stickyHeader.row) == fileId { | 1559 | resolvedFileId(for: stickyHeader.row) == fileId { |
| 1553 | return ReviewDiffScrollAnchor(fileId: fileId, screenY: 0) | 1560 | return ReviewDiffScrollAnchor(fileId: fileId, screenY: topContentInset) |
| 1554 | } | 1561 | } |
| 1555 | | 1562 | |
| 1556 | guard let headerOffset = fileHeaderOffset(forFileId: fileId) else { | 1563 | guard let headerOffset = fileHeaderOffset(forFileId: fileId) else { |