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
+36 -7
View File
@@ -1,7 +1,7 @@
import {useEffect, useState} from 'react' import {useEffect, useState} from 'react'
import {View} from 'react-native' import {ActivityIndicator, Pressable, View} from 'react-native'
import {ActivityIndicator} from 'react-native'
import Animated, { import Animated, {
type AnimatedRef,
Extrapolation, Extrapolation,
interpolate, interpolate,
runOnJS, runOnJS,
@@ -28,26 +28,39 @@ const AnimatedBlurView = Animated.createAnimatedComponent(BlurView)
export function GrowableBanner({ export function GrowableBanner({
backButton, backButton,
children, children,
onPress,
bannerRef,
}: { }: {
backButton?: React.ReactNode backButton?: React.ReactNode
children: React.ReactNode children: React.ReactNode
onPress?: () => void
bannerRef?: AnimatedRef<Animated.View>
}) { }) {
const pagerContext = usePagerHeaderContext() const pagerContext = usePagerHeaderContext()
// plain non-growable mode for Android/Web // plain non-growable mode for Android/Web
if (!pagerContext || !isIOS) { if (!pagerContext || !isIOS) {
return ( return (
<View style={[a.w_full, a.h_full]}> <Pressable
{children} 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} {backButton}
</View> </Pressable>
) )
} }
const {scrollY} = pagerContext const {scrollY} = pagerContext
return ( return (
<GrowableBannerInner scrollY={scrollY} backButton={backButton}> <GrowableBannerInner
scrollY={scrollY}
backButton={backButton}
onPress={onPress}
bannerRef={bannerRef}>
{children} {children}
</GrowableBannerInner> </GrowableBannerInner>
) )
@@ -57,10 +70,14 @@ function GrowableBannerInner({
scrollY, scrollY,
backButton, backButton,
children, children,
onPress,
bannerRef,
}: { }: {
scrollY: SharedValue<number> scrollY: SharedValue<number>
backButton?: React.ReactNode backButton?: React.ReactNode
children: React.ReactNode children: React.ReactNode
onPress?: () => void
bannerRef?: AnimatedRef<Animated.View>
}) { }) {
const {top: topInset} = useSafeAreaInsets() const {top: topInset} = useSafeAreaInsets()
const isFetching = useIsProfileFetching() const isFetching = useIsProfileFetching()
@@ -124,14 +141,26 @@ function GrowableBannerInner({
{transformOrigin: 'bottom'}, {transformOrigin: 'bottom'},
animatedStyle, animatedStyle,
]}> ]}>
{children} <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 <AnimatedBlurView
pointerEvents="none"
style={[a.absolute, a.inset_0]} style={[a.absolute, a.inset_0]}
tint="dark" tint="dark"
animatedProps={animatedBlurViewProps} animatedProps={animatedBlurViewProps}
/> />
</Animated.View> </Animated.View>
<View <View
pointerEvents="none"
style={[ style={[
a.absolute, a.absolute,
a.inset_0, a.inset_0,
+36 -10
View File
@@ -1,5 +1,5 @@
import {memo, useCallback, useEffect, useMemo} from 'react' import {memo, useCallback, useEffect, useMemo} from 'react'
import {TouchableWithoutFeedback, View} from 'react-native' import {Pressable, View} from 'react-native'
import Animated, { import Animated, {
measure, measure,
type MeasuredDimensions, type MeasuredDimensions,
@@ -63,6 +63,7 @@ let ProfileHeaderShell = ({
const liveStatusControl = useDialogControl() const liveStatusControl = useDialogControl()
const aviRef = useAnimatedRef() const aviRef = useAnimatedRef()
const bannerRef = useAnimatedRef<Animated.View>()
const onPressBack = useCallback(() => { const onPressBack = useCallback(() => {
if (navigation.canGoBack()) { if (navigation.canGoBack()) {
@@ -73,20 +74,31 @@ let ProfileHeaderShell = ({
}, [navigation]) }, [navigation])
const _openLightbox = useCallback( const _openLightbox = useCallback(
(uri: string, thumbRect: MeasuredDimensions | null) => { (
uri: string,
thumbRect: MeasuredDimensions | null,
type: 'circle-avi' | 'image' = 'circle-avi',
) => {
openLightbox({ openLightbox({
images: [ images: [
{ {
uri, uri,
thumbUri: uri, thumbUri: uri,
thumbRect, thumbRect,
dimensions: { dimensions:
// It's fine if it's actually smaller but we know it's 1:1. type === 'circle-avi'
height: 1000, ? {
width: 1000, // 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, thumbDimensions: null,
type: 'circle-avi', type,
}, },
], ],
index: 0, index: 0,
@@ -142,6 +154,18 @@ let ProfileHeaderShell = ({
playHaptic, 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 ( return (
<View style={t.atoms.bg} pointerEvents={isIOS ? 'auto' : 'box-none'}> <View style={t.atoms.bg} pointerEvents={isIOS ? 'auto' : 'box-none'}>
<View <View
@@ -149,6 +173,8 @@ let ProfileHeaderShell = ({
style={[a.relative, {height: 150}]}> style={[a.relative, {height: 150}]}>
<StatusBarShadow /> <StatusBarShadow />
<GrowableBanner <GrowableBanner
onPress={isPlaceholderProfile ? undefined : onPressBanner}
bannerRef={bannerRef}
backButton={ backButton={
!hideBackButton && ( !hideBackButton && (
<Button <Button
@@ -234,7 +260,7 @@ let ProfileHeaderShell = ({
))} ))}
<GrowableAvatar style={[a.absolute, {top: 104, left: 10}]}> <GrowableAvatar style={[a.absolute, {top: 104, left: 10}]}>
<TouchableWithoutFeedback <Pressable
testID="profileHeaderAviButton" testID="profileHeaderAviButton"
onPress={onPressAvi} onPress={onPressAvi}
accessibilityRole="image" accessibilityRole="image"
@@ -265,7 +291,7 @@ let ProfileHeaderShell = ({
{live.isActive && <LiveIndicator size="large" />} {live.isActive && <LiveIndicator size="large" />}
</Animated.View> </Animated.View>
</View> </View>
</TouchableWithoutFeedback> </Pressable>
</GrowableAvatar> </GrowableAvatar>
{live.isActive && {live.isActive &&