Enable lightbox for profile banner (#9643)

* allow pressing banner to view

* remove special type
This commit is contained in:
Samuel Newman
2026-01-06 20:37:47 +02:00
committed by GitHub
parent 43ebb80a5b
commit 0deb9511af
2 changed files with 72 additions and 17 deletions
+34 -5
View File
@@ -1,7 +1,7 @@
import {useEffect, useState} from 'react'
import {View} from 'react-native'
import {ActivityIndicator} from 'react-native'
import {ActivityIndicator, Pressable, View} from 'react-native'
import Animated, {
type AnimatedRef,
Extrapolation,
interpolate,
runOnJS,
@@ -28,26 +28,39 @@ const AnimatedBlurView = Animated.createAnimatedComponent(BlurView)
export function GrowableBanner({
backButton,
children,
onPress,
bannerRef,
}: {
backButton?: React.ReactNode
children: React.ReactNode
onPress?: () => void
bannerRef?: AnimatedRef<Animated.View>
}) {
const pagerContext = usePagerHeaderContext()
// plain non-growable mode for Android/Web
if (!pagerContext || !isIOS) {
return (
<View style={[a.w_full, a.h_full]}>
<Pressable
onPress={onPress}
accessibilityRole="image"
style={[a.w_full, a.h_full]}>
<Animated.View ref={bannerRef} style={[a.w_full, a.h_full]}>
{children}
</Animated.View>
{backButton}
</View>
</Pressable>
)
}
const {scrollY} = pagerContext
return (
<GrowableBannerInner scrollY={scrollY} backButton={backButton}>
<GrowableBannerInner
scrollY={scrollY}
backButton={backButton}
onPress={onPress}
bannerRef={bannerRef}>
{children}
</GrowableBannerInner>
)
@@ -57,10 +70,14 @@ function GrowableBannerInner({
scrollY,
backButton,
children,
onPress,
bannerRef,
}: {
scrollY: SharedValue<number>
backButton?: React.ReactNode
children: React.ReactNode
onPress?: () => void
bannerRef?: AnimatedRef<Animated.View>
}) {
const {top: topInset} = useSafeAreaInsets()
const isFetching = useIsProfileFetching()
@@ -124,14 +141,26 @@ function GrowableBannerInner({
{transformOrigin: 'bottom'},
animatedStyle,
]}>
<Pressable
onPress={onPress}
accessibilityRole="image"
style={[a.w_full, a.h_full]}>
<Animated.View
ref={bannerRef}
collapsable={false}
style={[a.w_full, a.h_full]}>
{children}
</Animated.View>
</Pressable>
<AnimatedBlurView
pointerEvents="none"
style={[a.absolute, a.inset_0]}
tint="dark"
animatedProps={animatedBlurViewProps}
/>
</Animated.View>
<View
pointerEvents="none"
style={[
a.absolute,
a.inset_0,
+32 -6
View File
@@ -1,5 +1,5 @@
import {memo, useCallback, useEffect, useMemo} from 'react'
import {TouchableWithoutFeedback, View} from 'react-native'
import {Pressable, View} from 'react-native'
import Animated, {
measure,
type MeasuredDimensions,
@@ -63,6 +63,7 @@ let ProfileHeaderShell = ({
const liveStatusControl = useDialogControl()
const aviRef = useAnimatedRef()
const bannerRef = useAnimatedRef<Animated.View>()
const onPressBack = useCallback(() => {
if (navigation.canGoBack()) {
@@ -73,20 +74,31 @@ let ProfileHeaderShell = ({
}, [navigation])
const _openLightbox = useCallback(
(uri: string, thumbRect: MeasuredDimensions | null) => {
(
uri: string,
thumbRect: MeasuredDimensions | null,
type: 'circle-avi' | 'image' = 'circle-avi',
) => {
openLightbox({
images: [
{
uri,
thumbUri: uri,
thumbRect,
dimensions: {
dimensions:
type === 'circle-avi'
? {
// It's fine if it's actually smaller but we know it's 1:1.
height: 1000,
width: 1000,
}
: {
// Banner aspect ratio is 3:1
width: 3000,
height: 1000,
},
thumbDimensions: null,
type: 'circle-avi',
type,
},
],
index: 0,
@@ -142,6 +154,18 @@ let ProfileHeaderShell = ({
playHaptic,
])
const onPressBanner = useCallback(() => {
const modui = moderation.ui('banner')
const banner = profile.banner
if (banner && !(modui.blur && modui.noOverride)) {
runOnUI(() => {
'worklet'
const rect = measure(bannerRef)
runOnJS(_openLightbox)(banner, rect, 'image')
})()
}
}, [profile.banner, moderation, _openLightbox, bannerRef])
return (
<View style={t.atoms.bg} pointerEvents={isIOS ? 'auto' : 'box-none'}>
<View
@@ -149,6 +173,8 @@ let ProfileHeaderShell = ({
style={[a.relative, {height: 150}]}>
<StatusBarShadow />
<GrowableBanner
onPress={isPlaceholderProfile ? undefined : onPressBanner}
bannerRef={bannerRef}
backButton={
!hideBackButton && (
<Button
@@ -234,7 +260,7 @@ let ProfileHeaderShell = ({
))}
<GrowableAvatar style={[a.absolute, {top: 104, left: 10}]}>
<TouchableWithoutFeedback
<Pressable
testID="profileHeaderAviButton"
onPress={onPressAvi}
accessibilityRole="image"
@@ -265,7 +291,7 @@ let ProfileHeaderShell = ({
{live.isActive && <LiveIndicator size="large" />}
</Animated.View>
</View>
</TouchableWithoutFeedback>
</Pressable>
</GrowableAvatar>
{live.isActive &&