diff --git a/src/components/Post/Embed/ImageEmbed.tsx b/src/components/Post/Embed/ImageEmbed.tsx index 6ca166ff10..2adc33b6e6 100644 --- a/src/components/Post/Embed/ImageEmbed.tsx +++ b/src/components/Post/Embed/ImageEmbed.tsx @@ -4,7 +4,7 @@ import {Image} from 'expo-image' import {useLightboxControls} from '#/state/lightbox' import {type Dimensions} from '#/view/com/lightbox/ImageViewing/@types' -import {atoms as a} from '#/alf' +import {atoms as a, tokens} from '#/alf' import {AutoSizedImage} from '#/components/images/AutoSizedImage' import {Gallery} from '#/components/images/Gallery' import {ImageLayoutGrid} from '#/components/images/ImageLayoutGrid' @@ -42,6 +42,7 @@ export function ImageEmbed({ thumbRect: null, thumbRef: refs[i] ?? null, thumbDimensions: fetchedDims[i] ?? null, + thumbBorderRadius: tokens.borderRadius.md, type: 'image', })), index, diff --git a/src/view/com/lightbox/ImageViewing/@types/index.ts b/src/view/com/lightbox/ImageViewing/@types/index.ts index 8435513af0..c3aca3d859 100644 --- a/src/view/com/lightbox/ImageViewing/@types/index.ts +++ b/src/view/com/lightbox/ImageViewing/@types/index.ts @@ -29,6 +29,7 @@ export type ImageSource = { thumbDimensions: Dimensions | null thumbRect: MeasuredDimensions | null thumbRef?: AnimatedRef | null + thumbBorderRadius?: number alt?: string type: 'image' | 'circle-avi' | 'rect-avi' } @@ -37,3 +38,12 @@ export type Transform = Exclude< TransformsStyle['transform'], string | undefined > + +export type LightboxTransforms = { + scaleAndMoveTransform: Transform + cropFrameTransform: Transform + cropContentTransform: Transform + borderRadius: number + isResting: boolean + isHidden: boolean +} diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx index 6e045ede34..9d128a41cf 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx +++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx @@ -19,7 +19,7 @@ import {Image} from 'expo-image' import { type Dimensions as ImageDimensions, type ImageSource, - type Transform, + type LightboxTransforms, } from '../../@types' import { applyRounding, @@ -53,15 +53,7 @@ type Props = { imageAspect: number | undefined imageDimensions: ImageDimensions | undefined dismissSwipePan: PanGesture - transforms: Readonly< - SharedValue<{ - scaleAndMoveTransform: Transform - cropFrameTransform: Transform - cropContentTransform: Transform - isResting: boolean - isHidden: boolean - }> - > + transforms: Readonly> } const ImageItem = ({ imageSrc, @@ -339,11 +331,12 @@ const ImageItem = ({ }) const imageCropStyle = useAnimatedStyle(() => { - const {cropFrameTransform} = transforms.get() + const {cropFrameTransform, borderRadius: br} = transforms.get() return { flex: 1, overflow: 'hidden', transform: cropFrameTransform, + borderRadius: br, } }) diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx index 2af9816301..52173e057a 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx +++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx @@ -29,7 +29,7 @@ import {Image} from 'expo-image' import { type Dimensions as ImageDimensions, type ImageSource, - type Transform, + type LightboxTransforms, } from '../../@types' const MAX_ORIGINAL_IMAGE_ZOOM = 2 @@ -52,15 +52,7 @@ type Props = { imageAspect: number | undefined imageDimensions: ImageDimensions | undefined dismissSwipePan: PanGesture - transforms: Readonly< - SharedValue<{ - scaleAndMoveTransform: Transform - cropFrameTransform: Transform - cropContentTransform: Transform - isResting: boolean - isHidden: boolean - }> - > + transforms: Readonly> } const ImageItem = ({ @@ -170,10 +162,11 @@ const ImageItem = ({ const imageCropStyle = useAnimatedStyle(() => { const screenSize = measureSafeArea() - const {cropFrameTransform} = transforms.get() + const {cropFrameTransform, borderRadius: br} = transforms.get() return { overflow: 'hidden', transform: cropFrameTransform, + borderRadius: br, width: screenSize.width, maxHeight: screenSize.height, alignSelf: 'center', diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx index 767fcadc20..cd0e45d3c5 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx +++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx @@ -9,7 +9,7 @@ import {type Dimensions} from '#/lib/media/types' import { type Dimensions as ImageDimensions, type ImageSource, - type Transform, + type LightboxTransforms, } from '../../@types' type Props = { @@ -29,15 +29,7 @@ type Props = { imageAspect: number | undefined imageDimensions: ImageDimensions | undefined dismissSwipePan: PanGesture - transforms: Readonly< - SharedValue<{ - scaleAndMoveTransform: Transform - cropFrameTransform: Transform - cropContentTransform: Transform - isResting: boolean - isHidden: boolean - }> - > + transforms: Readonly> } const ImageItem = (_props: Props) => { diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx index 82d1ce27a5..40cabd722d 100644 --- a/src/view/com/lightbox/ImageViewing/index.tsx +++ b/src/view/com/lightbox/ImageViewing/index.tsx @@ -52,7 +52,11 @@ import {useTheme} from '#/alf' import {setSystemUITheme} from '#/alf/util/systemUI' import {IS_IOS} from '#/env' import {PlatformInfo} from '../../../../../modules/expo-bluesky-swiss-army' -import {type ImageSource, type Transform} from './@types' +import { + type ImageSource, + type LightboxTransforms, + type Transform, +} from './@types' import ImageDefaultHeader from './components/ImageDefaultHeader' import ImageItem from './components/ImageItem/ImageItem' @@ -494,8 +498,8 @@ function LightboxImage({ return safeArea }, [safeAreaRef, heightDelayedForJSThreadOnly, widthDelayedForJSThreadOnly]) - const {thumbRect: thumbRectJS} = imageSrc - const transforms = useDerivedValue(() => { + const {thumbRect: thumbRectJS, thumbBorderRadius} = imageSrc + const transforms = useDerivedValue(() => { 'worklet' const safeArea = measureSafeArea() const openProgressValue = openProgress.get() @@ -506,6 +510,7 @@ function LightboxImage({ return { isHidden: true, isResting: false, + borderRadius: 0, scaleAndMoveTransform: [], cropFrameTransform: [], cropContentTransform: [], @@ -525,12 +530,14 @@ function LightboxImage({ thumbRect, safeArea, imageAspect, + thumbBorderRadius, ) } } return { isHidden: false, isResting: dismissTranslateY === 0, + borderRadius: 0, scaleAndMoveTransform: [{translateY: dismissTranslateY}], cropFrameTransform: [], cropContentTransform: [], @@ -772,10 +779,12 @@ function interpolateTransform( }, safeArea: {width: number; height: number; x: number; y: number}, imageAspect: number, + thumbBorderRadius?: number, ): { scaleAndMoveTransform: Transform cropFrameTransform: Transform cropContentTransform: Transform + borderRadius: number isResting: boolean isHidden: boolean } { @@ -827,12 +836,23 @@ function interpolateTransform( [0, 1], [croppedFinalHeight / finalHeight, 1], ) + // The border radius in the source thumbnail needs to be scaled to account + // for the crop frame and overall scale so it visually matches at progress=0. + const sourceBorderRadius = thumbBorderRadius ?? 0 + const initialCropScaleX = croppedFinalWidth / finalWidth + const borderRadius = interpolate( + progress, + [0, 1], + [sourceBorderRadius / (initialScale * initialCropScaleX), 0], + ) + return { isHidden: false, isResting: progress === 1, scaleAndMoveTransform: [{translateX}, {translateY}, {scale}], cropFrameTransform: [{scaleX: cropScaleX}, {scaleY: cropScaleY}], cropContentTransform: [{scaleX: 1 / cropScaleX}, {scaleY: 1 / cropScaleY}], + borderRadius, } }