Skip to content

Commit 25d76b7

Browse files
isaacclaude
andcommitted
fix(instantpage): V2 slideshow black image backdrop + current-page-only gallery transition
Two InstantView-V2 slideshow rendering corrections: - Black backdrop: the visible fill behind a slideshow photo is the image node's emptyColor, which for the rich-message theme is panelBackgroundColor (accentControlColor·0.1) — the placeholder-gray. Add a scoped emptyColor override to InstantPageImageNode (threaded via makeMediaWrapper, default nil = unchanged for all other callers) and pass .black from the slideshow, so the backdrop is a proper gallery black rather than the placeholder color. Pairs with the earlier SlideshowView/pageView .black backgrounds. - Gallery close: a slideshow registers under EVERY contained media index, so the gallery could ask it to animate in/out to a media sitting on an off-screen page. Track the current page and return a transition node only for the media on the currently-displayed page (nil otherwise → the gallery uses a plain fade instead of flying to the wrong place). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9408604 commit 25d76b7

3 files changed

Lines changed: 28 additions & 7 deletions

File tree

‎submodules/InstantPageUI/Sources/InstantPageImageNode.swift‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode, InstantPageExt
5959
private let interactive: Bool
6060
private let roundCorners: Bool
6161
private let fit: Bool
62+
/// When set, overrides the per-media-type placeholder/letterbox `emptyColor` (e.g. the slideshow uses
63+
/// black instead of the panel/placeholder color). nil = keep the per-type default.
64+
private let emptyColorOverride: UIColor?
6265
private let openMedia: (InstantPageMedia) -> Void
6366
private let longPressMedia: (InstantPageMedia) -> Void
6467
private let getPreloadedResource: (String) -> Data?
@@ -91,7 +94,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode, InstantPageExt
9194
// enclosing V2 media view drives the dust cover + reveal timing. Default off (no effect on web IV).
9295
private var contentBlurredSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
9396

94-
init(context: AccountContext, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute], interactive: Bool, roundCorners: Bool, fit: Bool, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, imageReferenceForMedia: ((TelegramMediaImage) -> ImageMediaReference)? = nil, fileReferenceForMedia: ((TelegramMediaFile) -> FileMediaReference)? = nil, autoDownloadImage: ((TelegramMediaImage) -> Bool)? = nil, autoDownloadFile: ((TelegramMediaFile) -> Bool)? = nil, getPreloadedResource: @escaping (String) -> Data?) {
97+
init(context: AccountContext, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute], interactive: Bool, roundCorners: Bool, fit: Bool, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, imageReferenceForMedia: ((TelegramMediaImage) -> ImageMediaReference)? = nil, fileReferenceForMedia: ((TelegramMediaFile) -> FileMediaReference)? = nil, autoDownloadImage: ((TelegramMediaImage) -> Bool)? = nil, autoDownloadFile: ((TelegramMediaFile) -> Bool)? = nil, emptyColor: UIColor? = nil, getPreloadedResource: @escaping (String) -> Data?) {
9598
self.context = context
9699
self.theme = theme
97100
self.webPage = webPage
@@ -100,6 +103,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode, InstantPageExt
100103
self.interactive = interactive
101104
self.roundCorners = roundCorners
102105
self.fit = fit
106+
self.emptyColorOverride = emptyColor
103107
self.openMedia = openMedia
104108
self.longPressMedia = longPressMedia
105109
self.getPreloadedResource = getPreloadedResource
@@ -418,17 +422,17 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode, InstantPageExt
418422
let boundingSize = size
419423
let radius: CGFloat = self.roundCorners ? floor(min(imageSize.width, imageSize.height) / 2.0) : 0.0
420424
let makeLayout = self.imageNode.asyncLayout()
421-
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), emptyColor: self.theme.panelBackgroundColor))
425+
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), emptyColor: self.emptyColorOverride ?? self.theme.panelBackgroundColor))
422426
apply()
423-
427+
424428
self.linkIconNode.frame = CGRect(x: size.width - 38.0, y: 14.0, width: 24.0, height: 24.0)
425429
} else if case let .file(file) = self.media.media, let dimensions = self.effectiveMediaDimensions() {
426430
let emptyColor = file.mimeType.hasPrefix("image/") ? self.theme.imageTintColor : nil
427-
431+
428432
let imageSize = dimensions.cgSize.aspectFilled(size)
429433
let boundingSize = size
430434
let makeLayout = self.imageNode.asyncLayout()
431-
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), emptyColor: emptyColor))
435+
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), emptyColor: self.emptyColorOverride ?? emptyColor))
432436
apply()
433437
} else if case .geo = self.media.media {
434438
let presentationTheme = self.context.sharedContext.currentPresentationData.with { $0 }.theme

‎submodules/InstantPageUI/Sources/InstantPageV2MediaViews.swift‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ func makeMediaWrapper(
9191
renderContext: InstantPageV2RenderContext,
9292
theme: InstantPageTheme,
9393
openMedia: @escaping (InstantPageMedia) -> Void,
94-
longPressMedia: @escaping (InstantPageMedia) -> Void
94+
longPressMedia: @escaping (InstantPageMedia) -> Void,
95+
emptyColor: UIColor? = nil
9596
) -> InstantPageImageNode {
9697
let imageNode = InstantPageImageNode(
9798
context: renderContext.context,
@@ -111,6 +112,7 @@ func makeMediaWrapper(
111112
fileReferenceForMedia: renderContext.fileReference,
112113
autoDownloadImage: renderContext.shouldAutoDownloadImage,
113114
autoDownloadFile: renderContext.shouldAutoDownloadFile,
115+
emptyColor: emptyColor,
114116
getPreloadedResource: { _ in nil }
115117
)
116118
imageNode.frame = CGRect(origin: .zero, size: frame.size)

‎submodules/InstantPageUI/Sources/InstantPageV2SlideshowView.swift‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ final class InstantPageV2SlideshowView: UIView, InstantPageItemView, UIScrollVie
2727
private var pageViews: [UIView] = []
2828
private var pageImageNodes: [InstantPageImageNode] = []
2929

30+
// The index (into `item.medias`) of the page currently centered in the scroll view. Only this page's
31+
// media has an on-screen representation, so it is the only one the gallery may animate in/out to.
32+
private var currentPageIndex: Int = 0
33+
3034
init(item: InstantPageV2SlideshowItem, renderContext: InstantPageV2RenderContext, theme: InstantPageTheme) {
3135
self.item = item
3236
self.renderContext = renderContext
@@ -87,7 +91,8 @@ final class InstantPageV2SlideshowView: UIView, InstantPageItemView, UIScrollVie
8791
renderContext: self.renderContext,
8892
theme: self.theme,
8993
openMedia: openMedia,
90-
longPressMedia: { _ in }
94+
longPressMedia: { _ in },
95+
emptyColor: .black
9196
)
9297
pageView.addSubview(node.view)
9398
self.pageImageNodes.append(node)
@@ -100,6 +105,7 @@ final class InstantPageV2SlideshowView: UIView, InstantPageItemView, UIScrollVie
100105

101106
self.pageControlNode.pagesCount = self.item.medias.count
102107
self.pageControlNode.setPage(0.0)
108+
self.currentPageIndex = 0
103109
// Re-register media indices when rebuilding while already on-window (positional reuse with
104110
// changed content); no-ops before the view is attached, where didMoveToWindow handles it.
105111
self.registerMedias()
@@ -148,6 +154,7 @@ final class InstantPageV2SlideshowView: UIView, InstantPageItemView, UIScrollVie
148154
guard width > 0.0, !self.item.medias.isEmpty else { return }
149155
let page = Int((scrollView.contentOffset.x + width / 2.0) / width)
150156
let clamped = max(0, min(self.item.medias.count - 1, page))
157+
self.currentPageIndex = clamped
151158
self.pageControlNode.setPage(CGFloat(clamped))
152159
}
153160

@@ -170,6 +177,14 @@ final class InstantPageV2SlideshowView: UIView, InstantPageItemView, UIScrollVie
170177
// MARK: InstantPageItemView gallery hooks
171178

172179
func instantPageTransitionNode(for media: InstantPageMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
180+
// A slideshow registers under EVERY contained media index, so the gallery may ask about a media that
181+
// sits on an off-screen page. Only the currently-displayed page has an on-screen representation;
182+
// animating in/out to a scrolled-away page's node would fly to the wrong place. Return nil for any
183+
// non-current media so the gallery falls back to a plain fade instead of using it to animate.
184+
guard self.currentPageIndex >= 0, self.currentPageIndex < self.item.medias.count,
185+
self.item.medias[self.currentPageIndex].index == media.index else {
186+
return nil
187+
}
173188
for node in self.pageImageNodes {
174189
if let transition = node.transitionNode(media: media) {
175190
return transition

0 commit comments

Comments
 (0)