diff --git a/src/components/Post/Embed/ImageContextMenu.tsx b/src/components/Post/Embed/ImageContextMenu.tsx index 3867fe9acf..b6bed6c49d 100644 --- a/src/components/Post/Embed/ImageContextMenu.tsx +++ b/src/components/Post/Embed/ImageContextMenu.tsx @@ -1,4 +1,5 @@ import {type ReactNode} from 'react' +import {type StyleProp, type ViewStyle} from 'react-native' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -22,6 +23,7 @@ export function ImageContextMenu({ aspectRatio, borderRadius, onPreviewPress, + style, children, }: { fullsizeUri: string @@ -29,6 +31,7 @@ export function ImageContextMenu({ aspectRatio: number | undefined borderRadius?: number onPreviewPress?: () => void + style?: StyleProp children: ReactNode }) { const {_} = useLingui() @@ -46,7 +49,7 @@ export function ImageContextMenu({ } return ( - + + openLightbox({ + images: items.map(item => ({ + ...item, + thumbRect: null, + thumbRef: null, + thumbDimensions: null, + thumbBorderRadius: tokens.borderRadius.md, + type: 'image', + })), + index, + }) if (images.length === 1) { const image = images[0] @@ -70,19 +82,7 @@ export function ImageEmbed({ fullsizeUri={image.fullsize} aspectRatio={aspect} borderRadius={tokens.borderRadius.md} - onPreviewPress={() => - openLightbox({ - images: items.map(item => ({ - ...item, - thumbRect: null, - thumbRef: null, - thumbDimensions: null, - thumbBorderRadius: tokens.borderRadius.md, - type: 'image', - })), - index: 0, - }) - }> + onPreviewPress={() => onPreviewPress(0)}> @@ -126,6 +127,7 @@ export function ImageEmbed({ images={images} onPress={onPress} onPressIn={onPressIn} + onPreviewPress={onPreviewPress} viewContext={rest.viewContext} /> diff --git a/src/components/images/Gallery/index.tsx b/src/components/images/Gallery/index.tsx index 1566f09736..1f2056c466 100644 --- a/src/components/images/Gallery/index.tsx +++ b/src/components/images/Gallery/index.tsx @@ -24,7 +24,7 @@ import {mergeRefs} from '#/lib/merge-refs' import {useA11y} from '#/state/a11y' import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture' -import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' +import {atoms as a, tokens, useBreakpoints, useTheme, web} from '#/alf' import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/components/icons/ArrowsDiagonal' import {AutoSizedImage} from '#/components/images/AutoSizedImage' import { @@ -36,6 +36,7 @@ import {useKeyboardHandlers} from '#/components/images/Gallery/useKeyboardHandle import {usePointerHandlers} from '#/components/images/Gallery/usePointerHandlers' import {getAspectRatio} from '#/components/images/Gallery/utils' import {MediaInsetBorder} from '#/components/MediaInsetBorder' +import {ImageContextMenu} from '#/components/Post/Embed/ImageContextMenu' import {PostEmbedViewContext} from '#/components/Post/Embed/types' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' @@ -52,6 +53,7 @@ interface GalleryProps { fetchedDims: (Dimensions | null)[], ) => void onPressIn?: (index: number) => void + onPreviewPress?: (index: number) => void viewContext?: PostEmbedViewContext } @@ -95,6 +97,7 @@ export function Gallery({ images, onPress, onPressIn, + onPreviewPress, viewContext, }: GalleryProps) { const {t: l} = useLingui() @@ -306,6 +309,9 @@ export function Gallery({ : undefined } onPressIn={onPressIn ? () => onPressIn(index) : undefined} + onPreviewPress={ + onPreviewPress ? () => onPreviewPress(index) : undefined + } /> ) }} @@ -378,6 +384,7 @@ function GalleryImage({ onThumbDims, onPress, onPressIn, + onPreviewPress, }: { contentHeight: number image: AppBskyEmbedImages.ViewImage @@ -391,6 +398,7 @@ function GalleryImage({ onThumbDims: (index: number, dims: Dimensions) => void onPress?: () => void onPressIn?: () => void + onPreviewPress?: () => void }) { const t = useTheme() const {t: l} = useLingui() @@ -416,124 +424,130 @@ function GalleryImage({ collapsable={false} aria-roledescription={l`slide`} aria-label={image.alt || l`Image ${index + 1} of ${imageCount}`}> - setFocused(true)} - onBlur={() => setFocused(false)} - accessibilityRole="button" - accessibilityLabel={image.alt || l`Image ${index + 1}`} - accessibilityHint={l`Opens full image`} - android_ripple={{ - color: utils.alpha(t.atoms.bg.backgroundColor, 0.2), - foreground: true, - }} - style={({pressed}) => [ - a.rounded_md, - a.overflow_hidden, - t.atoms.bg_contrast_25, - web([ - { - cursor: 'inherit', - outline: 0, - border: 0, - }, - a.transition_transform, - {transitionDuration: '200ms'}, - pressed && {transform: [{scale: 0.99}]}, - ]), - ]}> - { - const ar = getAspectRatio(e.source) - if (ar && ar !== aspectRatio) { - setAspectRatio(ar) - } - onThumbDims(index, { - width: e.source.width, - height: e.source.height, - }) + + setFocused(true)} + onBlur={() => setFocused(false)} + accessibilityRole="button" + accessibilityLabel={image.alt || l`Image ${index + 1}`} + accessibilityHint={l`Opens full image`} + android_ripple={{ + color: utils.alpha(t.atoms.bg.backgroundColor, 0.2), + foreground: true, }} - /> - - {(hasAlt || isCropped) && !hideBadges ? ( - [ + a.rounded_md, + a.overflow_hidden, + t.atoms.bg_contrast_25, + web([ { - bottom: a.p_xs.padding, - right: a.p_xs.padding, - gap: 3, + cursor: 'inherit', + outline: 0, + border: 0, }, - largeAltBadge && { - gap: 4, - }, - ]}> - {isCropped && ( - - - - )} - {hasAlt && ( - - - ALT - - - )} - - ) : null} + a.transition_transform, + {transitionDuration: '200ms'}, + pressed && {transform: [{scale: 0.99}]}, + ]), + ]}> + { + const ar = getAspectRatio(e.source) + if (ar && ar !== aspectRatio) { + setAspectRatio(ar) + } + onThumbDims(index, { + width: e.source.width, + height: e.source.height, + }) + }} + /> - + {isCropped && ( + + + + )} + {hasAlt && ( + + + ALT + + + )} + + ) : null} + + - + /> + + ) } diff --git a/src/components/images/ImageLayoutGrid.tsx b/src/components/images/ImageLayoutGrid.tsx index 0017ddf9cf..ef9f351b10 100644 --- a/src/components/images/ImageLayoutGrid.tsx +++ b/src/components/images/ImageLayoutGrid.tsx @@ -17,6 +17,7 @@ interface ImageLayoutGridProps { ) => void onLongPress?: (index: number) => void onPressIn?: (index: number) => void + onPreviewPress?: (index: number) => void style?: StyleProp viewContext?: PostEmbedViewContext } @@ -48,6 +49,7 @@ interface ImageLayoutGridInnerProps { ) => void onLongPress?: (index: number) => void onPressIn?: (index: number) => void + onPreviewPress?: (index: number) => void viewContext?: PostEmbedViewContext gap: {gap: number} } diff --git a/src/components/images/ImageLayoutGridItem.tsx b/src/components/images/ImageLayoutGridItem.tsx index b809a783fa..2f694ddf52 100644 --- a/src/components/images/ImageLayoutGridItem.tsx +++ b/src/components/images/ImageLayoutGridItem.tsx @@ -11,6 +11,7 @@ import {type Dimensions} from '#/lib/media/types' import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' import {atoms as a, useTheme} from '#/alf' import {MediaInsetBorder} from '#/components/MediaInsetBorder' +import {ImageContextMenu} from '#/components/Post/Embed/ImageContextMenu' import {PostEmbedViewContext} from '#/components/Post/Embed/types' import {Text} from '#/components/Typography' @@ -26,6 +27,8 @@ interface Props { ) => void onLongPress?: EventFunction onPressIn?: EventFunction + /** Fired from the native iOS peek preview tap. */ + onPreviewPress?: EventFunction imageStyle?: StyleProp viewContext?: PostEmbedViewContext insetBorderStyle?: StyleProp @@ -40,6 +43,7 @@ export function GalleryItem({ onPress, onPressIn, onLongPress, + onPreviewPress, viewContext, insetBorderStyle, containerRefs, @@ -52,46 +56,60 @@ export function GalleryItem({ const hasAlt = !!image.alt const hideBadges = viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia + + const aspect = + image.aspectRatio && image.aspectRatio.height > 0 + ? image.aspectRatio.width / image.aspectRatio.height + : undefined + return ( - onPress(index, containerRefs, thumbDimsRef.current.slice()) - : undefined + onPreviewPress(index) : undefined } - onPressIn={onPressIn ? () => onPressIn(index) : undefined} - onLongPress={onLongPress ? () => onLongPress(index) : undefined} - android_ripple={{ - color: utils.alpha(t.atoms.bg.backgroundColor, 0.2), - foreground: true, - }} - style={[ - a.flex_1, - a.overflow_hidden, - t.atoms.bg_contrast_25, - imageStyle, - ]} - accessibilityRole="button" - accessibilityLabel={image.alt || _(msg`Image`)} - accessibilityHint=""> - { - thumbDimsRef.current[index] = { - width: e.source.width, - height: e.source.height, - } + style={a.flex_1}> + onPress(index, containerRefs, thumbDimsRef.current.slice()) + : undefined + } + onPressIn={onPressIn ? () => onPressIn(index) : undefined} + onLongPress={onLongPress ? () => onLongPress(index) : undefined} + android_ripple={{ + color: utils.alpha(t.atoms.bg.backgroundColor, 0.2), + foreground: true, }} - loading="lazy" - /> - - + style={[ + a.flex_1, + a.overflow_hidden, + t.atoms.bg_contrast_25, + imageStyle, + ]} + accessibilityRole="button" + accessibilityLabel={image.alt || _(msg`Image`)} + accessibilityHint=""> + { + thumbDimsRef.current[index] = { + width: e.source.width, + height: e.source.height, + } + }} + loading="lazy" + /> + + + {hasAlt && !hideBadges ? (