Bluesky app fork with some witchin' additions 💫

Enable lightbox for profile banner (#9643)

* allow pressing banner to view

* remove special type

authored by samuel.fm and committed by

GitHub 0deb9511 43ebb80a

+72 -17
+36 -7
src/screens/Profile/Header/GrowableBanner.tsx
··· 1 1 import {useEffect, useState} from 'react' 2 - import {View} from 'react-native' 3 - import {ActivityIndicator} from 'react-native' 2 + import {ActivityIndicator, Pressable, View} from 'react-native' 4 3 import Animated, { 4 + type AnimatedRef, 5 5 Extrapolation, 6 6 interpolate, 7 7 runOnJS, ··· 28 28 export function GrowableBanner({ 29 29 backButton, 30 30 children, 31 + onPress, 32 + bannerRef, 31 33 }: { 32 34 backButton?: React.ReactNode 33 35 children: React.ReactNode 36 + onPress?: () => void 37 + bannerRef?: AnimatedRef<Animated.View> 34 38 }) { 35 39 const pagerContext = usePagerHeaderContext() 36 40 37 41 // plain non-growable mode for Android/Web 38 42 if (!pagerContext || !isIOS) { 39 43 return ( 40 - <View style={[a.w_full, a.h_full]}> 41 - {children} 44 + <Pressable 45 + onPress={onPress} 46 + accessibilityRole="image" 47 + style={[a.w_full, a.h_full]}> 48 + <Animated.View ref={bannerRef} style={[a.w_full, a.h_full]}> 49 + {children} 50 + </Animated.View> 42 51 {backButton} 43 - </View> 52 + </Pressable> 44 53 ) 45 54 } 46 55 47 56 const {scrollY} = pagerContext 48 57 49 58 return ( 50 - <GrowableBannerInner scrollY={scrollY} backButton={backButton}> 59 + <GrowableBannerInner 60 + scrollY={scrollY} 61 + backButton={backButton} 62 + onPress={onPress} 63 + bannerRef={bannerRef}> 51 64 {children} 52 65 </GrowableBannerInner> 53 66 ) ··· 57 70 scrollY, 58 71 backButton, 59 72 children, 73 + onPress, 74 + bannerRef, 60 75 }: { 61 76 scrollY: SharedValue<number> 62 77 backButton?: React.ReactNode 63 78 children: React.ReactNode 79 + onPress?: () => void 80 + bannerRef?: AnimatedRef<Animated.View> 64 81 }) { 65 82 const {top: topInset} = useSafeAreaInsets() 66 83 const isFetching = useIsProfileFetching() ··· 124 141 {transformOrigin: 'bottom'}, 125 142 animatedStyle, 126 143 ]}> 127 - {children} 144 + <Pressable 145 + onPress={onPress} 146 + accessibilityRole="image" 147 + style={[a.w_full, a.h_full]}> 148 + <Animated.View 149 + ref={bannerRef} 150 + collapsable={false} 151 + style={[a.w_full, a.h_full]}> 152 + {children} 153 + </Animated.View> 154 + </Pressable> 128 155 <AnimatedBlurView 156 + pointerEvents="none" 129 157 style={[a.absolute, a.inset_0]} 130 158 tint="dark" 131 159 animatedProps={animatedBlurViewProps} 132 160 /> 133 161 </Animated.View> 134 162 <View 163 + pointerEvents="none" 135 164 style={[ 136 165 a.absolute, 137 166 a.inset_0,
+36 -10
src/screens/Profile/Header/Shell.tsx
··· 1 1 import {memo, useCallback, useEffect, useMemo} from 'react' 2 - import {TouchableWithoutFeedback, View} from 'react-native' 2 + import {Pressable, View} from 'react-native' 3 3 import Animated, { 4 4 measure, 5 5 type MeasuredDimensions, ··· 63 63 const liveStatusControl = useDialogControl() 64 64 65 65 const aviRef = useAnimatedRef() 66 + const bannerRef = useAnimatedRef<Animated.View>() 66 67 67 68 const onPressBack = useCallback(() => { 68 69 if (navigation.canGoBack()) { ··· 73 74 }, [navigation]) 74 75 75 76 const _openLightbox = useCallback( 76 - (uri: string, thumbRect: MeasuredDimensions | null) => { 77 + ( 78 + uri: string, 79 + thumbRect: MeasuredDimensions | null, 80 + type: 'circle-avi' | 'image' = 'circle-avi', 81 + ) => { 77 82 openLightbox({ 78 83 images: [ 79 84 { 80 85 uri, 81 86 thumbUri: uri, 82 87 thumbRect, 83 - dimensions: { 84 - // It's fine if it's actually smaller but we know it's 1:1. 85 - height: 1000, 86 - width: 1000, 87 - }, 88 + dimensions: 89 + type === 'circle-avi' 90 + ? { 91 + // It's fine if it's actually smaller but we know it's 1:1. 92 + height: 1000, 93 + width: 1000, 94 + } 95 + : { 96 + // Banner aspect ratio is 3:1 97 + width: 3000, 98 + height: 1000, 99 + }, 88 100 thumbDimensions: null, 89 - type: 'circle-avi', 101 + type, 90 102 }, 91 103 ], 92 104 index: 0, ··· 142 154 playHaptic, 143 155 ]) 144 156 157 + const onPressBanner = useCallback(() => { 158 + const modui = moderation.ui('banner') 159 + const banner = profile.banner 160 + if (banner && !(modui.blur && modui.noOverride)) { 161 + runOnUI(() => { 162 + 'worklet' 163 + const rect = measure(bannerRef) 164 + runOnJS(_openLightbox)(banner, rect, 'image') 165 + })() 166 + } 167 + }, [profile.banner, moderation, _openLightbox, bannerRef]) 168 + 145 169 return ( 146 170 <View style={t.atoms.bg} pointerEvents={isIOS ? 'auto' : 'box-none'}> 147 171 <View ··· 149 173 style={[a.relative, {height: 150}]}> 150 174 <StatusBarShadow /> 151 175 <GrowableBanner 176 + onPress={isPlaceholderProfile ? undefined : onPressBanner} 177 + bannerRef={bannerRef} 152 178 backButton={ 153 179 !hideBackButton && ( 154 180 <Button ··· 234 260 ))} 235 261 236 262 <GrowableAvatar style={[a.absolute, {top: 104, left: 10}]}> 237 - <TouchableWithoutFeedback 263 + <Pressable 238 264 testID="profileHeaderAviButton" 239 265 onPress={onPressAvi} 240 266 accessibilityRole="image" ··· 265 291 {live.isActive && <LiveIndicator size="large" />} 266 292 </Animated.View> 267 293 </View> 268 - </TouchableWithoutFeedback> 294 + </Pressable> 269 295 </GrowableAvatar> 270 296 271 297 {live.isActive &&