Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove unused lightbox options #1616

Merged
merged 11 commits into from
Oct 5, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,11 @@ type Props = {
imageSrc: ImageSource
onRequestClose: () => void
onZoom: (isZoomed: boolean) => void
onLongPress: (image: ImageSource) => void
delayLongPress: number
swipeToCloseEnabled?: boolean
doubleTapToZoomEnabled?: boolean
}

const AnimatedImage = Animated.createAnimatedComponent(Image)

const ImageItem = ({
imageSrc,
onZoom,
onRequestClose,
onLongPress,
delayLongPress,
swipeToCloseEnabled = true,
doubleTapToZoomEnabled = true,
}: Props) => {
const ImageItem = ({imageSrc, onZoom, onRequestClose}: Props) => {
const imageContainer = useRef<ScrollView & NativeMethodsMixin>(null)
const imageDimensions = useImageDimensions(imageSrc)
const [translate, scale] = getImageTransform(imageDimensions, SCREEN)
Expand All @@ -72,17 +60,10 @@ const ImageItem = ({
[onZoom],
)

const onLongPressHandler = useCallback(() => {
onLongPress(imageSrc)
}, [imageSrc, onLongPress])

const [panHandlers, scaleValue, translateValue] = usePanResponder({
initialScale: scale || 1,
initialTranslate: translate || {x: 0, y: 0},
onZoom: onZoomPerformed,
doubleTapToZoomEnabled,
onLongPress: onLongPressHandler,
delayLongPress,
})

const imagesStyles = getImageStyles(
Expand Down Expand Up @@ -126,11 +107,9 @@ const ImageItem = ({
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.imageScrollContainer}
scrollEnabled={swipeToCloseEnabled}
{...(swipeToCloseEnabled && {
onScroll,
onScrollEndDrag,
})}>
scrollEnabled={true}
onScroll={onScroll}
onScrollEndDrag={onScrollEndDrag}>
<AnimatedImage
{...panHandlers}
source={imageSrc}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,57 +16,45 @@ import {
View,
NativeScrollEvent,
NativeSyntheticEvent,
NativeTouchEvent,
TouchableWithoutFeedback,
} from 'react-native'
import {Image} from 'expo-image'

import useDoubleTapToZoom from '../../hooks/useDoubleTapToZoom'
import useImageDimensions from '../../hooks/useImageDimensions'

import {getImageStyles, getImageTransform} from '../../utils'
import {ImageSource} from '../../@types'
import {ImageLoading} from './ImageLoading'

const DOUBLE_TAP_DELAY = 300
const SWIPE_CLOSE_OFFSET = 75
const SWIPE_CLOSE_VELOCITY = 1
const SCREEN = Dimensions.get('screen')
const SCREEN_WIDTH = SCREEN.width
const SCREEN_HEIGHT = SCREEN.height
const MIN_ZOOM = 2
const MAX_SCALE = 2

type Props = {
imageSrc: ImageSource
onRequestClose: () => void
onZoom: (scaled: boolean) => void
onLongPress: (image: ImageSource) => void
delayLongPress: number
swipeToCloseEnabled?: boolean
doubleTapToZoomEnabled?: boolean
}

const AnimatedImage = Animated.createAnimatedComponent(Image)

const ImageItem = ({
imageSrc,
onZoom,
onRequestClose,
onLongPress,
delayLongPress,
swipeToCloseEnabled = true,
doubleTapToZoomEnabled = true,
}: Props) => {
let lastTapTS: number | null = null

const ImageItem = ({imageSrc, onZoom, onRequestClose}: Props) => {
const scrollViewRef = useRef<ScrollView>(null)
const [loaded, setLoaded] = useState(false)
const [scaled, setScaled] = useState(false)
const imageDimensions = useImageDimensions(imageSrc)
const handleDoubleTap = useDoubleTapToZoom(
scrollViewRef,
scaled,
SCREEN,
imageDimensions,
)

const [translate, scale] = getImageTransform(imageDimensions, SCREEN)

// TODO: It's not valid to reinitialize Animated values during render.
// This is a bug.
const scrollValueY = new Animated.Value(0)
const scaleValue = new Animated.Value(scale || 1)
const translateValue = new Animated.ValueXY(translate)
Expand All @@ -91,15 +79,11 @@ const ImageItem = ({
onZoom(currentScaled)
setScaled(currentScaled)

if (
!currentScaled &&
swipeToCloseEnabled &&
Math.abs(velocityY) > SWIPE_CLOSE_VELOCITY
) {
if (!currentScaled && Math.abs(velocityY) > SWIPE_CLOSE_VELOCITY) {
onRequestClose()
}
},
[onRequestClose, onZoom, swipeToCloseEnabled],
[onRequestClose, onZoom],
)

const onScroll = ({nativeEvent}: NativeSyntheticEvent<NativeScrollEvent>) => {
Expand All @@ -112,9 +96,40 @@ const ImageItem = ({
scrollValueY.setValue(offsetY)
}

const onLongPressHandler = useCallback(() => {
onLongPress(imageSrc)
}, [imageSrc, onLongPress])
const handleDoubleTap = useCallback(
(event: NativeSyntheticEvent<NativeTouchEvent>) => {
const nowTS = new Date().getTime()
const scrollResponderRef = scrollViewRef?.current?.getScrollResponder()

if (lastTapTS && nowTS - lastTapTS < DOUBLE_TAP_DELAY) {
let nextZoomRect = {
x: 0,
y: 0,
width: SCREEN.width,
height: SCREEN.height,
}

const willZoom = !scaled
if (willZoom) {
const {pageX, pageY} = event.nativeEvent
nextZoomRect = getZoomRectAfterDoubleTap(
imageDimensions,
pageX,
pageY,
)
}

// @ts-ignore
scrollResponderRef?.scrollResponderZoomTo({
...nextZoomRect, // This rect is in screen coordinates
animated: true,
})
} else {
lastTapTS = nowTS
}
},
[imageDimensions, scaled],
)

return (
<View>
Expand All @@ -126,17 +141,13 @@ const ImageItem = ({
showsVerticalScrollIndicator={false}
maximumZoomScale={maxScrollViewZoom}
contentContainerStyle={styles.imageScrollContainer}
scrollEnabled={swipeToCloseEnabled}
scrollEnabled={true}
onScroll={onScroll}
onScrollEndDrag={onScrollEndDrag}
scrollEventThrottle={1}
{...(swipeToCloseEnabled && {
onScroll,
})}>
scrollEventThrottle={1}>
{(!loaded || !imageDimensions) && <ImageLoading />}
<TouchableWithoutFeedback
onPress={doubleTapToZoomEnabled ? handleDoubleTap : undefined}
onLongPress={onLongPressHandler}
delayLongPress={delayLongPress}
onPress={handleDoubleTap}
accessibilityRole="image"
accessibilityLabel={imageSrc.alt}
accessibilityHint="">
Expand All @@ -161,4 +172,92 @@ const styles = StyleSheet.create({
},
})

const getZoomRectAfterDoubleTap = (
imageDimensions: {width: number; height: number} | null,
touchX: number,
touchY: number,
): {
x: number
y: number
width: number
height: number
} => {
if (!imageDimensions) {
return {
x: 0,
y: 0,
width: SCREEN.width,
height: SCREEN.height,
}
}

// First, let's figure out how much we want to zoom in.
// We want to try to zoom in at least close enough to get rid of black bars.
const imageAspect = imageDimensions.width / imageDimensions.height
const screenAspect = SCREEN.width / SCREEN.height
const zoom = Math.max(
imageAspect / screenAspect,
screenAspect / imageAspect,
MIN_ZOOM,
)
// Unlike in the Android version, we don't constrain the *max* zoom level here.
// Instead, this is done in the ScrollView props so that it constraints pinch too.

// Next, we'll be calculating the rectangle to "zoom into" in screen coordinates.
// We already know the zoom level, so this gives us the rectangle size.
let rectWidth = SCREEN.width / zoom
let rectHeight = SCREEN.height / zoom

// Before we settle on the zoomed rect, figure out the safe area it has to be inside.
// We don't want to introduce new black bars or make existing black bars unbalanced.
let minX = 0
let minY = 0
let maxX = SCREEN.width - rectWidth
let maxY = SCREEN.height - rectHeight
if (imageAspect >= screenAspect) {
// The image has horizontal black bars. Exclude them from the safe area.
const renderedHeight = SCREEN.width / imageAspect
const horizontalBarHeight = (SCREEN.height - renderedHeight) / 2
minY += horizontalBarHeight
maxY -= horizontalBarHeight
} else {
// The image has vertical black bars. Exclude them from the safe area.
const renderedWidth = SCREEN.height * imageAspect
const verticalBarWidth = (SCREEN.width - renderedWidth) / 2
minX += verticalBarWidth
maxX -= verticalBarWidth
}

// Finally, we can position the rect according to its size and the safe area.
let rectX
if (maxX >= minX) {
// Content fills the screen horizontally so we have horizontal wiggle room.
// Try to keep the tapped point under the finger after zoom.
rectX = touchX - touchX / zoom
rectX = Math.min(rectX, maxX)
rectX = Math.max(rectX, minX)
} else {
// Keep the rect centered on the screen so that black bars are balanced.
rectX = SCREEN.width / 2 - rectWidth / 2
}
let rectY
if (maxY >= minY) {
// Content fills the screen vertically so we have vertical wiggle room.
// Try to keep the tapped point under the finger after zoom.
rectY = touchY - touchY / zoom
rectY = Math.min(rectY, maxY)
rectY = Math.max(rectY, minY)
} else {
// Keep the rect centered on the screen so that black bars are balanced.
rectY = SCREEN.height / 2 - rectHeight / 2
}

return {
x: rectX,
y: rectY,
height: rectHeight,
width: rectWidth,
}
}

export default React.memo(ImageItem)
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ type Props = {
imageSrc: ImageSource
onRequestClose: () => void
onZoom: (scaled: boolean) => void
onLongPress: (image: ImageSource) => void
delayLongPress: number
swipeToCloseEnabled?: boolean
doubleTapToZoomEnabled?: boolean
}

const ImageItem = (_props: Props) => {
Expand Down
47 changes: 0 additions & 47 deletions src/view/com/lightbox/ImageViewing/hooks/useAnimatedComponents.ts

This file was deleted.

Loading