Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Measure tapped image coordinates before opening lightbox (#6001)

* Measure image on press

* Pass dimensions to the lightbox component

authored by

dan and committed by
GitHub
1e32327d 6f4703e8

+56 -11
+1
src/screens/Profile/Header/Shell.tsx
··· 57 57 openLightbox({ 58 58 type: 'profile-image', 59 59 profile: profile, 60 + thumbDims: null, 60 61 }) 61 62 } 62 63 }, [openLightbox, profile, moderation])
+3
src/state/lightbox.tsx
··· 1 1 import React from 'react' 2 + import type {MeasuredDimensions} from 'react-native-reanimated' 2 3 import {AppBskyActorDefs} from '@atproto/api' 3 4 4 5 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' ··· 6 7 type ProfileImageLightbox = { 7 8 type: 'profile-image' 8 9 profile: AppBskyActorDefs.ProfileViewDetailed 10 + thumbDims: null 9 11 } 10 12 11 13 type ImagesLightboxItem = { ··· 17 19 type ImagesLightbox = { 18 20 type: 'images' 19 21 images: ImagesLightboxItem[] 22 + thumbDims: MeasuredDimensions | null 20 23 index: number 21 24 } 22 25
+3
src/view/com/lightbox/ImageViewing/index.tsx
··· 11 11 import React, {ComponentType, useCallback, useMemo, useState} from 'react' 12 12 import {Platform, StyleSheet, View} from 'react-native' 13 13 import PagerView from 'react-native-pager-view' 14 + import {MeasuredDimensions} from 'react-native-reanimated' 14 15 import Animated, {useAnimatedStyle, withSpring} from 'react-native-reanimated' 15 16 import {Edge, SafeAreaView} from 'react-native-safe-area-context' 16 17 ··· 20 21 21 22 type Props = { 22 23 images: ImageSource[] 24 + thumbDims: MeasuredDimensions | null 23 25 initialImageIndex: number 24 26 visible: boolean 25 27 onRequestClose: () => void ··· 32 34 33 35 function ImageViewing({ 34 36 images, 37 + thumbDims: _thumbDims, // TODO: Pass down and use for animation. 35 38 initialImageIndex, 36 39 visible, 37 40 onRequestClose,
+2
src/view/com/lightbox/Lightbox.tsx
··· 35 35 {uri: opts.profile.avatar || '', thumbUri: opts.profile.avatar || ''}, 36 36 ]} 37 37 initialImageIndex={0} 38 + thumbDims={opts.thumbDims} 38 39 visible 39 40 onRequestClose={onClose} 40 41 FooterComponent={LightboxFooter} ··· 46 47 <ImageView 47 48 images={opts.images.map(img => ({...img}))} 48 49 initialImageIndex={opts.index} 50 + thumbDims={opts.thumbDims} 49 51 visible 50 52 onRequestClose={onClose} 51 53 FooterComponent={LightboxFooter}
+1
src/view/com/profile/ProfileSubpageHeader.tsx
··· 74 74 type: 'images', 75 75 images: [{uri: avatar, thumbUri: avatar}], 76 76 index: 0, 77 + thumbDims: null, 77 78 }) 78 79 } 79 80 }, [openLightbox, avatar])
+9 -4
src/view/com/util/images/Gallery.tsx
··· 1 1 import React from 'react' 2 2 import {Pressable, StyleProp, View, ViewStyle} from 'react-native' 3 + import Animated, {AnimatedRef, useAnimatedRef} from 'react-native-reanimated' 3 4 import {Image, ImageStyle} from 'expo-image' 4 5 import {AppBskyEmbedImages} from '@atproto/api' 5 6 import {msg} from '@lingui/macro' ··· 16 17 interface Props { 17 18 images: AppBskyEmbedImages.ViewImage[] 18 19 index: number 19 - onPress?: EventFunction 20 + onPress?: ( 21 + index: number, 22 + containerRef: AnimatedRef<React.Component<{}, {}, any>>, 23 + ) => void 20 24 onLongPress?: EventFunction 21 25 onPressIn?: EventFunction 22 26 imageStyle?: StyleProp<ImageStyle> ··· 41 45 const hasAlt = !!image.alt 42 46 const hideBadges = 43 47 viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia 48 + const containerRef = useAnimatedRef() 44 49 return ( 45 - <View style={a.flex_1}> 50 + <Animated.View style={a.flex_1} ref={containerRef}> 46 51 <Pressable 47 - onPress={onPress ? () => onPress(index) : undefined} 52 + onPress={onPress ? () => onPress(index, containerRef) : undefined} 48 53 onPressIn={onPressIn ? () => onPressIn(index) : undefined} 49 54 onLongPress={onLongPress ? () => onLongPress(index) : undefined} 50 55 style={[ ··· 95 100 </Text> 96 101 </View> 97 102 ) : null} 98 - </View> 103 + </Animated.View> 99 104 ) 100 105 }
+9 -2
src/view/com/util/images/ImageLayoutGrid.tsx
··· 1 1 import React from 'react' 2 2 import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' 3 + import {AnimatedRef} from 'react-native-reanimated' 3 4 import {AppBskyEmbedImages} from '@atproto/api' 4 5 5 6 import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types' ··· 8 9 9 10 interface ImageLayoutGridProps { 10 11 images: AppBskyEmbedImages.ViewImage[] 11 - onPress?: (index: number) => void 12 + onPress?: ( 13 + index: number, 14 + containerRef: AnimatedRef<React.Component<{}, {}, any>>, 15 + ) => void 12 16 onLongPress?: (index: number) => void 13 17 onPressIn?: (index: number) => void 14 18 style?: StyleProp<ViewStyle> ··· 36 40 37 41 interface ImageLayoutGridInnerProps { 38 42 images: AppBskyEmbedImages.ViewImage[] 39 - onPress?: (index: number) => void 43 + onPress?: ( 44 + index: number, 45 + containerRef: AnimatedRef<React.Component<{}, {}, any>>, 46 + ) => void 40 47 onLongPress?: (index: number) => void 41 48 onPressIn?: (index: number) => void 42 49 viewContext?: PostEmbedViewContext
+28 -5
src/view/com/util/post-embeds/index.tsx
··· 6 6 View, 7 7 ViewStyle, 8 8 } from 'react-native' 9 + import Animated, { 10 + AnimatedRef, 11 + measure, 12 + MeasuredDimensions, 13 + runOnJS, 14 + runOnUI, 15 + useAnimatedRef, 16 + } from 'react-native-reanimated' 9 17 import {Image} from 'expo-image' 10 18 import { 11 19 AppBskyEmbedExternal, ··· 61 69 viewContext?: PostEmbedViewContext 62 70 }) { 63 71 const {openLightbox} = useLightboxControls() 72 + const containerRef = useAnimatedRef() 64 73 65 74 // quote post with media 66 75 // = ··· 138 147 alt: img.alt, 139 148 aspectRatio: img.aspectRatio, 140 149 })) 141 - const _openLightbox = (index: number) => { 150 + const _openLightbox = ( 151 + index: number, 152 + thumbDims: MeasuredDimensions | null, 153 + ) => { 142 154 openLightbox({ 143 155 type: 'images', 144 156 images: items, 145 157 index, 158 + thumbDims, 146 159 }) 147 160 } 161 + const onPress = ( 162 + index: number, 163 + ref: AnimatedRef<React.Component<{}, {}, any>>, 164 + ) => { 165 + runOnUI(() => { 166 + 'worklet' 167 + const dims = measure(ref) 168 + runOnJS(_openLightbox)(index, dims) 169 + })() 170 + } 148 171 const onPressIn = (_: number) => { 149 172 InteractionManager.runAfterInteractions(() => { 150 173 Image.prefetch(items.map(i => i.uri)) ··· 155 178 const image = images[0] 156 179 return ( 157 180 <ContentHider modui={moderation?.ui('contentMedia')}> 158 - <View style={[a.mt_sm, style]}> 181 + <Animated.View ref={containerRef} style={[a.mt_sm, style]}> 159 182 <AutoSizedImage 160 183 crop={ 161 184 viewContext === PostEmbedViewContext.ThreadHighlighted ··· 166 189 : 'constrained' 167 190 } 168 191 image={image} 169 - onPress={() => _openLightbox(0)} 192 + onPress={() => onPress(0, containerRef)} 170 193 onPressIn={() => onPressIn(0)} 171 194 hideBadge={ 172 195 viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia 173 196 } 174 197 /> 175 - </View> 198 + </Animated.View> 176 199 </ContentHider> 177 200 ) 178 201 } ··· 182 205 <View style={[a.mt_sm, style]}> 183 206 <ImageLayoutGrid 184 207 images={embed.images} 185 - onPress={_openLightbox} 208 + onPress={onPress} 186 209 onPressIn={onPressIn} 187 210 viewContext={viewContext} 188 211 />