@planningcenter/chat-react-native 3.12.0 → 3.12.1-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"image_attachment.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation/attachments/image_attachment.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAA;AAkCvF,OAAO,EAAE,qCAAqC,EAAE,MAAM,2DAA2D,CAAA;AAwBjH,MAAM,MAAM,SAAS,GAAG;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,wBAAgB,eAAe,CAAC,EAC9B,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,4BAA4B,GAC7B,EAAE;IACD,UAAU,EAAE,qCAAqC,CAAA;IACjD,gBAAgB,EAAE,qCAAqC,EAAE,CAAA;IACzD,iBAAiB,EAAE,MAAM,CAAA;IACzB,SAAS,EAAE,SAAS,CAAA;IACpB,4BAA4B,EAAE,CAAC,UAAU,EAAE,qCAAqC,KAAK,IAAI,CAAA;CAC1F,qBAyCA"}
1
+ {"version":3,"file":"image_attachment.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation/attachments/image_attachment.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAA;AAgCvF,OAAO,EAAE,qCAAqC,EAAE,MAAM,2DAA2D,CAAA;AAuBjH,MAAM,MAAM,SAAS,GAAG;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,wBAAgB,eAAe,CAAC,EAC9B,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,4BAA4B,GAC7B,EAAE;IACD,UAAU,EAAE,qCAAqC,CAAA;IACjD,gBAAgB,EAAE,qCAAqC,EAAE,CAAA;IACzD,iBAAiB,EAAE,MAAM,CAAA;IACzB,SAAS,EAAE,SAAS,CAAA;IACpB,4BAA4B,EAAE,CAAC,UAAU,EAAE,qCAAqC,KAAK,IAAI,CAAA;CAC1F,qBAyCA"}
@@ -1,7 +1,7 @@
1
1
  import React, { useMemo, useState, useCallback } from 'react';
2
- import { StatusBar, StyleSheet, Modal, View, Linking, Dimensions, Platform, } from 'react-native';
2
+ import { StatusBar, StyleSheet, Modal, View, Linking, Dimensions, } from 'react-native';
3
3
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
4
- import { FlatList, Gesture, GestureDetector, GestureHandlerRootView, Pressable, } from 'react-native-gesture-handler';
4
+ import { FlatList, Gesture, GestureDetector, GestureHandlerRootView, } from 'react-native-gesture-handler';
5
5
  import Animated, { runOnJS, useAnimatedStyle, useAnimatedReaction, useSharedValue, withSpring, withDecay, } from 'react-native-reanimated';
6
6
  import { tokens } from '../../../vendor/tapestry/tokens';
7
7
  import { IconButton, Image, Heading, Text } from '../../display';
@@ -14,7 +14,6 @@ const { width: WINDOW_WIDTH, height: WINDOW_HEIGHT } = Dimensions.get('window');
14
14
  const DISMISS_PAN_THRESHOLD = 250;
15
15
  const MIN_DISTANCE_FOR_PAN = 10; // Higher threshold gives pinching priority
16
16
  const SINGLE_FINGER_POINTER = 1; // Single-finger panning / tapping helps to avoid conflicts with pinching
17
- const MAX_TRAVEL_DISTANCE_FOR_DOUBLE_TAP_PLATFORM = Platform.OS === 'ios' ? 8 : 0; // Causes taps to be unreliable on Android
18
17
  const DEFAULT_OPACITY = 1;
19
18
  const DEFAULT_TRANSLATE_X = 0;
20
19
  const DEFAULT_TRANSLATE_Y = 0;
@@ -77,25 +76,20 @@ const LightboxModal = ({ visible, setModalVisible, imageAttachments, initialImag
77
76
  // React (JS) State:
78
77
  const [isStatusBarHidden, setIsStatusBarHidden] = useState(false);
79
78
  const [flatListScrollEnabled, setFlatListScrollEnabled] = useState(true);
80
- const [panGestureEnabled, setPanGestureEnabled] = useState(false);
81
79
  // Syncs toolbar useSharedValue state with React's state so that the status bar can be hidden/shown based on the toolbar's animation
82
80
  useAnimatedReaction(() => lightboxToolbarVisible.value, value => {
83
81
  runOnJS(setIsStatusBarHidden)(value === 0);
84
82
  });
85
- // Syncs FlatList scroll state with scale changes
86
- // When image is at default scale, enable scroll and disable pan gesture
87
- // When image is zoomed in, disable scroll and enable pan gesture
88
- useAnimatedReaction(() => scale.value, value => {
89
- const enableFlatListScroll = value === DEFAULT_SCALE;
90
- runOnJS(setFlatListScrollEnabled)(enableFlatListScroll);
91
- runOnJS(setPanGestureEnabled)(!enableFlatListScroll);
92
- });
93
83
  /* ============================
94
84
  HANDLERS
95
85
  ============================ */
96
86
  const handleOpenInBrowser = useCallback(() => {
97
87
  Linking.openURL(urlMedium || url);
98
88
  }, [urlMedium, url]);
89
+ // Helper to enable or disable FlatList scroll state after gestures have ended
90
+ const enableFlatListScroll = useCallback((enableScroll) => {
91
+ setFlatListScrollEnabled(enableScroll);
92
+ }, []);
99
93
  const resetDismissGestures = useCallback(() => {
100
94
  dismissY.value = withSpring(0, RESET_SPRING_CONFIG);
101
95
  opacity.value = withSpring(DEFAULT_OPACITY, RESET_SPRING_CONFIG);
@@ -109,6 +103,7 @@ const LightboxModal = ({ visible, setModalVisible, imageAttachments, initialImag
109
103
  savedTranslateX.value = DEFAULT_TRANSLATE_X;
110
104
  savedTranslateY.value = DEFAULT_TRANSLATE_Y;
111
105
  lightboxToolbarVisible.value = withSpring(1, RESET_SPRING_CONFIG);
106
+ enableFlatListScroll(true);
112
107
  }, [
113
108
  resetDismissGestures,
114
109
  scale,
@@ -118,6 +113,7 @@ const LightboxModal = ({ visible, setModalVisible, imageAttachments, initialImag
118
113
  savedTranslateX,
119
114
  savedTranslateY,
120
115
  lightboxToolbarVisible,
116
+ enableFlatListScroll,
121
117
  ]);
122
118
  const handleCloseModal = useCallback(() => {
123
119
  setModalVisible(false);
@@ -252,9 +248,6 @@ const LightboxModal = ({ visible, setModalVisible, imageAttachments, initialImag
252
248
  lightboxToolbarVisible.value = withSpring(lightboxToolbarVisible.value > 0.5 ? 0 : 1, RESET_SPRING_CONFIG);
253
249
  });
254
250
  const doubleTapGesture = Gesture.Tap()
255
- .maxDeltaX(MAX_TRAVEL_DISTANCE_FOR_DOUBLE_TAP_PLATFORM)
256
- .maxDeltaY(MAX_TRAVEL_DISTANCE_FOR_DOUBLE_TAP_PLATFORM)
257
- .maxDistance(MAX_TRAVEL_DISTANCE_FOR_DOUBLE_TAP_PLATFORM)
258
251
  .minPointers(SINGLE_FINGER_POINTER)
259
252
  .numberOfTaps(2)
260
253
  .onStart(e => {
@@ -288,6 +281,8 @@ const LightboxModal = ({ visible, setModalVisible, imageAttachments, initialImag
288
281
  savedScale.value = DOUBLE_TAP_ZOOM_SCALE;
289
282
  savedTranslateX.value = clampedTranslate.x;
290
283
  savedTranslateY.value = clampedTranslate.y;
284
+ // Disable FlatList scroll since image is now zoomed
285
+ runOnJS(enableFlatListScroll)(false);
291
286
  }
292
287
  });
293
288
  const pinchGesture = Gesture.Pinch()
@@ -356,20 +351,24 @@ const LightboxModal = ({ visible, setModalVisible, imageAttachments, initialImag
356
351
  savedScale.value = finalScale;
357
352
  savedTranslateX.value = clampedTranslate.x;
358
353
  savedTranslateY.value = clampedTranslate.y;
354
+ // Disable FlatList scroll since image is still zoomed
355
+ runOnJS(enableFlatListScroll)(false);
359
356
  });
360
357
  const panGesture = Gesture.Pan()
361
358
  .minDistance(MIN_DISTANCE_FOR_PAN)
362
359
  .minPointers(SINGLE_FINGER_POINTER)
363
360
  .maxPointers(SINGLE_FINGER_POINTER)
364
- .enabled(panGestureEnabled)
365
361
  .onStart(() => {
362
+ // Only start pan if image is zoomed in - prevents conflicts with FlatList
363
+ if (scale.value <= DEFAULT_SCALE)
364
+ return;
366
365
  // Update saved position to current animated position
367
366
  // This ensures smooth continuation if decay is running
368
367
  savedTranslateX.value = translateX.value;
369
368
  savedTranslateY.value = translateY.value;
370
369
  })
371
370
  .onUpdate(e => {
372
- // Only pan if image is zoomed in
371
+ // Only pan if image is zoomed in - prevents conflicts with FlatList
373
372
  if (scale.value <= DEFAULT_SCALE)
374
373
  return;
375
374
  const newTranslateX = savedTranslateX.value + e.translationX;
@@ -448,22 +447,15 @@ const LightboxModal = ({ visible, setModalVisible, imageAttachments, initialImag
448
447
  <StatusBar barStyle="light-content" hidden={isStatusBarHidden} animated showHideTransition="slide"/>
449
448
  <GestureHandlerRootView>
450
449
  <GestureDetector gesture={composedGesture}>
451
- <PreventPressEventsBubbling>
452
- <FlatList data={imageAttachments} renderItem={({ item, index }) => (<GestureImage item={item} gesturesEnabled={index === currentImageIndex} scale={scale} translateX={translateX} translateY={translateY} dismissY={dismissY} opacity={opacity}/>)} keyExtractor={(item, index) => `${item.id}-${index}`} horizontal pagingEnabled scrollEnabled={flatListScrollEnabled} showsHorizontalScrollIndicator={false} initialScrollIndex={initialImageIndex} getItemLayout={getItemLayout} onMomentumScrollEnd={onMomentumScrollEnd} onViewableItemsChanged={onViewableItemsChanged} viewabilityConfig={{
450
+ <FlatList data={imageAttachments} renderItem={({ item, index }) => (<GestureImage item={item} gesturesEnabled={index === currentImageIndex} scale={scale} translateX={translateX} translateY={translateY} dismissY={dismissY} opacity={opacity}/>)} keyExtractor={(item, index) => `${item.id}-${index}`} horizontal pagingEnabled scrollEnabled={flatListScrollEnabled} showsHorizontalScrollIndicator={false} initialScrollIndex={initialImageIndex} getItemLayout={getItemLayout} onMomentumScrollEnd={onMomentumScrollEnd} onViewableItemsChanged={onViewableItemsChanged} viewabilityConfig={{
453
451
  itemVisiblePercentThreshold: 50, // 50% of the image must be visible in the window to be considered viewable
454
452
  }} style={styles.gallery} contentContainerStyle={styles.galleryContentContainer}/>
455
- </PreventPressEventsBubbling>
456
453
  </GestureDetector>
457
454
  </GestureHandlerRootView>
458
455
  <LightboxToolbarHeader metaProps={metaProps} lightboxToolbarVisible={lightboxToolbarVisible} isStatusBarHidden={isStatusBarHidden} handleOpenInBrowser={handleOpenInBrowser} handleCloseModal={handleCloseModal}/>
459
456
  {imageAttachments.length > 1 && (<LightboxToolbarFooter lightboxToolbarVisible={lightboxToolbarVisible} totalImages={imageAttachments.length} currentImageIndex={currentImageIndex}/>)}
460
457
  </Modal>);
461
458
  };
462
- const PreventPressEventsBubbling = ({ children }) => {
463
- return (<Pressable onLongPress={() => { }} onPress={() => { }}>
464
- {children}
465
- </Pressable>);
466
- };
467
459
  const GestureImage = ({ item, gesturesEnabled, scale, translateX, translateY, dismissY, opacity, }) => {
468
460
  const styles = useStyles();
469
461
  const { url: itemUrl, urlMedium: itemUrlMedium } = item.attributes;
@@ -477,7 +469,15 @@ const GestureImage = ({ item, gesturesEnabled, scale, translateX, translateY, di
477
469
  opacity: opacity.value,
478
470
  };
479
471
  });
480
- return (<Image source={{ uri: itemUrlMedium || itemUrl }} style={styles.gestureImage} animatedImageStyle={animatedImageStyles} loadingBackgroundStyles={styles.gestureImageLoading} resizeMode="contain" alt=""/>);
472
+ return (<TouchEventIsolator>
473
+ <Image source={{ uri: itemUrlMedium || itemUrl }} style={styles.gestureImage} animatedImageStyle={animatedImageStyles} loadingBackgroundStyles={styles.gestureImageLoading} resizeMode="contain" alt=""/>
474
+ </TouchEventIsolator>);
475
+ };
476
+ const TouchEventIsolator = ({ children }) => {
477
+ const styles = useStyles();
478
+ return (<View style={styles.touchEventIsolator} onStartShouldSetResponder={() => true}>
479
+ {children}
480
+ </View>);
481
481
  };
482
482
  const LightboxToolbarHeader = ({ metaProps, lightboxToolbarVisible, isStatusBarHidden, handleOpenInBrowser, handleCloseModal, }) => {
483
483
  const styles = useStyles();
@@ -523,6 +523,9 @@ const useStyles = ({ imageWidth = 100, imageHeight = 100 } = {}) => {
523
523
  container: {
524
524
  maxWidth: '100%',
525
525
  },
526
+ touchEventIsolator: {
527
+ flex: 1,
528
+ },
526
529
  attachmentImageWrapper: {
527
530
  width: '100%',
528
531
  minWidth: 200,
@@ -1 +1 @@
1
- {"version":3,"file":"image_attachment.js","sourceRoot":"","sources":["../../../../src/components/conversation/attachments/image_attachment.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useMemo, useState, Dispatch, SetStateAction, useCallback } from 'react'\nimport {\n StatusBar,\n StyleSheet,\n Modal,\n View,\n Linking,\n Dimensions,\n NativeSyntheticEvent,\n NativeScrollEvent,\n ViewToken,\n Platform,\n} from 'react-native'\nimport { useSafeAreaInsets } from 'react-native-safe-area-context'\nimport {\n FlatList,\n Gesture,\n GestureDetector,\n GestureHandlerRootView,\n Pressable,\n} from 'react-native-gesture-handler'\nimport Animated, {\n runOnJS,\n useAnimatedStyle,\n useAnimatedReaction,\n useSharedValue,\n withSpring,\n withDecay,\n SharedValue,\n} from 'react-native-reanimated'\nimport { tokens } from '../../../vendor/tapestry/tokens'\nimport { IconButton, Image, Heading, Text } from '../../display'\nimport colorFunction from 'color'\nimport { formatDatePreview } from '../../../utils/date'\nimport { DenormalizedMessageAttachmentResource } from '../../../types/resources/denormalized_attachment_resource'\nimport { PlatformPressable } from '@react-navigation/elements'\nimport { useTheme } from '../../../hooks'\nimport { platformFontWeightMedium } from '../../../utils'\n\nconst { width: WINDOW_WIDTH, height: WINDOW_HEIGHT } = Dimensions.get('window')\nconst DISMISS_PAN_THRESHOLD = 250\nconst MIN_DISTANCE_FOR_PAN = 10 // Higher threshold gives pinching priority\nconst SINGLE_FINGER_POINTER = 1 // Single-finger panning / tapping helps to avoid conflicts with pinching\nconst MAX_TRAVEL_DISTANCE_FOR_DOUBLE_TAP_PLATFORM = Platform.OS === 'ios' ? 8 : 0 // Causes taps to be unreliable on Android\nconst DEFAULT_OPACITY = 1\nconst DEFAULT_TRANSLATE_X = 0\nconst DEFAULT_TRANSLATE_Y = 0\nconst DEFAULT_SCALE = 1\nconst MIN_SCALE = 0.5\nconst MAX_SCALE = 5\nconst MAX_SCALE_OVERSHOOT = 5\nconst DOUBLE_TAP_ZOOM_SCALE = 2\nconst DECAY_VELOCITY_FACTOR = 0.4\nconst RESET_SPRING_CONFIG = {\n damping: 20,\n stiffness: 150,\n}\n\nexport type MetaProps = {\n authorName: string\n createdAt: string\n}\n\nexport function ImageAttachment({\n attachment,\n imageAttachments,\n currentImageIndex,\n metaProps,\n onMessageAttachmentLongPress,\n}: {\n attachment: DenormalizedMessageAttachmentResource\n imageAttachments: DenormalizedMessageAttachmentResource[]\n currentImageIndex: number\n metaProps: MetaProps\n onMessageAttachmentLongPress: (attachment: DenormalizedMessageAttachmentResource) => void\n}) {\n const { attributes } = attachment\n const { url, urlMedium, filename, metadata = {} } = attributes\n const { colors } = useTheme()\n\n const styles = useStyles({ imageWidth: metadata.width, imageHeight: metadata.height })\n const [visible, setVisible] = useState(false)\n\n // Force modal to remount with fresh state\n // Fixes a bug where dismissing the modal too quickly causes the Reanimated shared values (like toolbarVisible) to not reset.\n const [modalKey, setModalKey] = useState(0)\n\n return (\n <>\n <PlatformPressable\n style={styles.container}\n onPress={() => {\n setModalKey(prev => prev + 1)\n setVisible(true)\n }}\n onLongPress={() => onMessageAttachmentLongPress(attachment)}\n android_ripple={{ color: colors.androidRippleNeutral, foreground: true }}\n accessibilityHint=\"Long press for more options\"\n >\n <Image\n source={{ uri: urlMedium || url }}\n style={styles.image}\n wrapperStyle={styles.attachmentImageWrapper}\n alt={filename}\n />\n </PlatformPressable>\n <LightboxModal\n key={modalKey}\n visible={visible}\n setModalVisible={setVisible}\n imageAttachments={imageAttachments}\n initialImageIndex={currentImageIndex}\n metaProps={metaProps}\n />\n </>\n )\n}\n\ninterface LightboxModalProps {\n visible: boolean\n setModalVisible: Dispatch<SetStateAction<boolean>>\n imageAttachments: DenormalizedMessageAttachmentResource[]\n initialImageIndex: number\n metaProps: MetaProps\n}\n\nconst LightboxModal = ({\n visible,\n setModalVisible,\n imageAttachments,\n initialImageIndex,\n metaProps,\n}: LightboxModalProps) => {\n const styles = useStyles()\n const insets = useSafeAreaInsets()\n const [currentImageIndex, setCurrentImageIndex] = useState(initialImageIndex)\n\n // Get current image data\n const currentImage = imageAttachments[currentImageIndex]\n const { url, urlMedium, metadata = {} } = currentImage.attributes\n const imageWidth = metadata.width\n const imageHeight = metadata.height\n\n // Calculate available space for image display\n const availableWindowWidth = WINDOW_WIDTH\n const availableWindowHeight = WINDOW_HEIGHT - insets.top - insets.bottom\n\n /* ============================\n ANIMATION VALUES\n ============================ */\n // Native State:\n const dismissY = useSharedValue(0) // vertical distance to dismiss modal\n const opacity = useSharedValue(DEFAULT_OPACITY) // opacity of modal\n const scale = useSharedValue(DEFAULT_SCALE) // zoom level of image\n const focalX = useSharedValue(0) // focal point of image between fingers\n const focalY = useSharedValue(0) // focal point of image between fingers\n const translateX = useSharedValue(DEFAULT_TRANSLATE_X) // horizontal distance to pan image\n const translateY = useSharedValue(DEFAULT_TRANSLATE_Y) // vertical distance to pan image\n const savedScale = useSharedValue(DEFAULT_SCALE) // previous zoom level\n const savedTranslateX = useSharedValue(DEFAULT_TRANSLATE_X) // previous horizontal position\n const savedTranslateY = useSharedValue(DEFAULT_TRANSLATE_Y) // previous vertical position\n const lightboxToolbarVisible = useSharedValue(1) // toolbar visibility state\n\n // React (JS) State:\n const [isStatusBarHidden, setIsStatusBarHidden] = useState(false)\n const [flatListScrollEnabled, setFlatListScrollEnabled] = useState(true)\n const [panGestureEnabled, setPanGestureEnabled] = useState(false)\n\n // Syncs toolbar useSharedValue state with React's state so that the status bar can be hidden/shown based on the toolbar's animation\n useAnimatedReaction(\n () => lightboxToolbarVisible.value,\n value => {\n runOnJS(setIsStatusBarHidden)(value === 0)\n }\n )\n\n // Syncs FlatList scroll state with scale changes\n // When image is at default scale, enable scroll and disable pan gesture\n // When image is zoomed in, disable scroll and enable pan gesture\n useAnimatedReaction(\n () => scale.value,\n value => {\n const enableFlatListScroll = value === DEFAULT_SCALE\n runOnJS(setFlatListScrollEnabled)(enableFlatListScroll)\n runOnJS(setPanGestureEnabled)(!enableFlatListScroll)\n }\n )\n\n /* ============================\n HANDLERS\n ============================ */\n const handleOpenInBrowser = useCallback(() => {\n Linking.openURL(urlMedium || url)\n }, [urlMedium, url])\n\n const resetDismissGestures = useCallback(() => {\n dismissY.value = withSpring(0, RESET_SPRING_CONFIG)\n opacity.value = withSpring(DEFAULT_OPACITY, RESET_SPRING_CONFIG)\n }, [dismissY, opacity])\n\n const resetAllGestures = useCallback(() => {\n resetDismissGestures()\n scale.value = withSpring(DEFAULT_SCALE, RESET_SPRING_CONFIG)\n translateX.value = withSpring(DEFAULT_TRANSLATE_X, RESET_SPRING_CONFIG)\n translateY.value = withSpring(DEFAULT_TRANSLATE_Y, RESET_SPRING_CONFIG)\n savedScale.value = DEFAULT_SCALE\n savedTranslateX.value = DEFAULT_TRANSLATE_X\n savedTranslateY.value = DEFAULT_TRANSLATE_Y\n lightboxToolbarVisible.value = withSpring(1, RESET_SPRING_CONFIG)\n }, [\n resetDismissGestures,\n scale,\n translateX,\n translateY,\n savedScale,\n savedTranslateX,\n savedTranslateY,\n lightboxToolbarVisible,\n ])\n\n const handleCloseModal = useCallback(() => {\n setModalVisible(false)\n resetAllGestures()\n }, [setModalVisible, resetAllGestures])\n\n /* ============================\n UTILITY WORKLET FUNCTIONS\n 'worklet' runs functions on the UI thread, instead of the JS thread for better performance.\n ============================ */\n const getImageContainedToWindowDimensions = () => {\n 'worklet'\n\n if (!imageWidth || !imageHeight) {\n return { width: availableWindowWidth, height: availableWindowHeight }\n }\n\n const imageAspectRatio = imageWidth / imageHeight\n const windowAspectRatio = availableWindowWidth / availableWindowHeight\n\n // Constrain image width if its wider than window\n if (imageAspectRatio > windowAspectRatio) {\n return {\n width: availableWindowWidth,\n height: availableWindowWidth / imageAspectRatio,\n }\n }\n\n // Constrain image height if its taller than window\n return {\n width: availableWindowHeight * imageAspectRatio,\n height: availableWindowHeight,\n }\n }\n\n const zoomToFocalPoint = ({\n focalPointX,\n focalPointY,\n targetScale,\n currentSavedScale,\n currentSavedTranslateX,\n currentSavedTranslateY,\n }: {\n focalPointX: number\n focalPointY: number\n targetScale: number\n currentSavedScale: number\n currentSavedTranslateX: number\n currentSavedTranslateY: number\n }) => {\n 'worklet'\n\n // How far the focal point is from the center of the window\n const windowCenterX = WINDOW_WIDTH / 2\n const windowCenterY = WINDOW_HEIGHT / 2\n\n // Position of focal point relative to current image position\n const focalRelativeX = focalPointX - windowCenterX - currentSavedTranslateX\n const focalRelativeY = focalPointY - windowCenterY - currentSavedTranslateY\n\n // Calculate new translation to keep focal point under fingers\n const scaleRatio = targetScale / currentSavedScale\n const newTranslateX = currentSavedTranslateX + focalRelativeX * (1 - scaleRatio)\n const newTranslateY = currentSavedTranslateY + focalRelativeY * (1 - scaleRatio)\n\n return {\n translateX: newTranslateX,\n translateY: newTranslateY,\n }\n }\n\n const getMaxTranslationLimits = (currentScale: number) => {\n 'worklet'\n\n const { width: containedImageWidth, height: containedImageHeight } =\n getImageContainedToWindowDimensions()\n\n // Image dimensions after scaling/zooming\n const scaledWidth = containedImageWidth * currentScale\n const scaledHeight = containedImageHeight * currentScale\n\n // How much the scaled image exceeds the window container\n const excessWidth = scaledWidth - availableWindowWidth\n const excessHeight = scaledHeight - availableWindowHeight\n\n // How far the image can move in each direction before hitting window edges\n const maxTranslateX = Math.max(DEFAULT_TRANSLATE_X, excessWidth / 2)\n const maxTranslateY = Math.max(DEFAULT_TRANSLATE_Y, excessHeight / 2)\n\n return { maxTranslateX, maxTranslateY }\n }\n\n const clampDecay = (currentScale: number): { x: [number, number]; y: [number, number] } => {\n 'worklet'\n\n const { maxTranslateX, maxTranslateY } = getMaxTranslationLimits(currentScale)\n\n return {\n x: [-maxTranslateX, maxTranslateX],\n y: [-maxTranslateY, maxTranslateY],\n }\n }\n\n const clampTranslation = ({\n x,\n y,\n currentScale,\n }: {\n x: number\n y: number\n currentScale: number\n }) => {\n 'worklet'\n\n const { maxTranslateX, maxTranslateY } = getMaxTranslationLimits(currentScale)\n\n return {\n x: Math.min(maxTranslateX, Math.max(-maxTranslateX, x)),\n y: Math.min(maxTranslateY, Math.max(-maxTranslateY, y)),\n }\n }\n\n const isImageAtVerticalBoundry = (currentScale: number) => {\n 'worklet'\n\n const { height: containedImageHeight } = getImageContainedToWindowDimensions()\n\n // Calculate how much the image can exceed the window container\n const scaledHeight = containedImageHeight * currentScale\n const excessHeight = scaledHeight - availableWindowHeight\n const maxTranslateY = Math.max(DEFAULT_TRANSLATE_Y, excessHeight / 2)\n\n const currentTranslateY = translateY.value\n const panPositionTolerance = 1 // buffer to account for translateY being at a subpixel position\n\n const atTopBoundry = currentTranslateY >= maxTranslateY - panPositionTolerance\n const atBottomBoundry = currentTranslateY <= -maxTranslateY + panPositionTolerance\n\n return atTopBoundry || atBottomBoundry\n }\n\n /* ============================\n UTILITY FLATLIST FUNCTIONS\n Supports the image gallery layout and swipe functionality.\n ============================ */\n\n // Used in tandem with FlatList's initialScrollIndex to quickly calculate the position and size of each image before they load.\n const getItemLayout = useCallback(\n (_: unknown, index: number) => ({\n length: WINDOW_WIDTH,\n offset: WINDOW_WIDTH * index,\n index,\n }),\n []\n )\n\n // Captures the current image's index after the FlatList finishes its scroll animation.\n // Used in tandem with onViewableItemsChanged to ensure the final value for currentImageIndex is set.\n const onMomentumScrollEnd = useCallback(\n (event: NativeSyntheticEvent<NativeScrollEvent>) => {\n // Calculate the index of the image that is currently visible\n const imageOffsetX = event.nativeEvent.contentOffset.x\n const newImageIndex = Math.round(imageOffsetX / WINDOW_WIDTH)\n\n // Check if the image index has changed and the FlatList didn't scroll past the first or last image\n const didImageIndexChange = newImageIndex !== currentImageIndex\n const isImageIndexWithinBounds = newImageIndex >= 0 && newImageIndex < imageAttachments.length\n\n if (didImageIndexChange && isImageIndexWithinBounds) {\n setCurrentImageIndex(newImageIndex)\n }\n },\n [currentImageIndex, imageAttachments.length]\n )\n\n // Supplements onMomentumScrollEnd by capturing the current image's index while the FlatList is actively scrolling.\n // Used in tandem with viewabilityConfig to trigger when the image is 50% visible in the window.\n const onViewableItemsChanged = useCallback(\n ({ viewableItems }: { viewableItems: ViewToken<DenormalizedMessageAttachmentResource>[] }) => {\n if (viewableItems.length === 0) return\n\n // Use the first viewable item which is enforced by the FlatList's pagingEnabled prop that allows only two images to be visible at a time when scrolling.\n const firstViewableItem = viewableItems[0]\n const newIndex = firstViewableItem.index\n\n if (newIndex !== null && newIndex !== currentImageIndex) {\n setCurrentImageIndex(newIndex)\n }\n },\n [currentImageIndex]\n )\n\n /* ============================\n GESTURES\n ============================ */\n const singleTapGesture = Gesture.Tap()\n .minPointers(SINGLE_FINGER_POINTER)\n .numberOfTaps(1)\n .onStart(() => {\n lightboxToolbarVisible.value = withSpring(\n lightboxToolbarVisible.value > 0.5 ? 0 : 1,\n RESET_SPRING_CONFIG\n )\n })\n\n const doubleTapGesture = Gesture.Tap()\n .maxDeltaX(MAX_TRAVEL_DISTANCE_FOR_DOUBLE_TAP_PLATFORM)\n .maxDeltaY(MAX_TRAVEL_DISTANCE_FOR_DOUBLE_TAP_PLATFORM)\n .maxDistance(MAX_TRAVEL_DISTANCE_FOR_DOUBLE_TAP_PLATFORM)\n .minPointers(SINGLE_FINGER_POINTER)\n .numberOfTaps(2)\n .onStart(e => {\n const isZoomedIn = scale.value > DEFAULT_SCALE\n if (isZoomedIn) {\n runOnJS(resetAllGestures)()\n } else {\n // Hide toolbar when starting to zoom\n lightboxToolbarVisible.value = withSpring(0, RESET_SPRING_CONFIG)\n\n // Zoom to 2x at tap location\n const newTranslation = zoomToFocalPoint({\n focalPointX: e.x,\n focalPointY: e.y,\n targetScale: DOUBLE_TAP_ZOOM_SCALE,\n currentSavedScale: savedScale.value,\n currentSavedTranslateX: savedTranslateX.value,\n currentSavedTranslateY: savedTranslateY.value,\n })\n\n // Apply clamping to ensure image edges remain within window boundaries\n const clampedTranslate = clampTranslation({\n x: newTranslation.translateX,\n y: newTranslation.translateY,\n currentScale: DOUBLE_TAP_ZOOM_SCALE,\n })\n\n // Animate to new scale and translation\n scale.value = withSpring(DOUBLE_TAP_ZOOM_SCALE, RESET_SPRING_CONFIG)\n translateX.value = withSpring(clampedTranslate.x, RESET_SPRING_CONFIG)\n translateY.value = withSpring(clampedTranslate.y, RESET_SPRING_CONFIG)\n\n // Update saved state for next gesture\n savedScale.value = DOUBLE_TAP_ZOOM_SCALE\n savedTranslateX.value = clampedTranslate.x\n savedTranslateY.value = clampedTranslate.y\n }\n })\n\n const pinchGesture = Gesture.Pinch()\n .onStart(e => {\n focalX.value = e.focalX\n focalY.value = e.focalY\n\n // Hide toolbar when starting to zoom\n lightboxToolbarVisible.value = withSpring(0, RESET_SPRING_CONFIG)\n\n // Ensure that pinch accounts for the decay animation and starts from the true current position.\n savedTranslateX.value = translateX.value\n savedTranslateY.value = translateY.value\n })\n .onUpdate(e => {\n // Zoom image in/out within scale limits\n const newScale = savedScale.value * e.scale\n const newScaleClamped = Math.min(\n MAX_SCALE + MAX_SCALE_OVERSHOOT,\n Math.max(MIN_SCALE, newScale)\n )\n scale.value = newScaleClamped\n\n // Calculate new translation to keep focal point under fingers\n const newTranslation = zoomToFocalPoint({\n focalPointX: focalX.value,\n focalPointY: focalY.value,\n targetScale: newScaleClamped,\n currentSavedScale: savedScale.value,\n currentSavedTranslateX: savedTranslateX.value,\n currentSavedTranslateY: savedTranslateY.value,\n })\n\n // Apply translation without clamping to ensure focal point doesn't jump during gesture\n translateX.value = newTranslation.translateX\n translateY.value = newTranslation.translateY\n })\n .onEnd(() => {\n const currentScale = scale.value\n\n // Dismiss modal if fully zoomed out\n if (currentScale <= MIN_SCALE) {\n runOnJS(handleCloseModal)()\n return\n }\n\n // Reset all gestures if image is near default scale\n const isNearDefaultScale = currentScale <= DEFAULT_SCALE + 0.1\n if (isNearDefaultScale) {\n runOnJS(resetAllGestures)()\n return\n }\n\n // Check if overshooting the max scale and clamp it\n const finalScale = currentScale > MAX_SCALE ? MAX_SCALE : currentScale\n\n // Recalculate translation using focal point to always return to the same position when image is overshooting the max scale\n const newTranslation = zoomToFocalPoint({\n focalPointX: focalX.value,\n focalPointY: focalY.value,\n targetScale: finalScale,\n currentSavedScale: savedScale.value,\n currentSavedTranslateX: savedTranslateX.value,\n currentSavedTranslateY: savedTranslateY.value,\n })\n\n // Ensure image edges remain within window boundaries\n const clampedTranslate = clampTranslation({\n x: newTranslation.translateX,\n y: newTranslation.translateY,\n currentScale: finalScale,\n })\n\n // Animate to position and scale limits with a spring\n scale.value = withSpring(finalScale, RESET_SPRING_CONFIG)\n translateX.value = withSpring(clampedTranslate.x, RESET_SPRING_CONFIG)\n translateY.value = withSpring(clampedTranslate.y, RESET_SPRING_CONFIG)\n\n // Save state for next gesture\n savedScale.value = finalScale\n savedTranslateX.value = clampedTranslate.x\n savedTranslateY.value = clampedTranslate.y\n })\n\n const panGesture = Gesture.Pan()\n .minDistance(MIN_DISTANCE_FOR_PAN)\n .minPointers(SINGLE_FINGER_POINTER)\n .maxPointers(SINGLE_FINGER_POINTER)\n .enabled(panGestureEnabled)\n .onStart(() => {\n // Update saved position to current animated position\n // This ensures smooth continuation if decay is running\n savedTranslateX.value = translateX.value\n savedTranslateY.value = translateY.value\n })\n .onUpdate(e => {\n // Only pan if image is zoomed in\n if (scale.value <= DEFAULT_SCALE) return\n\n const newTranslateX = savedTranslateX.value + e.translationX\n const newTranslateY = savedTranslateY.value + e.translationY\n\n // Ensure image edges remain within window boundaries\n const clampedTranslate = clampTranslation({\n x: newTranslateX,\n y: newTranslateY,\n currentScale: scale.value,\n })\n translateX.value = clampedTranslate.x\n translateY.value = clampedTranslate.y\n })\n .onEnd(e => {\n // Prevents saving pan position if image is zoomed out while panning\n if (scale.value <= DEFAULT_SCALE) return\n\n const clampDecayBounds = clampDecay(scale.value)\n\n translateX.value = withDecay(\n {\n velocity: e.velocityX * DECAY_VELOCITY_FACTOR,\n clamp: clampDecayBounds.x,\n deceleration: 0.996,\n },\n finished => {\n if (finished) {\n savedTranslateX.value = translateX.value\n }\n }\n )\n\n translateY.value = withDecay(\n {\n velocity: e.velocityY * DECAY_VELOCITY_FACTOR,\n clamp: clampDecayBounds.y,\n deceleration: 0.996,\n },\n finished => {\n if (finished) {\n savedTranslateY.value = translateY.value\n }\n }\n )\n })\n\n const panToDismissModalGesture = Gesture.Pan()\n .onUpdate(e => {\n const atDefaultScale = scale.value === DEFAULT_SCALE\n\n // Calculate zoom conditions for dismissing modal\n const atVerticalBoundry = isImageAtVerticalBoundry(scale.value)\n const panDirectionIsPrimarilyVertical = Math.abs(e.translationY) > Math.abs(e.translationX)\n const canDismissWhileZoomed = atVerticalBoundry && panDirectionIsPrimarilyVertical\n\n if (!(atDefaultScale || canDismissWhileZoomed)) return\n\n // Fade image if its been panned past 50% of the dismiss threshold\n const panDistance = Math.abs(e.translationY)\n const halfThreshold = DISMISS_PAN_THRESHOLD / 2\n const fadeDistance = Math.max(0, panDistance - halfThreshold)\n const fadeProgress = fadeDistance / halfThreshold\n\n opacity.value = Math.max(0, DEFAULT_OPACITY - fadeProgress)\n dismissY.value = e.translationY\n })\n .onEnd(() => {\n const exceededDismissThreshold = Math.abs(dismissY.value) > DISMISS_PAN_THRESHOLD\n\n if (exceededDismissThreshold) {\n runOnJS(handleCloseModal)()\n } else {\n runOnJS(resetDismissGestures)()\n }\n })\n\n /* ==============================\n COMPOSE GESTURES\n ================================= */\n // Race between pinch and pan ensures only one is active at a time, preserving focal point logic\n const pinchOrPanGestures = Gesture.Race(pinchGesture, panGesture)\n\n // Exclusive race ensures single tap doesn't interfere with double tap\n const tapGestures = Gesture.Exclusive(doubleTapGesture, singleTapGesture)\n\n // Race between tap gestures and pinch/pan\n const transformImageGestures = Gesture.Race(tapGestures, pinchOrPanGestures)\n\n // Dismiss can work simultaneously with all gestures\n const composedGesture = Gesture.Simultaneous(transformImageGestures, panToDismissModalGesture)\n\n return (\n <Modal visible={visible} transparent animationType=\"fade\" onRequestClose={handleCloseModal}>\n <StatusBar\n barStyle=\"light-content\"\n hidden={isStatusBarHidden}\n animated\n showHideTransition=\"slide\"\n />\n <GestureHandlerRootView>\n <GestureDetector gesture={composedGesture}>\n <PreventPressEventsBubbling>\n <FlatList\n data={imageAttachments}\n renderItem={({ item, index }) => (\n <GestureImage\n item={item}\n gesturesEnabled={index === currentImageIndex}\n scale={scale}\n translateX={translateX}\n translateY={translateY}\n dismissY={dismissY}\n opacity={opacity}\n />\n )}\n keyExtractor={(item, index) => `${item.id}-${index}`}\n horizontal\n pagingEnabled\n scrollEnabled={flatListScrollEnabled}\n showsHorizontalScrollIndicator={false}\n initialScrollIndex={initialImageIndex}\n getItemLayout={getItemLayout}\n onMomentumScrollEnd={onMomentumScrollEnd}\n onViewableItemsChanged={onViewableItemsChanged}\n viewabilityConfig={{\n itemVisiblePercentThreshold: 50, // 50% of the image must be visible in the window to be considered viewable\n }}\n style={styles.gallery}\n contentContainerStyle={styles.galleryContentContainer}\n />\n </PreventPressEventsBubbling>\n </GestureDetector>\n </GestureHandlerRootView>\n <LightboxToolbarHeader\n metaProps={metaProps}\n lightboxToolbarVisible={lightboxToolbarVisible}\n isStatusBarHidden={isStatusBarHidden}\n handleOpenInBrowser={handleOpenInBrowser}\n handleCloseModal={handleCloseModal}\n />\n {imageAttachments.length > 1 && (\n <LightboxToolbarFooter\n lightboxToolbarVisible={lightboxToolbarVisible}\n totalImages={imageAttachments.length}\n currentImageIndex={currentImageIndex}\n />\n )}\n </Modal>\n )\n}\n\nconst PreventPressEventsBubbling = ({ children }: { children: React.ReactNode }) => {\n return (\n <Pressable onLongPress={() => {}} onPress={() => {}}>\n {children}\n </Pressable>\n )\n}\n\ninterface GestureImageProps {\n item: DenormalizedMessageAttachmentResource\n gesturesEnabled: boolean\n scale: SharedValue<number>\n translateX: SharedValue<number>\n translateY: SharedValue<number>\n dismissY: SharedValue<number>\n opacity: SharedValue<number>\n}\n\nconst GestureImage = ({\n item,\n gesturesEnabled,\n scale,\n translateX,\n translateY,\n dismissY,\n opacity,\n}: GestureImageProps) => {\n const styles = useStyles()\n const { url: itemUrl, urlMedium: itemUrlMedium } = item.attributes\n\n const animatedImageStyles = useAnimatedStyle(() => {\n return {\n transform: [\n { translateX: gesturesEnabled ? translateX.value : DEFAULT_TRANSLATE_X },\n { translateY: gesturesEnabled ? translateY.value + dismissY.value : DEFAULT_TRANSLATE_Y },\n { scale: gesturesEnabled ? scale.value : DEFAULT_SCALE },\n ],\n opacity: opacity.value,\n }\n })\n\n return (\n <Image\n source={{ uri: itemUrlMedium || itemUrl }}\n style={styles.gestureImage}\n animatedImageStyle={animatedImageStyles}\n loadingBackgroundStyles={styles.gestureImageLoading}\n resizeMode=\"contain\"\n alt=\"\"\n />\n )\n}\n\ninterface LightboxToolbarHeaderProps {\n metaProps: MetaProps\n lightboxToolbarVisible: SharedValue<number>\n isStatusBarHidden: boolean\n handleOpenInBrowser: () => void\n handleCloseModal: () => void\n}\n\nconst LightboxToolbarHeader = ({\n metaProps,\n lightboxToolbarVisible,\n isStatusBarHidden,\n handleOpenInBrowser,\n handleCloseModal,\n}: LightboxToolbarHeaderProps) => {\n const styles = useStyles()\n const { authorName, createdAt } = metaProps\n\n const animatedHeaderStyles = useAnimatedStyle(() => ({\n opacity: lightboxToolbarVisible.value,\n transform: [\n { translateY: (1 - lightboxToolbarVisible.value) * -20 }, // slide up when hiding\n ],\n }))\n\n return (\n <Animated.View\n style={[styles.lightboxToolbar, styles.lightboxToolbarHeader, animatedHeaderStyles]}\n accessibilityRole=\"toolbar\"\n >\n <View style={styles.lightboxToolbarHeaderMetaContainer}>\n <Heading variant=\"h3\" style={styles.lightboxToolbarHeaderTitle} numberOfLines={1}>\n {authorName}\n </Heading>\n <Text variant=\"tertiary\" style={styles.lightboxToolbarHeaderSubtitle}>\n {formatDatePreview(createdAt)}\n </Text>\n </View>\n <IconButton\n onPress={handleOpenInBrowser}\n disabled={isStatusBarHidden}\n name=\"general.newWindow\"\n accessibilityRole=\"link\"\n accessibilityLabel=\"Open image in browser\"\n accessibilityHint=\"Image can be downloaded and shared through the browser.\"\n style={styles.lightboxToolbarButton}\n iconStyle={styles.lightboxToolbarButtonIcon}\n size=\"lg\"\n />\n <IconButton\n onPress={handleCloseModal}\n disabled={isStatusBarHidden}\n name=\"general.x\"\n accessibilityLabel=\"Close image\"\n style={styles.lightboxToolbarButton}\n iconStyle={styles.lightboxToolbarButtonIcon}\n size=\"lg\"\n />\n </Animated.View>\n )\n}\n\ninterface LightboxToolbarFooterProps {\n lightboxToolbarVisible: SharedValue<number>\n totalImages: number\n currentImageIndex: number\n}\n\nconst LightboxToolbarFooter = ({\n lightboxToolbarVisible,\n totalImages,\n currentImageIndex,\n}: LightboxToolbarFooterProps) => {\n const styles = useStyles()\n\n const animatedFooterStyles = useAnimatedStyle(() => ({\n opacity: lightboxToolbarVisible.value,\n transform: [\n { translateY: (1 - lightboxToolbarVisible.value) * 20 }, // slide down when showing\n ],\n }))\n\n return (\n <Animated.View\n style={[styles.lightboxToolbar, styles.lightboxToolbarFooter, animatedFooterStyles]}\n accessibilityRole=\"toolbar\"\n >\n <Text style={styles.lightboxToolbarFooterText}>\n {currentImageIndex + 1} of {totalImages}\n </Text>\n </Animated.View>\n )\n}\n\ninterface UseStylesProps {\n imageWidth?: number\n imageHeight?: number\n}\n\nconst useStyles = ({ imageWidth = 100, imageHeight = 100 }: UseStylesProps = {}) => {\n const { top, bottom } = useSafeAreaInsets()\n const backgroundColor = tokens.colorNeutral7\n const transparentBackgroundColor = useMemo(\n () => colorFunction(backgroundColor).alpha(0.8).toString(),\n [backgroundColor]\n )\n\n return StyleSheet.create({\n container: {\n maxWidth: '100%',\n },\n attachmentImageWrapper: {\n width: '100%',\n minWidth: 200,\n aspectRatio: imageWidth / imageHeight,\n },\n image: {\n borderRadius: 8,\n },\n gallery: {\n backgroundColor,\n },\n galleryContentContainer: {\n paddingTop: top,\n paddingBottom: bottom,\n },\n gestureImage: {\n height: '100%',\n width: WINDOW_WIDTH,\n backgroundColor: 'transparent',\n },\n gestureImageLoading: {\n backgroundColor,\n },\n lightboxToolbar: {\n width: '100%',\n position: 'absolute',\n flexDirection: 'row',\n alignItems: 'center',\n gap: 20,\n paddingHorizontal: 16,\n backgroundColor: transparentBackgroundColor,\n },\n lightboxToolbarButton: {\n backgroundColor,\n height: 40,\n width: 40,\n borderRadius: 50,\n borderWidth: 1,\n borderColor: tokens.colorNeutral24,\n },\n lightboxToolbarButtonIcon: {\n color: tokens.colorNeutral88,\n },\n lightboxToolbarHeader: {\n top: 0,\n paddingTop: top + 16,\n paddingBottom: 8,\n },\n lightboxToolbarHeaderMetaContainer: {\n flex: 1,\n },\n lightboxToolbarHeaderTitle: {\n marginRight: 'auto',\n flexShrink: 1,\n color: tokens.colorNeutral88,\n },\n lightboxToolbarHeaderSubtitle: {\n color: tokens.colorNeutral68,\n },\n lightboxToolbarFooter: {\n justifyContent: 'center',\n bottom: 0,\n paddingTop: 8,\n paddingBottom: bottom + 16,\n },\n lightboxToolbarFooterText: {\n color: tokens.colorNeutral88,\n fontWeight: platformFontWeightMedium,\n },\n })\n}\n"]}
1
+ {"version":3,"file":"image_attachment.js","sourceRoot":"","sources":["../../../../src/components/conversation/attachments/image_attachment.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useMemo, useState, Dispatch, SetStateAction, useCallback } from 'react'\nimport {\n StatusBar,\n StyleSheet,\n Modal,\n View,\n Linking,\n Dimensions,\n NativeSyntheticEvent,\n NativeScrollEvent,\n ViewToken,\n} from 'react-native'\nimport { useSafeAreaInsets } from 'react-native-safe-area-context'\nimport {\n FlatList,\n Gesture,\n GestureDetector,\n GestureHandlerRootView,\n} from 'react-native-gesture-handler'\nimport Animated, {\n runOnJS,\n useAnimatedStyle,\n useAnimatedReaction,\n useSharedValue,\n withSpring,\n withDecay,\n SharedValue,\n} from 'react-native-reanimated'\nimport { tokens } from '../../../vendor/tapestry/tokens'\nimport { IconButton, Image, Heading, Text } from '../../display'\nimport colorFunction from 'color'\nimport { formatDatePreview } from '../../../utils/date'\nimport { DenormalizedMessageAttachmentResource } from '../../../types/resources/denormalized_attachment_resource'\nimport { PlatformPressable } from '@react-navigation/elements'\nimport { useTheme } from '../../../hooks'\nimport { platformFontWeightMedium } from '../../../utils'\n\nconst { width: WINDOW_WIDTH, height: WINDOW_HEIGHT } = Dimensions.get('window')\nconst DISMISS_PAN_THRESHOLD = 250\nconst MIN_DISTANCE_FOR_PAN = 10 // Higher threshold gives pinching priority\nconst SINGLE_FINGER_POINTER = 1 // Single-finger panning / tapping helps to avoid conflicts with pinching\nconst DEFAULT_OPACITY = 1\nconst DEFAULT_TRANSLATE_X = 0\nconst DEFAULT_TRANSLATE_Y = 0\nconst DEFAULT_SCALE = 1\nconst MIN_SCALE = 0.5\nconst MAX_SCALE = 5\nconst MAX_SCALE_OVERSHOOT = 5\nconst DOUBLE_TAP_ZOOM_SCALE = 2\nconst DECAY_VELOCITY_FACTOR = 0.4\nconst RESET_SPRING_CONFIG = {\n damping: 20,\n stiffness: 150,\n}\n\nexport type MetaProps = {\n authorName: string\n createdAt: string\n}\n\nexport function ImageAttachment({\n attachment,\n imageAttachments,\n currentImageIndex,\n metaProps,\n onMessageAttachmentLongPress,\n}: {\n attachment: DenormalizedMessageAttachmentResource\n imageAttachments: DenormalizedMessageAttachmentResource[]\n currentImageIndex: number\n metaProps: MetaProps\n onMessageAttachmentLongPress: (attachment: DenormalizedMessageAttachmentResource) => void\n}) {\n const { attributes } = attachment\n const { url, urlMedium, filename, metadata = {} } = attributes\n const { colors } = useTheme()\n\n const styles = useStyles({ imageWidth: metadata.width, imageHeight: metadata.height })\n const [visible, setVisible] = useState(false)\n\n // Force modal to remount with fresh state\n // Fixes a bug where dismissing the modal too quickly causes the Reanimated shared values (like toolbarVisible) to not reset.\n const [modalKey, setModalKey] = useState(0)\n\n return (\n <>\n <PlatformPressable\n style={styles.container}\n onPress={() => {\n setModalKey(prev => prev + 1)\n setVisible(true)\n }}\n onLongPress={() => onMessageAttachmentLongPress(attachment)}\n android_ripple={{ color: colors.androidRippleNeutral, foreground: true }}\n accessibilityHint=\"Long press for more options\"\n >\n <Image\n source={{ uri: urlMedium || url }}\n style={styles.image}\n wrapperStyle={styles.attachmentImageWrapper}\n alt={filename}\n />\n </PlatformPressable>\n <LightboxModal\n key={modalKey}\n visible={visible}\n setModalVisible={setVisible}\n imageAttachments={imageAttachments}\n initialImageIndex={currentImageIndex}\n metaProps={metaProps}\n />\n </>\n )\n}\n\ninterface LightboxModalProps {\n visible: boolean\n setModalVisible: Dispatch<SetStateAction<boolean>>\n imageAttachments: DenormalizedMessageAttachmentResource[]\n initialImageIndex: number\n metaProps: MetaProps\n}\n\nconst LightboxModal = ({\n visible,\n setModalVisible,\n imageAttachments,\n initialImageIndex,\n metaProps,\n}: LightboxModalProps) => {\n const styles = useStyles()\n const insets = useSafeAreaInsets()\n const [currentImageIndex, setCurrentImageIndex] = useState(initialImageIndex)\n\n // Get current image data\n const currentImage = imageAttachments[currentImageIndex]\n const { url, urlMedium, metadata = {} } = currentImage.attributes\n const imageWidth = metadata.width\n const imageHeight = metadata.height\n\n // Calculate available space for image display\n const availableWindowWidth = WINDOW_WIDTH\n const availableWindowHeight = WINDOW_HEIGHT - insets.top - insets.bottom\n\n /* ============================\n ANIMATION VALUES\n ============================ */\n // Native State:\n const dismissY = useSharedValue(0) // vertical distance to dismiss modal\n const opacity = useSharedValue(DEFAULT_OPACITY) // opacity of modal\n const scale = useSharedValue(DEFAULT_SCALE) // zoom level of image\n const focalX = useSharedValue(0) // focal point of image between fingers\n const focalY = useSharedValue(0) // focal point of image between fingers\n const translateX = useSharedValue(DEFAULT_TRANSLATE_X) // horizontal distance to pan image\n const translateY = useSharedValue(DEFAULT_TRANSLATE_Y) // vertical distance to pan image\n const savedScale = useSharedValue(DEFAULT_SCALE) // previous zoom level\n const savedTranslateX = useSharedValue(DEFAULT_TRANSLATE_X) // previous horizontal position\n const savedTranslateY = useSharedValue(DEFAULT_TRANSLATE_Y) // previous vertical position\n const lightboxToolbarVisible = useSharedValue(1) // toolbar visibility state\n\n // React (JS) State:\n const [isStatusBarHidden, setIsStatusBarHidden] = useState(false)\n const [flatListScrollEnabled, setFlatListScrollEnabled] = useState(true)\n\n // Syncs toolbar useSharedValue state with React's state so that the status bar can be hidden/shown based on the toolbar's animation\n useAnimatedReaction(\n () => lightboxToolbarVisible.value,\n value => {\n runOnJS(setIsStatusBarHidden)(value === 0)\n }\n )\n\n /* ============================\n HANDLERS\n ============================ */\n const handleOpenInBrowser = useCallback(() => {\n Linking.openURL(urlMedium || url)\n }, [urlMedium, url])\n\n // Helper to enable or disable FlatList scroll state after gestures have ended\n const enableFlatListScroll = useCallback((enableScroll: boolean) => {\n setFlatListScrollEnabled(enableScroll)\n }, [])\n\n const resetDismissGestures = useCallback(() => {\n dismissY.value = withSpring(0, RESET_SPRING_CONFIG)\n opacity.value = withSpring(DEFAULT_OPACITY, RESET_SPRING_CONFIG)\n }, [dismissY, opacity])\n\n const resetAllGestures = useCallback(() => {\n resetDismissGestures()\n scale.value = withSpring(DEFAULT_SCALE, RESET_SPRING_CONFIG)\n translateX.value = withSpring(DEFAULT_TRANSLATE_X, RESET_SPRING_CONFIG)\n translateY.value = withSpring(DEFAULT_TRANSLATE_Y, RESET_SPRING_CONFIG)\n savedScale.value = DEFAULT_SCALE\n savedTranslateX.value = DEFAULT_TRANSLATE_X\n savedTranslateY.value = DEFAULT_TRANSLATE_Y\n lightboxToolbarVisible.value = withSpring(1, RESET_SPRING_CONFIG)\n enableFlatListScroll(true)\n }, [\n resetDismissGestures,\n scale,\n translateX,\n translateY,\n savedScale,\n savedTranslateX,\n savedTranslateY,\n lightboxToolbarVisible,\n enableFlatListScroll,\n ])\n\n const handleCloseModal = useCallback(() => {\n setModalVisible(false)\n resetAllGestures()\n }, [setModalVisible, resetAllGestures])\n\n /* ============================\n UTILITY WORKLET FUNCTIONS\n 'worklet' runs functions on the UI thread, instead of the JS thread for better performance.\n ============================ */\n const getImageContainedToWindowDimensions = () => {\n 'worklet'\n\n if (!imageWidth || !imageHeight) {\n return { width: availableWindowWidth, height: availableWindowHeight }\n }\n\n const imageAspectRatio = imageWidth / imageHeight\n const windowAspectRatio = availableWindowWidth / availableWindowHeight\n\n // Constrain image width if its wider than window\n if (imageAspectRatio > windowAspectRatio) {\n return {\n width: availableWindowWidth,\n height: availableWindowWidth / imageAspectRatio,\n }\n }\n\n // Constrain image height if its taller than window\n return {\n width: availableWindowHeight * imageAspectRatio,\n height: availableWindowHeight,\n }\n }\n\n const zoomToFocalPoint = ({\n focalPointX,\n focalPointY,\n targetScale,\n currentSavedScale,\n currentSavedTranslateX,\n currentSavedTranslateY,\n }: {\n focalPointX: number\n focalPointY: number\n targetScale: number\n currentSavedScale: number\n currentSavedTranslateX: number\n currentSavedTranslateY: number\n }) => {\n 'worklet'\n\n // How far the focal point is from the center of the window\n const windowCenterX = WINDOW_WIDTH / 2\n const windowCenterY = WINDOW_HEIGHT / 2\n\n // Position of focal point relative to current image position\n const focalRelativeX = focalPointX - windowCenterX - currentSavedTranslateX\n const focalRelativeY = focalPointY - windowCenterY - currentSavedTranslateY\n\n // Calculate new translation to keep focal point under fingers\n const scaleRatio = targetScale / currentSavedScale\n const newTranslateX = currentSavedTranslateX + focalRelativeX * (1 - scaleRatio)\n const newTranslateY = currentSavedTranslateY + focalRelativeY * (1 - scaleRatio)\n\n return {\n translateX: newTranslateX,\n translateY: newTranslateY,\n }\n }\n\n const getMaxTranslationLimits = (currentScale: number) => {\n 'worklet'\n\n const { width: containedImageWidth, height: containedImageHeight } =\n getImageContainedToWindowDimensions()\n\n // Image dimensions after scaling/zooming\n const scaledWidth = containedImageWidth * currentScale\n const scaledHeight = containedImageHeight * currentScale\n\n // How much the scaled image exceeds the window container\n const excessWidth = scaledWidth - availableWindowWidth\n const excessHeight = scaledHeight - availableWindowHeight\n\n // How far the image can move in each direction before hitting window edges\n const maxTranslateX = Math.max(DEFAULT_TRANSLATE_X, excessWidth / 2)\n const maxTranslateY = Math.max(DEFAULT_TRANSLATE_Y, excessHeight / 2)\n\n return { maxTranslateX, maxTranslateY }\n }\n\n const clampDecay = (currentScale: number): { x: [number, number]; y: [number, number] } => {\n 'worklet'\n\n const { maxTranslateX, maxTranslateY } = getMaxTranslationLimits(currentScale)\n\n return {\n x: [-maxTranslateX, maxTranslateX],\n y: [-maxTranslateY, maxTranslateY],\n }\n }\n\n const clampTranslation = ({\n x,\n y,\n currentScale,\n }: {\n x: number\n y: number\n currentScale: number\n }) => {\n 'worklet'\n\n const { maxTranslateX, maxTranslateY } = getMaxTranslationLimits(currentScale)\n\n return {\n x: Math.min(maxTranslateX, Math.max(-maxTranslateX, x)),\n y: Math.min(maxTranslateY, Math.max(-maxTranslateY, y)),\n }\n }\n\n const isImageAtVerticalBoundry = (currentScale: number) => {\n 'worklet'\n\n const { height: containedImageHeight } = getImageContainedToWindowDimensions()\n\n // Calculate how much the image can exceed the window container\n const scaledHeight = containedImageHeight * currentScale\n const excessHeight = scaledHeight - availableWindowHeight\n const maxTranslateY = Math.max(DEFAULT_TRANSLATE_Y, excessHeight / 2)\n\n const currentTranslateY = translateY.value\n const panPositionTolerance = 1 // buffer to account for translateY being at a subpixel position\n\n const atTopBoundry = currentTranslateY >= maxTranslateY - panPositionTolerance\n const atBottomBoundry = currentTranslateY <= -maxTranslateY + panPositionTolerance\n\n return atTopBoundry || atBottomBoundry\n }\n\n /* ============================\n UTILITY FLATLIST FUNCTIONS\n Supports the image gallery layout and swipe functionality.\n ============================ */\n\n // Used in tandem with FlatList's initialScrollIndex to quickly calculate the position and size of each image before they load.\n const getItemLayout = useCallback(\n (_: unknown, index: number) => ({\n length: WINDOW_WIDTH,\n offset: WINDOW_WIDTH * index,\n index,\n }),\n []\n )\n\n // Captures the current image's index after the FlatList finishes its scroll animation.\n // Used in tandem with onViewableItemsChanged to ensure the final value for currentImageIndex is set.\n const onMomentumScrollEnd = useCallback(\n (event: NativeSyntheticEvent<NativeScrollEvent>) => {\n // Calculate the index of the image that is currently visible\n const imageOffsetX = event.nativeEvent.contentOffset.x\n const newImageIndex = Math.round(imageOffsetX / WINDOW_WIDTH)\n\n // Check if the image index has changed and the FlatList didn't scroll past the first or last image\n const didImageIndexChange = newImageIndex !== currentImageIndex\n const isImageIndexWithinBounds = newImageIndex >= 0 && newImageIndex < imageAttachments.length\n\n if (didImageIndexChange && isImageIndexWithinBounds) {\n setCurrentImageIndex(newImageIndex)\n }\n },\n [currentImageIndex, imageAttachments.length]\n )\n\n // Supplements onMomentumScrollEnd by capturing the current image's index while the FlatList is actively scrolling.\n // Used in tandem with viewabilityConfig to trigger when the image is 50% visible in the window.\n const onViewableItemsChanged = useCallback(\n ({ viewableItems }: { viewableItems: ViewToken<DenormalizedMessageAttachmentResource>[] }) => {\n if (viewableItems.length === 0) return\n\n // Use the first viewable item which is enforced by the FlatList's pagingEnabled prop that allows only two images to be visible at a time when scrolling.\n const firstViewableItem = viewableItems[0]\n const newIndex = firstViewableItem.index\n\n if (newIndex !== null && newIndex !== currentImageIndex) {\n setCurrentImageIndex(newIndex)\n }\n },\n [currentImageIndex]\n )\n\n /* ============================\n GESTURES\n ============================ */\n const singleTapGesture = Gesture.Tap()\n .minPointers(SINGLE_FINGER_POINTER)\n .numberOfTaps(1)\n .onStart(() => {\n lightboxToolbarVisible.value = withSpring(\n lightboxToolbarVisible.value > 0.5 ? 0 : 1,\n RESET_SPRING_CONFIG\n )\n })\n\n const doubleTapGesture = Gesture.Tap()\n .minPointers(SINGLE_FINGER_POINTER)\n .numberOfTaps(2)\n .onStart(e => {\n const isZoomedIn = scale.value > DEFAULT_SCALE\n if (isZoomedIn) {\n runOnJS(resetAllGestures)()\n } else {\n // Hide toolbar when starting to zoom\n lightboxToolbarVisible.value = withSpring(0, RESET_SPRING_CONFIG)\n\n // Zoom to 2x at tap location\n const newTranslation = zoomToFocalPoint({\n focalPointX: e.x,\n focalPointY: e.y,\n targetScale: DOUBLE_TAP_ZOOM_SCALE,\n currentSavedScale: savedScale.value,\n currentSavedTranslateX: savedTranslateX.value,\n currentSavedTranslateY: savedTranslateY.value,\n })\n\n // Apply clamping to ensure image edges remain within window boundaries\n const clampedTranslate = clampTranslation({\n x: newTranslation.translateX,\n y: newTranslation.translateY,\n currentScale: DOUBLE_TAP_ZOOM_SCALE,\n })\n\n // Animate to new scale and translation\n scale.value = withSpring(DOUBLE_TAP_ZOOM_SCALE, RESET_SPRING_CONFIG)\n translateX.value = withSpring(clampedTranslate.x, RESET_SPRING_CONFIG)\n translateY.value = withSpring(clampedTranslate.y, RESET_SPRING_CONFIG)\n\n // Update saved state for next gesture\n savedScale.value = DOUBLE_TAP_ZOOM_SCALE\n savedTranslateX.value = clampedTranslate.x\n savedTranslateY.value = clampedTranslate.y\n\n // Disable FlatList scroll since image is now zoomed\n runOnJS(enableFlatListScroll)(false)\n }\n })\n\n const pinchGesture = Gesture.Pinch()\n .onStart(e => {\n focalX.value = e.focalX\n focalY.value = e.focalY\n\n // Hide toolbar when starting to zoom\n lightboxToolbarVisible.value = withSpring(0, RESET_SPRING_CONFIG)\n\n // Ensure that pinch accounts for the decay animation and starts from the true current position.\n savedTranslateX.value = translateX.value\n savedTranslateY.value = translateY.value\n })\n .onUpdate(e => {\n // Zoom image in/out within scale limits\n const newScale = savedScale.value * e.scale\n const newScaleClamped = Math.min(\n MAX_SCALE + MAX_SCALE_OVERSHOOT,\n Math.max(MIN_SCALE, newScale)\n )\n scale.value = newScaleClamped\n\n // Calculate new translation to keep focal point under fingers\n const newTranslation = zoomToFocalPoint({\n focalPointX: focalX.value,\n focalPointY: focalY.value,\n targetScale: newScaleClamped,\n currentSavedScale: savedScale.value,\n currentSavedTranslateX: savedTranslateX.value,\n currentSavedTranslateY: savedTranslateY.value,\n })\n\n // Apply translation without clamping to ensure focal point doesn't jump during gesture\n translateX.value = newTranslation.translateX\n translateY.value = newTranslation.translateY\n })\n .onEnd(() => {\n const currentScale = scale.value\n\n // Dismiss modal if fully zoomed out\n if (currentScale <= MIN_SCALE) {\n runOnJS(handleCloseModal)()\n return\n }\n\n // Reset all gestures if image is near default scale\n const isNearDefaultScale = currentScale <= DEFAULT_SCALE + 0.1\n if (isNearDefaultScale) {\n runOnJS(resetAllGestures)()\n return\n }\n\n // Check if overshooting the max scale and clamp it\n const finalScale = currentScale > MAX_SCALE ? MAX_SCALE : currentScale\n\n // Recalculate translation using focal point to always return to the same position when image is overshooting the max scale\n const newTranslation = zoomToFocalPoint({\n focalPointX: focalX.value,\n focalPointY: focalY.value,\n targetScale: finalScale,\n currentSavedScale: savedScale.value,\n currentSavedTranslateX: savedTranslateX.value,\n currentSavedTranslateY: savedTranslateY.value,\n })\n\n // Ensure image edges remain within window boundaries\n const clampedTranslate = clampTranslation({\n x: newTranslation.translateX,\n y: newTranslation.translateY,\n currentScale: finalScale,\n })\n\n // Animate to position and scale limits with a spring\n scale.value = withSpring(finalScale, RESET_SPRING_CONFIG)\n translateX.value = withSpring(clampedTranslate.x, RESET_SPRING_CONFIG)\n translateY.value = withSpring(clampedTranslate.y, RESET_SPRING_CONFIG)\n\n // Save state for next gesture\n savedScale.value = finalScale\n savedTranslateX.value = clampedTranslate.x\n savedTranslateY.value = clampedTranslate.y\n\n // Disable FlatList scroll since image is still zoomed\n runOnJS(enableFlatListScroll)(false)\n })\n\n const panGesture = Gesture.Pan()\n .minDistance(MIN_DISTANCE_FOR_PAN)\n .minPointers(SINGLE_FINGER_POINTER)\n .maxPointers(SINGLE_FINGER_POINTER)\n .onStart(() => {\n // Only start pan if image is zoomed in - prevents conflicts with FlatList\n if (scale.value <= DEFAULT_SCALE) return\n\n // Update saved position to current animated position\n // This ensures smooth continuation if decay is running\n savedTranslateX.value = translateX.value\n savedTranslateY.value = translateY.value\n })\n .onUpdate(e => {\n // Only pan if image is zoomed in - prevents conflicts with FlatList\n if (scale.value <= DEFAULT_SCALE) return\n\n const newTranslateX = savedTranslateX.value + e.translationX\n const newTranslateY = savedTranslateY.value + e.translationY\n\n // Ensure image edges remain within window boundaries\n const clampedTranslate = clampTranslation({\n x: newTranslateX,\n y: newTranslateY,\n currentScale: scale.value,\n })\n translateX.value = clampedTranslate.x\n translateY.value = clampedTranslate.y\n })\n .onEnd(e => {\n // Prevents saving pan position if image is zoomed out while panning\n if (scale.value <= DEFAULT_SCALE) return\n\n const clampDecayBounds = clampDecay(scale.value)\n\n translateX.value = withDecay(\n {\n velocity: e.velocityX * DECAY_VELOCITY_FACTOR,\n clamp: clampDecayBounds.x,\n deceleration: 0.996,\n },\n finished => {\n if (finished) {\n savedTranslateX.value = translateX.value\n }\n }\n )\n\n translateY.value = withDecay(\n {\n velocity: e.velocityY * DECAY_VELOCITY_FACTOR,\n clamp: clampDecayBounds.y,\n deceleration: 0.996,\n },\n finished => {\n if (finished) {\n savedTranslateY.value = translateY.value\n }\n }\n )\n })\n\n const panToDismissModalGesture = Gesture.Pan()\n .onUpdate(e => {\n const atDefaultScale = scale.value === DEFAULT_SCALE\n\n // Calculate zoom conditions for dismissing modal\n const atVerticalBoundry = isImageAtVerticalBoundry(scale.value)\n const panDirectionIsPrimarilyVertical = Math.abs(e.translationY) > Math.abs(e.translationX)\n const canDismissWhileZoomed = atVerticalBoundry && panDirectionIsPrimarilyVertical\n\n if (!(atDefaultScale || canDismissWhileZoomed)) return\n\n // Fade image if its been panned past 50% of the dismiss threshold\n const panDistance = Math.abs(e.translationY)\n const halfThreshold = DISMISS_PAN_THRESHOLD / 2\n const fadeDistance = Math.max(0, panDistance - halfThreshold)\n const fadeProgress = fadeDistance / halfThreshold\n\n opacity.value = Math.max(0, DEFAULT_OPACITY - fadeProgress)\n dismissY.value = e.translationY\n })\n .onEnd(() => {\n const exceededDismissThreshold = Math.abs(dismissY.value) > DISMISS_PAN_THRESHOLD\n\n if (exceededDismissThreshold) {\n runOnJS(handleCloseModal)()\n } else {\n runOnJS(resetDismissGestures)()\n }\n })\n\n /* ==============================\n COMPOSE GESTURES\n ================================= */\n // Race between pinch and pan ensures only one is active at a time, preserving focal point logic\n const pinchOrPanGestures = Gesture.Race(pinchGesture, panGesture)\n\n // Exclusive race ensures single tap doesn't interfere with double tap\n const tapGestures = Gesture.Exclusive(doubleTapGesture, singleTapGesture)\n\n // Race between tap gestures and pinch/pan\n const transformImageGestures = Gesture.Race(tapGestures, pinchOrPanGestures)\n\n // Dismiss can work simultaneously with all gestures\n const composedGesture = Gesture.Simultaneous(transformImageGestures, panToDismissModalGesture)\n\n return (\n <Modal visible={visible} transparent animationType=\"fade\" onRequestClose={handleCloseModal}>\n <StatusBar\n barStyle=\"light-content\"\n hidden={isStatusBarHidden}\n animated\n showHideTransition=\"slide\"\n />\n <GestureHandlerRootView>\n <GestureDetector gesture={composedGesture}>\n <FlatList\n data={imageAttachments}\n renderItem={({ item, index }) => (\n <GestureImage\n item={item}\n gesturesEnabled={index === currentImageIndex}\n scale={scale}\n translateX={translateX}\n translateY={translateY}\n dismissY={dismissY}\n opacity={opacity}\n />\n )}\n keyExtractor={(item, index) => `${item.id}-${index}`}\n horizontal\n pagingEnabled\n scrollEnabled={flatListScrollEnabled}\n showsHorizontalScrollIndicator={false}\n initialScrollIndex={initialImageIndex}\n getItemLayout={getItemLayout}\n onMomentumScrollEnd={onMomentumScrollEnd}\n onViewableItemsChanged={onViewableItemsChanged}\n viewabilityConfig={{\n itemVisiblePercentThreshold: 50, // 50% of the image must be visible in the window to be considered viewable\n }}\n style={styles.gallery}\n contentContainerStyle={styles.galleryContentContainer}\n />\n </GestureDetector>\n </GestureHandlerRootView>\n <LightboxToolbarHeader\n metaProps={metaProps}\n lightboxToolbarVisible={lightboxToolbarVisible}\n isStatusBarHidden={isStatusBarHidden}\n handleOpenInBrowser={handleOpenInBrowser}\n handleCloseModal={handleCloseModal}\n />\n {imageAttachments.length > 1 && (\n <LightboxToolbarFooter\n lightboxToolbarVisible={lightboxToolbarVisible}\n totalImages={imageAttachments.length}\n currentImageIndex={currentImageIndex}\n />\n )}\n </Modal>\n )\n}\n\ninterface GestureImageProps {\n item: DenormalizedMessageAttachmentResource\n gesturesEnabled: boolean\n scale: SharedValue<number>\n translateX: SharedValue<number>\n translateY: SharedValue<number>\n dismissY: SharedValue<number>\n opacity: SharedValue<number>\n}\n\nconst GestureImage = ({\n item,\n gesturesEnabled,\n scale,\n translateX,\n translateY,\n dismissY,\n opacity,\n}: GestureImageProps) => {\n const styles = useStyles()\n const { url: itemUrl, urlMedium: itemUrlMedium } = item.attributes\n\n const animatedImageStyles = useAnimatedStyle(() => {\n return {\n transform: [\n { translateX: gesturesEnabled ? translateX.value : DEFAULT_TRANSLATE_X },\n { translateY: gesturesEnabled ? translateY.value + dismissY.value : DEFAULT_TRANSLATE_Y },\n { scale: gesturesEnabled ? scale.value : DEFAULT_SCALE },\n ],\n opacity: opacity.value,\n }\n })\n\n return (\n <TouchEventIsolator>\n <Image\n source={{ uri: itemUrlMedium || itemUrl }}\n style={styles.gestureImage}\n animatedImageStyle={animatedImageStyles}\n loadingBackgroundStyles={styles.gestureImageLoading}\n resizeMode=\"contain\"\n alt=\"\"\n />\n </TouchEventIsolator>\n )\n}\n\nconst TouchEventIsolator = ({ children }: { children: React.ReactNode }) => {\n const styles = useStyles()\n\n return (\n <View style={styles.touchEventIsolator} onStartShouldSetResponder={() => true}>\n {children}\n </View>\n )\n}\n\ninterface LightboxToolbarHeaderProps {\n metaProps: MetaProps\n lightboxToolbarVisible: SharedValue<number>\n isStatusBarHidden: boolean\n handleOpenInBrowser: () => void\n handleCloseModal: () => void\n}\n\nconst LightboxToolbarHeader = ({\n metaProps,\n lightboxToolbarVisible,\n isStatusBarHidden,\n handleOpenInBrowser,\n handleCloseModal,\n}: LightboxToolbarHeaderProps) => {\n const styles = useStyles()\n const { authorName, createdAt } = metaProps\n\n const animatedHeaderStyles = useAnimatedStyle(() => ({\n opacity: lightboxToolbarVisible.value,\n transform: [\n { translateY: (1 - lightboxToolbarVisible.value) * -20 }, // slide up when hiding\n ],\n }))\n\n return (\n <Animated.View\n style={[styles.lightboxToolbar, styles.lightboxToolbarHeader, animatedHeaderStyles]}\n accessibilityRole=\"toolbar\"\n >\n <View style={styles.lightboxToolbarHeaderMetaContainer}>\n <Heading variant=\"h3\" style={styles.lightboxToolbarHeaderTitle} numberOfLines={1}>\n {authorName}\n </Heading>\n <Text variant=\"tertiary\" style={styles.lightboxToolbarHeaderSubtitle}>\n {formatDatePreview(createdAt)}\n </Text>\n </View>\n <IconButton\n onPress={handleOpenInBrowser}\n disabled={isStatusBarHidden}\n name=\"general.newWindow\"\n accessibilityRole=\"link\"\n accessibilityLabel=\"Open image in browser\"\n accessibilityHint=\"Image can be downloaded and shared through the browser.\"\n style={styles.lightboxToolbarButton}\n iconStyle={styles.lightboxToolbarButtonIcon}\n size=\"lg\"\n />\n <IconButton\n onPress={handleCloseModal}\n disabled={isStatusBarHidden}\n name=\"general.x\"\n accessibilityLabel=\"Close image\"\n style={styles.lightboxToolbarButton}\n iconStyle={styles.lightboxToolbarButtonIcon}\n size=\"lg\"\n />\n </Animated.View>\n )\n}\n\ninterface LightboxToolbarFooterProps {\n lightboxToolbarVisible: SharedValue<number>\n totalImages: number\n currentImageIndex: number\n}\n\nconst LightboxToolbarFooter = ({\n lightboxToolbarVisible,\n totalImages,\n currentImageIndex,\n}: LightboxToolbarFooterProps) => {\n const styles = useStyles()\n\n const animatedFooterStyles = useAnimatedStyle(() => ({\n opacity: lightboxToolbarVisible.value,\n transform: [\n { translateY: (1 - lightboxToolbarVisible.value) * 20 }, // slide down when showing\n ],\n }))\n\n return (\n <Animated.View\n style={[styles.lightboxToolbar, styles.lightboxToolbarFooter, animatedFooterStyles]}\n accessibilityRole=\"toolbar\"\n >\n <Text style={styles.lightboxToolbarFooterText}>\n {currentImageIndex + 1} of {totalImages}\n </Text>\n </Animated.View>\n )\n}\n\ninterface UseStylesProps {\n imageWidth?: number\n imageHeight?: number\n}\n\nconst useStyles = ({ imageWidth = 100, imageHeight = 100 }: UseStylesProps = {}) => {\n const { top, bottom } = useSafeAreaInsets()\n const backgroundColor = tokens.colorNeutral7\n const transparentBackgroundColor = useMemo(\n () => colorFunction(backgroundColor).alpha(0.8).toString(),\n [backgroundColor]\n )\n\n return StyleSheet.create({\n container: {\n maxWidth: '100%',\n },\n touchEventIsolator: {\n flex: 1,\n },\n attachmentImageWrapper: {\n width: '100%',\n minWidth: 200,\n aspectRatio: imageWidth / imageHeight,\n },\n image: {\n borderRadius: 8,\n },\n gallery: {\n backgroundColor,\n },\n galleryContentContainer: {\n paddingTop: top,\n paddingBottom: bottom,\n },\n gestureImage: {\n height: '100%',\n width: WINDOW_WIDTH,\n backgroundColor: 'transparent',\n },\n gestureImageLoading: {\n backgroundColor,\n },\n lightboxToolbar: {\n width: '100%',\n position: 'absolute',\n flexDirection: 'row',\n alignItems: 'center',\n gap: 20,\n paddingHorizontal: 16,\n backgroundColor: transparentBackgroundColor,\n },\n lightboxToolbarButton: {\n backgroundColor,\n height: 40,\n width: 40,\n borderRadius: 50,\n borderWidth: 1,\n borderColor: tokens.colorNeutral24,\n },\n lightboxToolbarButtonIcon: {\n color: tokens.colorNeutral88,\n },\n lightboxToolbarHeader: {\n top: 0,\n paddingTop: top + 16,\n paddingBottom: 8,\n },\n lightboxToolbarHeaderMetaContainer: {\n flex: 1,\n },\n lightboxToolbarHeaderTitle: {\n marginRight: 'auto',\n flexShrink: 1,\n color: tokens.colorNeutral88,\n },\n lightboxToolbarHeaderSubtitle: {\n color: tokens.colorNeutral68,\n },\n lightboxToolbarFooter: {\n justifyContent: 'center',\n bottom: 0,\n paddingTop: 8,\n paddingBottom: bottom + 16,\n },\n lightboxToolbarFooterText: {\n color: tokens.colorNeutral88,\n fontWeight: platformFontWeightMedium,\n },\n })\n}\n"]}
@@ -31,14 +31,10 @@ export interface ImageProps extends ReactNativeImageProps {
31
31
  * Style the outer View of the image.
32
32
  */
33
33
  wrapperStyle?: ViewStyle;
34
- /**
35
- * Changes the underlying image component to Animated.Image.
36
- */
37
- animated?: boolean;
38
34
  /**
39
35
  * Style the image if animated.
40
36
  */
41
37
  animatedImageStyle?: AnimatedStyle<ImageStyle>;
42
38
  }
43
- export declare function Image({ source, onLoad, defaultLoading, loading, hideLoader, loaderSize, loadingBackgroundStyles, style, wrapperStyle, alt, animated, animatedImageStyle, ...props }: ImageProps): React.JSX.Element;
39
+ export declare function Image({ source, onLoad, defaultLoading, loading, hideLoader, loaderSize, loadingBackgroundStyles, style, wrapperStyle, alt, animatedImageStyle, ...props }: ImageProps): React.JSX.Element;
44
40
  //# sourceMappingURL=image.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../../src/components/display/image.tsx"],"names":[],"mappings":"AACA,OAAO,KAAmB,MAAM,OAAO,CAAA;AACvC,OAAO,EAGL,UAAU,EAEV,UAAU,IAAI,qBAAqB,EAGnC,SAAS,EACV,MAAM,cAAc,CAAA;AACrB,OAAiB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAQjE,MAAM,WAAW,UAAW,SAAQ,qBAAqB;IACvD;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAA;IACX;;OAEG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;OAEG;IACH,uBAAuB,CAAC,EAAE,SAAS,CAAA;IACnC;;OAEG;IACH,YAAY,CAAC,EAAE,SAAS,CAAA;IACxB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;OAEG;IACH,kBAAkB,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;CAC/C;AAED,wBAAgB,KAAK,CAAC,EACpB,MAAM,EACN,MAAa,EACb,cAAqB,EACrB,OAAe,EACf,UAAkB,EAClB,UAAe,EACf,uBAAuB,EACvB,KAAU,EACV,YAAiB,EACjB,GAAG,EACH,QAAgB,EAChB,kBAAuB,EACvB,GAAG,KAAK,EACT,EAAE,UAAU,qBAsCZ"}
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../../src/components/display/image.tsx"],"names":[],"mappings":"AACA,OAAO,KAAmB,MAAM,OAAO,CAAA;AACvC,OAAO,EAGL,UAAU,EAEV,UAAU,IAAI,qBAAqB,EAGnC,SAAS,EACV,MAAM,cAAc,CAAA;AACrB,OAAiB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAQjE,MAAM,WAAW,UAAW,SAAQ,qBAAqB;IACvD;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAA;IACX;;OAEG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;OAEG;IACH,uBAAuB,CAAC,EAAE,SAAS,CAAA;IACnC;;OAEG;IACH,YAAY,CAAC,EAAE,SAAS,CAAA;IACxB;;OAEG;IACH,kBAAkB,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;CAC/C;AAED,wBAAgB,KAAK,CAAC,EACpB,MAAM,EACN,MAAa,EACb,cAAqB,EACrB,OAAe,EACf,UAAkB,EAClB,UAAe,EACf,uBAAuB,EACvB,KAAU,EACV,YAAiB,EACjB,GAAG,EACH,kBAAuB,EACvB,GAAG,KAAK,EACT,EAAE,UAAU,qBAsCZ"}
@@ -4,7 +4,7 @@ import { Image as ReactNativeImage, StyleSheet, View, } from 'react-native';
4
4
  import Animated from 'react-native-reanimated';
5
5
  import { useTheme } from '../../hooks';
6
6
  import { Spinner } from './spinner';
7
- export function Image({ source, onLoad = noop, defaultLoading = true, loading = false, hideLoader = false, loaderSize = 24, loadingBackgroundStyles, style = {}, wrapperStyle = {}, alt, animated = false, animatedImageStyle = {}, ...props }) {
7
+ export function Image({ source, onLoad = noop, defaultLoading = true, loading = false, hideLoader = false, loaderSize = 24, loadingBackgroundStyles, style = {}, wrapperStyle = {}, alt, animatedImageStyle = {}, ...props }) {
8
8
  const [isImageLoading, setIsImageLoading] = useState(defaultLoading);
9
9
  const imageStyles = StyleSheet.flatten(style);
10
10
  const { width = '100%', height = '100%', borderRadius = 0 } = imageStyles || {};
@@ -14,7 +14,7 @@ export function Image({ source, onLoad = noop, defaultLoading = true, loading =
14
14
  onLoad?.(event);
15
15
  };
16
16
  const isLoading = isImageLoading || loading;
17
- const ImageComponent = animated || animatedImageStyle ? Animated.Image : ReactNativeImage;
17
+ const ImageComponent = animatedImageStyle ? Animated.Image : ReactNativeImage;
18
18
  return (<View style={wrapperStyle} accessible={Boolean(alt)} accessibilityRole="image" accessibilityState={{ busy: isLoading }} collapsable={false}>
19
19
  <ImageComponent style={[styles.image, imageStyles, animatedImageStyle]} onLoad={handleOnLoad} source={source} alt={isLoading ? '' : alt} {...props}/>
20
20
  {!hideLoader && isLoading && (<View style={[styles.loadingBackground, loadingBackgroundStyles]}>
@@ -1 +1 @@
1
- {"version":3,"file":"image.js","sourceRoot":"","sources":["../../../src/components/display/image.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAC7B,OAAO,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACvC,OAAO,EAIL,KAAK,IAAI,gBAAgB,EAEzB,UAAU,EACV,IAAI,GAEL,MAAM,cAAc,CAAA;AACrB,OAAO,QAA2B,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AA8CnC,MAAM,UAAU,KAAK,CAAC,EACpB,MAAM,EACN,MAAM,GAAG,IAAI,EACb,cAAc,GAAG,IAAI,EACrB,OAAO,GAAG,KAAK,EACf,UAAU,GAAG,KAAK,EAClB,UAAU,GAAG,EAAE,EACf,uBAAuB,EACvB,KAAK,GAAG,EAAE,EACV,YAAY,GAAG,EAAE,EACjB,GAAG,EACH,QAAQ,GAAG,KAAK,EAChB,kBAAkB,GAAG,EAAE,EACvB,GAAG,KAAK,EACG;IACX,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAA;IAEpE,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IAC7C,MAAM,EAAE,KAAK,GAAG,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,YAAY,GAAG,CAAC,EAAE,GAAG,WAAW,IAAI,EAAE,CAAA;IAC/E,MAAM,MAAM,GAAG,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAA;IAEzD,MAAM,YAAY,GAAG,CAAC,KAAU,EAAE,EAAE;QAClC,iBAAiB,CAAC,KAAK,CAAC,CAAA;QACxB,MAAM,EAAE,CAAC,KAAK,CAAC,CAAA;IACjB,CAAC,CAAA;IAED,MAAM,SAAS,GAAG,cAAc,IAAI,OAAO,CAAA;IAE3C,MAAM,cAAc,GAAG,QAAQ,IAAI,kBAAkB,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,gBAAgB,CAAA;IAEzF,OAAO,CACL,CAAC,IAAI,CACH,KAAK,CAAC,CAAC,YAAY,CAAC,CACpB,UAAU,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CACzB,iBAAiB,CAAC,OAAO,CACzB,kBAAkB,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CACxC,WAAW,CAAC,CAAC,KAAK,CAAC,CAEnB;MAAA,CAAC,cAAc,CACb,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,kBAAkB,CAAC,CAAC,CACvD,MAAM,CAAC,CAAC,YAAY,CAAC,CACrB,MAAM,CAAC,CAAC,MAAM,CAAC,CACf,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAC1B,IAAI,KAAK,CAAC,EAEZ;MAAA,CAAC,CAAC,UAAU,IAAI,SAAS,IAAI,CAC3B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,iBAAiB,EAAE,uBAAuB,CAAC,CAAC,CAC/D;UAAA,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,EAC5B;QAAA,EAAE,IAAI,CAAC,CACR,CACH;IAAA,EAAE,IAAI,CAAC,CACR,CAAA;AACH,CAAC;AAYD,MAAM,SAAS,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAU,EAAE,EAAE;IAC5D,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,CAAA;IAE7B,OAAO,UAAU,CAAC,MAAM,CAAC;QACvB,iBAAiB,EAAE;YACjB,QAAQ,EAAE,UAAU;YACpB,GAAG,EAAE,CAAC;YACN,IAAI,EAAE,CAAC;YACP,eAAe,EAAE,MAAM,CAAC,mBAAmB;YAC3C,YAAY;YACZ,KAAK;YACL,MAAM;SACP;QACD,KAAK,EAAE;YACL,eAAe,EAAE,MAAM,CAAC,mBAAmB;YAC3C,KAAK;YACL,MAAM;SACP;KACF,CAAC,CAAA;AACJ,CAAC,CAAA","sourcesContent":["import { noop } from 'lodash'\nimport React, { useState } from 'react'\nimport {\n AnimatableNumericValue,\n DimensionValue,\n ImageStyle,\n Image as ReactNativeImage,\n ImageProps as ReactNativeImageProps,\n StyleSheet,\n View,\n ViewStyle,\n} from 'react-native'\nimport Animated, { AnimatedStyle } from 'react-native-reanimated'\nimport { useTheme } from '../../hooks'\nimport { Spinner } from './spinner'\n\n// =================================\n// ====== Component ================\n// =================================\n\nexport interface ImageProps extends ReactNativeImageProps {\n /**\n * Describes the image to screen-readers and marks the image as `accessible`.\n * Passing an empty string will hide the image from screen-readers.\n */\n alt: string\n /**\n * Shows the image's loading spinner right away. Enabled by default.\n */\n defaultLoading?: boolean\n /**\n * Externally controls the loading state of the image.\n */\n loading?: boolean\n /**\n * Hide the loading spinner, regardless of the loading state.\n */\n hideLoader?: boolean\n /**\n * Size of the loading spinner.\n */\n loaderSize?: number\n /**\n * Style object for the preload background.\n */\n loadingBackgroundStyles?: ViewStyle\n /**\n * Style the outer View of the image.\n */\n wrapperStyle?: ViewStyle\n /**\n * Changes the underlying image component to Animated.Image.\n */\n animated?: boolean\n /**\n * Style the image if animated.\n */\n animatedImageStyle?: AnimatedStyle<ImageStyle>\n}\n\nexport function Image({\n source,\n onLoad = noop,\n defaultLoading = true,\n loading = false,\n hideLoader = false,\n loaderSize = 24,\n loadingBackgroundStyles,\n style = {},\n wrapperStyle = {},\n alt,\n animated = false,\n animatedImageStyle = {},\n ...props\n}: ImageProps) {\n const [isImageLoading, setIsImageLoading] = useState(defaultLoading)\n\n const imageStyles = StyleSheet.flatten(style)\n const { width = '100%', height = '100%', borderRadius = 0 } = imageStyles || {}\n const styles = useStyles({ width, height, borderRadius })\n\n const handleOnLoad = (event: any) => {\n setIsImageLoading(false)\n onLoad?.(event)\n }\n\n const isLoading = isImageLoading || loading\n\n const ImageComponent = animated || animatedImageStyle ? Animated.Image : ReactNativeImage\n\n return (\n <View\n style={wrapperStyle}\n accessible={Boolean(alt)}\n accessibilityRole=\"image\"\n accessibilityState={{ busy: isLoading }}\n collapsable={false}\n >\n <ImageComponent\n style={[styles.image, imageStyles, animatedImageStyle]}\n onLoad={handleOnLoad}\n source={source}\n alt={isLoading ? '' : alt}\n {...props}\n />\n {!hideLoader && isLoading && (\n <View style={[styles.loadingBackground, loadingBackgroundStyles]}>\n <Spinner size={loaderSize} />\n </View>\n )}\n </View>\n )\n}\n\n// =================================\n// ====== Styles ===================\n// =================================\n\ninterface Styles {\n width: DimensionValue\n height: DimensionValue\n borderRadius: AnimatableNumericValue | string\n}\n\nconst useStyles = ({ width, height, borderRadius }: Styles) => {\n const { colors } = useTheme()\n\n return StyleSheet.create({\n loadingBackground: {\n position: 'absolute',\n top: 0,\n left: 0,\n backgroundColor: colors.fillColorNeutral070,\n borderRadius,\n width,\n height,\n },\n image: {\n backgroundColor: colors.fillColorNeutral070,\n width,\n height,\n },\n })\n}\n"]}
1
+ {"version":3,"file":"image.js","sourceRoot":"","sources":["../../../src/components/display/image.tsx"],"names":[],"mappings":"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","sourcesContent":["import { noop } from 'lodash'\nimport React, { useState } from 'react'\nimport {\n AnimatableNumericValue,\n DimensionValue,\n ImageStyle,\n Image as ReactNativeImage,\n ImageProps as ReactNativeImageProps,\n StyleSheet,\n View,\n ViewStyle,\n} from 'react-native'\nimport Animated, { AnimatedStyle } from 'react-native-reanimated'\nimport { useTheme } from '../../hooks'\nimport { Spinner } from './spinner'\n\n// =================================\n// ====== Component ================\n// =================================\n\nexport interface ImageProps extends ReactNativeImageProps {\n /**\n * Describes the image to screen-readers and marks the image as `accessible`.\n * Passing an empty string will hide the image from screen-readers.\n */\n alt: string\n /**\n * Shows the image's loading spinner right away. Enabled by default.\n */\n defaultLoading?: boolean\n /**\n * Externally controls the loading state of the image.\n */\n loading?: boolean\n /**\n * Hide the loading spinner, regardless of the loading state.\n */\n hideLoader?: boolean\n /**\n * Size of the loading spinner.\n */\n loaderSize?: number\n /**\n * Style object for the preload background.\n */\n loadingBackgroundStyles?: ViewStyle\n /**\n * Style the outer View of the image.\n */\n wrapperStyle?: ViewStyle\n /**\n * Style the image if animated.\n */\n animatedImageStyle?: AnimatedStyle<ImageStyle>\n}\n\nexport function Image({\n source,\n onLoad = noop,\n defaultLoading = true,\n loading = false,\n hideLoader = false,\n loaderSize = 24,\n loadingBackgroundStyles,\n style = {},\n wrapperStyle = {},\n alt,\n animatedImageStyle = {},\n ...props\n}: ImageProps) {\n const [isImageLoading, setIsImageLoading] = useState(defaultLoading)\n\n const imageStyles = StyleSheet.flatten(style)\n const { width = '100%', height = '100%', borderRadius = 0 } = imageStyles || {}\n const styles = useStyles({ width, height, borderRadius })\n\n const handleOnLoad = (event: any) => {\n setIsImageLoading(false)\n onLoad?.(event)\n }\n\n const isLoading = isImageLoading || loading\n\n const ImageComponent = animatedImageStyle ? Animated.Image : ReactNativeImage\n\n return (\n <View\n style={wrapperStyle}\n accessible={Boolean(alt)}\n accessibilityRole=\"image\"\n accessibilityState={{ busy: isLoading }}\n collapsable={false}\n >\n <ImageComponent\n style={[styles.image, imageStyles, animatedImageStyle]}\n onLoad={handleOnLoad}\n source={source}\n alt={isLoading ? '' : alt}\n {...props}\n />\n {!hideLoader && isLoading && (\n <View style={[styles.loadingBackground, loadingBackgroundStyles]}>\n <Spinner size={loaderSize} />\n </View>\n )}\n </View>\n )\n}\n\n// =================================\n// ====== Styles ===================\n// =================================\n\ninterface Styles {\n width: DimensionValue\n height: DimensionValue\n borderRadius: AnimatableNumericValue | string\n}\n\nconst useStyles = ({ width, height, borderRadius }: Styles) => {\n const { colors } = useTheme()\n\n return StyleSheet.create({\n loadingBackground: {\n position: 'absolute',\n top: 0,\n left: 0,\n backgroundColor: colors.fillColorNeutral070,\n borderRadius,\n width,\n height,\n },\n image: {\n backgroundColor: colors.fillColorNeutral070,\n width,\n height,\n },\n })\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/chat-react-native",
3
- "version": "3.12.0",
3
+ "version": "3.12.1-rc.1",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -55,5 +55,5 @@
55
55
  "prettier": "^3.4.2",
56
56
  "typescript": "<5.6.0"
57
57
  },
58
- "gitHead": "1224a5919490e2682cb564d20b9ad5485925def8"
58
+ "gitHead": "50de9e75c7afc4a457b5aa25613eaa34513ede1d"
59
59
  }
@@ -9,7 +9,6 @@ import {
9
9
  NativeSyntheticEvent,
10
10
  NativeScrollEvent,
11
11
  ViewToken,
12
- Platform,
13
12
  } from 'react-native'
14
13
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
15
14
  import {
@@ -17,7 +16,6 @@ import {
17
16
  Gesture,
18
17
  GestureDetector,
19
18
  GestureHandlerRootView,
20
- Pressable,
21
19
  } from 'react-native-gesture-handler'
22
20
  import Animated, {
23
21
  runOnJS,
@@ -41,7 +39,6 @@ const { width: WINDOW_WIDTH, height: WINDOW_HEIGHT } = Dimensions.get('window')
41
39
  const DISMISS_PAN_THRESHOLD = 250
42
40
  const MIN_DISTANCE_FOR_PAN = 10 // Higher threshold gives pinching priority
43
41
  const SINGLE_FINGER_POINTER = 1 // Single-finger panning / tapping helps to avoid conflicts with pinching
44
- const MAX_TRAVEL_DISTANCE_FOR_DOUBLE_TAP_PLATFORM = Platform.OS === 'ios' ? 8 : 0 // Causes taps to be unreliable on Android
45
42
  const DEFAULT_OPACITY = 1
46
43
  const DEFAULT_TRANSLATE_X = 0
47
44
  const DEFAULT_TRANSLATE_Y = 0
@@ -164,7 +161,6 @@ const LightboxModal = ({
164
161
  // React (JS) State:
165
162
  const [isStatusBarHidden, setIsStatusBarHidden] = useState(false)
166
163
  const [flatListScrollEnabled, setFlatListScrollEnabled] = useState(true)
167
- const [panGestureEnabled, setPanGestureEnabled] = useState(false)
168
164
 
169
165
  // Syncs toolbar useSharedValue state with React's state so that the status bar can be hidden/shown based on the toolbar's animation
170
166
  useAnimatedReaction(
@@ -174,18 +170,6 @@ const LightboxModal = ({
174
170
  }
175
171
  )
176
172
 
177
- // Syncs FlatList scroll state with scale changes
178
- // When image is at default scale, enable scroll and disable pan gesture
179
- // When image is zoomed in, disable scroll and enable pan gesture
180
- useAnimatedReaction(
181
- () => scale.value,
182
- value => {
183
- const enableFlatListScroll = value === DEFAULT_SCALE
184
- runOnJS(setFlatListScrollEnabled)(enableFlatListScroll)
185
- runOnJS(setPanGestureEnabled)(!enableFlatListScroll)
186
- }
187
- )
188
-
189
173
  /* ============================
190
174
  HANDLERS
191
175
  ============================ */
@@ -193,6 +177,11 @@ const LightboxModal = ({
193
177
  Linking.openURL(urlMedium || url)
194
178
  }, [urlMedium, url])
195
179
 
180
+ // Helper to enable or disable FlatList scroll state after gestures have ended
181
+ const enableFlatListScroll = useCallback((enableScroll: boolean) => {
182
+ setFlatListScrollEnabled(enableScroll)
183
+ }, [])
184
+
196
185
  const resetDismissGestures = useCallback(() => {
197
186
  dismissY.value = withSpring(0, RESET_SPRING_CONFIG)
198
187
  opacity.value = withSpring(DEFAULT_OPACITY, RESET_SPRING_CONFIG)
@@ -207,6 +196,7 @@ const LightboxModal = ({
207
196
  savedTranslateX.value = DEFAULT_TRANSLATE_X
208
197
  savedTranslateY.value = DEFAULT_TRANSLATE_Y
209
198
  lightboxToolbarVisible.value = withSpring(1, RESET_SPRING_CONFIG)
199
+ enableFlatListScroll(true)
210
200
  }, [
211
201
  resetDismissGestures,
212
202
  scale,
@@ -216,6 +206,7 @@ const LightboxModal = ({
216
206
  savedTranslateX,
217
207
  savedTranslateY,
218
208
  lightboxToolbarVisible,
209
+ enableFlatListScroll,
219
210
  ])
220
211
 
221
212
  const handleCloseModal = useCallback(() => {
@@ -423,9 +414,6 @@ const LightboxModal = ({
423
414
  })
424
415
 
425
416
  const doubleTapGesture = Gesture.Tap()
426
- .maxDeltaX(MAX_TRAVEL_DISTANCE_FOR_DOUBLE_TAP_PLATFORM)
427
- .maxDeltaY(MAX_TRAVEL_DISTANCE_FOR_DOUBLE_TAP_PLATFORM)
428
- .maxDistance(MAX_TRAVEL_DISTANCE_FOR_DOUBLE_TAP_PLATFORM)
429
417
  .minPointers(SINGLE_FINGER_POINTER)
430
418
  .numberOfTaps(2)
431
419
  .onStart(e => {
@@ -462,6 +450,9 @@ const LightboxModal = ({
462
450
  savedScale.value = DOUBLE_TAP_ZOOM_SCALE
463
451
  savedTranslateX.value = clampedTranslate.x
464
452
  savedTranslateY.value = clampedTranslate.y
453
+
454
+ // Disable FlatList scroll since image is now zoomed
455
+ runOnJS(enableFlatListScroll)(false)
465
456
  }
466
457
  })
467
458
 
@@ -545,21 +536,26 @@ const LightboxModal = ({
545
536
  savedScale.value = finalScale
546
537
  savedTranslateX.value = clampedTranslate.x
547
538
  savedTranslateY.value = clampedTranslate.y
539
+
540
+ // Disable FlatList scroll since image is still zoomed
541
+ runOnJS(enableFlatListScroll)(false)
548
542
  })
549
543
 
550
544
  const panGesture = Gesture.Pan()
551
545
  .minDistance(MIN_DISTANCE_FOR_PAN)
552
546
  .minPointers(SINGLE_FINGER_POINTER)
553
547
  .maxPointers(SINGLE_FINGER_POINTER)
554
- .enabled(panGestureEnabled)
555
548
  .onStart(() => {
549
+ // Only start pan if image is zoomed in - prevents conflicts with FlatList
550
+ if (scale.value <= DEFAULT_SCALE) return
551
+
556
552
  // Update saved position to current animated position
557
553
  // This ensures smooth continuation if decay is running
558
554
  savedTranslateX.value = translateX.value
559
555
  savedTranslateY.value = translateY.value
560
556
  })
561
557
  .onUpdate(e => {
562
- // Only pan if image is zoomed in
558
+ // Only pan if image is zoomed in - prevents conflicts with FlatList
563
559
  if (scale.value <= DEFAULT_SCALE) return
564
560
 
565
561
  const newTranslateX = savedTranslateX.value + e.translationX
@@ -662,36 +658,34 @@ const LightboxModal = ({
662
658
  />
663
659
  <GestureHandlerRootView>
664
660
  <GestureDetector gesture={composedGesture}>
665
- <PreventPressEventsBubbling>
666
- <FlatList
667
- data={imageAttachments}
668
- renderItem={({ item, index }) => (
669
- <GestureImage
670
- item={item}
671
- gesturesEnabled={index === currentImageIndex}
672
- scale={scale}
673
- translateX={translateX}
674
- translateY={translateY}
675
- dismissY={dismissY}
676
- opacity={opacity}
677
- />
678
- )}
679
- keyExtractor={(item, index) => `${item.id}-${index}`}
680
- horizontal
681
- pagingEnabled
682
- scrollEnabled={flatListScrollEnabled}
683
- showsHorizontalScrollIndicator={false}
684
- initialScrollIndex={initialImageIndex}
685
- getItemLayout={getItemLayout}
686
- onMomentumScrollEnd={onMomentumScrollEnd}
687
- onViewableItemsChanged={onViewableItemsChanged}
688
- viewabilityConfig={{
689
- itemVisiblePercentThreshold: 50, // 50% of the image must be visible in the window to be considered viewable
690
- }}
691
- style={styles.gallery}
692
- contentContainerStyle={styles.galleryContentContainer}
693
- />
694
- </PreventPressEventsBubbling>
661
+ <FlatList
662
+ data={imageAttachments}
663
+ renderItem={({ item, index }) => (
664
+ <GestureImage
665
+ item={item}
666
+ gesturesEnabled={index === currentImageIndex}
667
+ scale={scale}
668
+ translateX={translateX}
669
+ translateY={translateY}
670
+ dismissY={dismissY}
671
+ opacity={opacity}
672
+ />
673
+ )}
674
+ keyExtractor={(item, index) => `${item.id}-${index}`}
675
+ horizontal
676
+ pagingEnabled
677
+ scrollEnabled={flatListScrollEnabled}
678
+ showsHorizontalScrollIndicator={false}
679
+ initialScrollIndex={initialImageIndex}
680
+ getItemLayout={getItemLayout}
681
+ onMomentumScrollEnd={onMomentumScrollEnd}
682
+ onViewableItemsChanged={onViewableItemsChanged}
683
+ viewabilityConfig={{
684
+ itemVisiblePercentThreshold: 50, // 50% of the image must be visible in the window to be considered viewable
685
+ }}
686
+ style={styles.gallery}
687
+ contentContainerStyle={styles.galleryContentContainer}
688
+ />
695
689
  </GestureDetector>
696
690
  </GestureHandlerRootView>
697
691
  <LightboxToolbarHeader
@@ -712,14 +706,6 @@ const LightboxModal = ({
712
706
  )
713
707
  }
714
708
 
715
- const PreventPressEventsBubbling = ({ children }: { children: React.ReactNode }) => {
716
- return (
717
- <Pressable onLongPress={() => {}} onPress={() => {}}>
718
- {children}
719
- </Pressable>
720
- )
721
- }
722
-
723
709
  interface GestureImageProps {
724
710
  item: DenormalizedMessageAttachmentResource
725
711
  gesturesEnabled: boolean
@@ -754,14 +740,26 @@ const GestureImage = ({
754
740
  })
755
741
 
756
742
  return (
757
- <Image
758
- source={{ uri: itemUrlMedium || itemUrl }}
759
- style={styles.gestureImage}
760
- animatedImageStyle={animatedImageStyles}
761
- loadingBackgroundStyles={styles.gestureImageLoading}
762
- resizeMode="contain"
763
- alt=""
764
- />
743
+ <TouchEventIsolator>
744
+ <Image
745
+ source={{ uri: itemUrlMedium || itemUrl }}
746
+ style={styles.gestureImage}
747
+ animatedImageStyle={animatedImageStyles}
748
+ loadingBackgroundStyles={styles.gestureImageLoading}
749
+ resizeMode="contain"
750
+ alt=""
751
+ />
752
+ </TouchEventIsolator>
753
+ )
754
+ }
755
+
756
+ const TouchEventIsolator = ({ children }: { children: React.ReactNode }) => {
757
+ const styles = useStyles()
758
+
759
+ return (
760
+ <View style={styles.touchEventIsolator} onStartShouldSetResponder={() => true}>
761
+ {children}
762
+ </View>
765
763
  )
766
764
  }
767
765
 
@@ -876,6 +874,9 @@ const useStyles = ({ imageWidth = 100, imageHeight = 100 }: UseStylesProps = {})
876
874
  container: {
877
875
  maxWidth: '100%',
878
876
  },
877
+ touchEventIsolator: {
878
+ flex: 1,
879
+ },
879
880
  attachmentImageWrapper: {
880
881
  width: '100%',
881
882
  minWidth: 200,
@@ -48,10 +48,6 @@ export interface ImageProps extends ReactNativeImageProps {
48
48
  * Style the outer View of the image.
49
49
  */
50
50
  wrapperStyle?: ViewStyle
51
- /**
52
- * Changes the underlying image component to Animated.Image.
53
- */
54
- animated?: boolean
55
51
  /**
56
52
  * Style the image if animated.
57
53
  */
@@ -69,7 +65,6 @@ export function Image({
69
65
  style = {},
70
66
  wrapperStyle = {},
71
67
  alt,
72
- animated = false,
73
68
  animatedImageStyle = {},
74
69
  ...props
75
70
  }: ImageProps) {
@@ -86,7 +81,7 @@ export function Image({
86
81
 
87
82
  const isLoading = isImageLoading || loading
88
83
 
89
- const ImageComponent = animated || animatedImageStyle ? Animated.Image : ReactNativeImage
84
+ const ImageComponent = animatedImageStyle ? Animated.Image : ReactNativeImage
90
85
 
91
86
  return (
92
87
  <View