temp rm pagerview

This commit is contained in:
Samuel Newman
2025-09-12 13:11:52 +03:00
parent 4aac5c1482
commit 30c2394271
4 changed files with 13 additions and 417 deletions
-1
View File
@@ -192,7 +192,6 @@
"react-native-get-random-values": "~1.11.0", "react-native-get-random-values": "~1.11.0",
"react-native-keyboard-controller": "1.18.5", "react-native-keyboard-controller": "1.18.5",
"react-native-mmkv": "^2.12.2", "react-native-mmkv": "^2.12.2",
"react-native-pager-view": "6.8.0",
"react-native-progress": "bluesky-social/react-native-progress", "react-native-progress": "bluesky-social/react-native-progress",
"react-native-qrcode-styled": "^0.3.3", "react-native-qrcode-styled": "^0.3.3",
"react-native-reanimated": "^3.19.1", "react-native-reanimated": "^3.19.1",
+7 -306
View File
@@ -8,37 +8,27 @@
// Original code copied and simplified from the link below as the codebase is currently not maintained: // Original code copied and simplified from the link below as the codebase is currently not maintained:
// https://github.com/jobtoday/react-native-image-viewing // https://github.com/jobtoday/react-native-image-viewing
import React, {useCallback, useEffect, useMemo, useState} from 'react' import React, {useEffect, useMemo, useState} from 'react'
import {LayoutAnimation, PixelRatio, StyleSheet, View} from 'react-native' import {LayoutAnimation, StyleSheet, View} from 'react-native'
import {SystemBars} from 'react-native-edge-to-edge' import {SystemBars} from 'react-native-edge-to-edge'
import {Gesture} from 'react-native-gesture-handler'
import PagerView from 'react-native-pager-view'
import Animated, { import Animated, {
type AnimatedRef, type AnimatedRef,
cancelAnimation, cancelAnimation,
interpolate,
measure, measure,
runOnJS, runOnJS,
type SharedValue, type SharedValue,
useAnimatedReaction, useAnimatedReaction,
useAnimatedRef, useAnimatedRef,
useAnimatedStyle, useAnimatedStyle,
useDerivedValue,
useSharedValue, useSharedValue,
withDecay,
withSpring, withSpring,
type WithSpringConfig, type WithSpringConfig,
} from 'react-native-reanimated' } from 'react-native-reanimated'
import { import {SafeAreaView} from 'react-native-safe-area-context'
SafeAreaView,
useSafeAreaFrame,
useSafeAreaInsets,
} from 'react-native-safe-area-context'
import * as ScreenOrientation from 'expo-screen-orientation' import * as ScreenOrientation from 'expo-screen-orientation'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Trans} from '@lingui/macro' import {Trans} from '@lingui/macro'
import {type Dimensions} from '#/lib/media/types'
import {colors, s} from '#/lib/styles' import {colors, s} from '#/lib/styles'
import {isIOS} from '#/platform/detection' import {isIOS} from '#/platform/detection'
import {type Lightbox} from '#/state/lightbox' import {type Lightbox} from '#/state/lightbox'
@@ -48,14 +38,10 @@ import {ScrollView} from '#/view/com/util/Views'
import {useTheme} from '#/alf' import {useTheme} from '#/alf'
import {setSystemUITheme} from '#/alf/util/systemUI' import {setSystemUITheme} from '#/alf/util/systemUI'
import {PlatformInfo} from '../../../../../modules/expo-bluesky-swiss-army' import {PlatformInfo} from '../../../../../modules/expo-bluesky-swiss-army'
import {type ImageSource, type Transform} from './@types' import {type ImageSource} from './@types'
import ImageDefaultHeader from './components/ImageDefaultHeader' import ImageDefaultHeader from './components/ImageDefaultHeader'
import ImageItem from './components/ImageItem/ImageItem'
type Rect = {x: number; y: number; width: number; height: number}
const PORTRAIT_UP = ScreenOrientation.OrientationLock.PORTRAIT_UP const PORTRAIT_UP = ScreenOrientation.OrientationLock.PORTRAIT_UP
const PIXEL_RATIO = PixelRatio.get()
const SLOW_SPRING: WithSpringConfig = { const SLOW_SPRING: WithSpringConfig = {
mass: isIOS ? 1.25 : 0.75, mass: isIOS ? 1.25 : 0.75,
@@ -210,10 +196,9 @@ function ImageView({
}) { }) {
const {images, index: initialImageIndex} = lightbox const {images, index: initialImageIndex} = lightbox
const isAnimated = useMemo(() => canAnimate(lightbox), [lightbox]) const isAnimated = useMemo(() => canAnimate(lightbox), [lightbox])
const [isScaled, setIsScaled] = useState(false) const [isScaled] = useState(false)
const [isDragging, setIsDragging] = useState(false) const [imageIndex] = useState(initialImageIndex)
const [imageIndex, setImageIndex] = useState(initialImageIndex) const [showControls] = useState(true)
const [showControls, setShowControls] = useState(true)
const [isAltExpanded, setAltExpanded] = React.useState(false) const [isAltExpanded, setAltExpanded] = React.useState(false)
const dismissSwipeTranslateY = useSharedValue(0) const dismissSwipeTranslateY = useSharedValue(0)
const isFlyingAway = useSharedValue(false) const isFlyingAway = useSharedValue(false)
@@ -285,17 +270,6 @@ function ImageView({
} }
}) })
const onTap = useCallback(() => {
setShowControls(show => !show)
}, [])
const onZoom = useCallback((nextIsScaled: boolean) => {
setIsScaled(nextIsScaled)
if (nextIsScaled) {
setShowControls(false)
}
}, [])
useAnimatedReaction( useAnimatedReaction(
() => { () => {
const screenSize = measure(safeAreaRef) const screenSize = measure(safeAreaRef)
@@ -335,37 +309,6 @@ function ImageView({
style={[styles.backdrop, backdropStyle]} style={[styles.backdrop, backdropStyle]}
renderToHardwareTextureAndroid renderToHardwareTextureAndroid
/> />
<PagerView
scrollEnabled={!isScaled}
initialPage={initialImageIndex}
onPageSelected={e => {
setImageIndex(e.nativeEvent.position)
setIsScaled(false)
}}
onPageScrollStateChanged={e => {
setIsDragging(e.nativeEvent.pageScrollState !== 'idle')
}}
overdrag={true}
style={styles.pager}>
{images.map((imageSrc, i) => (
<View key={imageSrc.uri}>
<LightboxImage
onTap={onTap}
onZoom={onZoom}
imageSrc={imageSrc}
onRequestClose={onRequestClose}
isScrollViewBeingDragged={isDragging}
showControls={showControls}
safeAreaRef={safeAreaRef}
isScaled={isScaled}
isFlyingAway={isFlyingAway}
isActive={i === imageIndex}
dismissSwipeTranslateY={dismissSwipeTranslateY}
openProgress={openProgress}
/>
</View>
))}
</PagerView>
<View style={styles.controls}> <View style={styles.controls}>
<Animated.View <Animated.View
style={animatedHeaderStyle} style={animatedHeaderStyle}
@@ -389,164 +332,6 @@ function ImageView({
) )
} }
function LightboxImage({
imageSrc,
onTap,
onZoom,
onRequestClose,
isScrollViewBeingDragged,
isScaled,
isFlyingAway,
isActive,
showControls,
safeAreaRef,
openProgress,
dismissSwipeTranslateY,
}: {
imageSrc: ImageSource
onRequestClose: () => void
onTap: () => void
onZoom: (scaled: boolean) => void
isScrollViewBeingDragged: boolean
isScaled: boolean
isActive: boolean
isFlyingAway: SharedValue<boolean>
showControls: boolean
safeAreaRef: AnimatedRef<View>
openProgress: SharedValue<number>
dismissSwipeTranslateY: SharedValue<number>
}) {
const [fetchedDims, setFetchedDims] = React.useState<Dimensions | null>(null)
const dims = fetchedDims ?? imageSrc.dimensions ?? imageSrc.thumbDimensions
let imageAspect: number | undefined
if (dims) {
imageAspect = dims.width / dims.height
if (Number.isNaN(imageAspect)) {
imageAspect = undefined
}
}
const safeFrameDelayedForJSThreadOnly = useSafeAreaFrame()
const safeInsetsDelayedForJSThreadOnly = useSafeAreaInsets()
const measureSafeArea = React.useCallback(() => {
'worklet'
let safeArea: Rect | null = measure(safeAreaRef)
if (!safeArea) {
if (_WORKLET) {
console.error('Expected to always be able to measure safe area.')
}
const frame = safeFrameDelayedForJSThreadOnly
const insets = safeInsetsDelayedForJSThreadOnly
safeArea = {
x: frame.x + insets.left,
y: frame.y + insets.top,
width: frame.width - insets.left - insets.right,
height: frame.height - insets.top - insets.bottom,
}
}
return safeArea
}, [
safeFrameDelayedForJSThreadOnly,
safeInsetsDelayedForJSThreadOnly,
safeAreaRef,
])
const {thumbRect} = imageSrc
const transforms = useDerivedValue(() => {
'worklet'
const safeArea = measureSafeArea()
const openProgressValue = openProgress.get()
const dismissTranslateY =
isActive && openProgressValue === 1 ? dismissSwipeTranslateY.get() : 0
if (openProgressValue === 0 && isFlyingAway.get()) {
return {
isHidden: true,
isResting: false,
scaleAndMoveTransform: [],
cropFrameTransform: [],
cropContentTransform: [],
}
}
if (isActive && thumbRect && imageAspect && openProgressValue < 1) {
return interpolateTransform(
openProgressValue,
thumbRect,
safeArea,
imageAspect,
)
}
return {
isHidden: false,
isResting: dismissTranslateY === 0,
scaleAndMoveTransform: [{translateY: dismissTranslateY}],
cropFrameTransform: [],
cropContentTransform: [],
}
})
const dismissSwipePan = Gesture.Pan()
.enabled(isActive && !isScaled)
.activeOffsetY([-10, 10])
.failOffsetX([-10, 10])
.maxPointers(1)
.onUpdate(e => {
'worklet'
if (openProgress.get() !== 1 || isFlyingAway.get()) {
return
}
dismissSwipeTranslateY.set(e.translationY)
})
.onEnd(e => {
'worklet'
if (openProgress.get() !== 1 || isFlyingAway.get()) {
return
}
if (Math.abs(e.velocityY) > 200) {
isFlyingAway.set(true)
if (dismissSwipeTranslateY.get() === 0) {
// HACK: If the initial value is 0, withDecay() animation doesn't start.
// This is a bug in Reanimated, but for now we'll work around it like this.
dismissSwipeTranslateY.set(1)
}
dismissSwipeTranslateY.set(() => {
'worklet'
return withDecay({
velocity: e.velocityY,
velocityFactor: Math.max(3500 / Math.abs(e.velocityY), 1), // Speed up if it's too slow.
deceleration: 1, // Danger! This relies on the reaction below stopping it.
})
})
} else {
dismissSwipeTranslateY.set(() => {
'worklet'
return withSpring(0, {
stiffness: 700,
damping: 50,
})
})
}
})
return (
<ImageItem
imageSrc={imageSrc}
onTap={onTap}
onZoom={onZoom}
onRequestClose={onRequestClose}
onLoad={setFetchedDims}
isScrollViewBeingDragged={isScrollViewBeingDragged}
showControls={showControls}
measureSafeArea={measureSafeArea}
imageAspect={imageAspect}
imageDimensions={dims ?? undefined}
dismissSwipePan={dismissSwipePan}
transforms={transforms}
/>
)
}
function LightboxFooter({ function LightboxFooter({
images, images,
index, index,
@@ -698,90 +483,6 @@ const styles = StyleSheet.create({
}, },
}) })
function interpolatePx(
px: number,
inputRange: readonly number[],
outputRange: readonly number[],
) {
'worklet'
const value = interpolate(px, inputRange, outputRange)
return Math.round(value * PIXEL_RATIO) / PIXEL_RATIO
}
function interpolateTransform(
progress: number,
thumbnailDims: {
pageX: number
width: number
pageY: number
height: number
},
safeArea: {width: number; height: number; x: number; y: number},
imageAspect: number,
): {
scaleAndMoveTransform: Transform
cropFrameTransform: Transform
cropContentTransform: Transform
isResting: boolean
isHidden: boolean
} {
'worklet'
const thumbAspect = thumbnailDims.width / thumbnailDims.height
let uncroppedInitialWidth
let uncroppedInitialHeight
if (imageAspect > thumbAspect) {
uncroppedInitialWidth = thumbnailDims.height * imageAspect
uncroppedInitialHeight = thumbnailDims.height
} else {
uncroppedInitialWidth = thumbnailDims.width
uncroppedInitialHeight = thumbnailDims.width / imageAspect
}
const safeAreaAspect = safeArea.width / safeArea.height
let finalWidth
let finalHeight
if (safeAreaAspect > imageAspect) {
finalWidth = safeArea.height * imageAspect
finalHeight = safeArea.height
} else {
finalWidth = safeArea.width
finalHeight = safeArea.width / imageAspect
}
const initialScale = Math.min(
uncroppedInitialWidth / finalWidth,
uncroppedInitialHeight / finalHeight,
)
const croppedFinalWidth = thumbnailDims.width / initialScale
const croppedFinalHeight = thumbnailDims.height / initialScale
const screenCenterX = safeArea.width / 2
const screenCenterY = safeArea.height / 2
const thumbnailSafeAreaX = thumbnailDims.pageX - safeArea.x
const thumbnailSafeAreaY = thumbnailDims.pageY - safeArea.y
const thumbnailCenterX = thumbnailSafeAreaX + thumbnailDims.width / 2
const thumbnailCenterY = thumbnailSafeAreaY + thumbnailDims.height / 2
const initialTranslateX = thumbnailCenterX - screenCenterX
const initialTranslateY = thumbnailCenterY - screenCenterY
const scale = interpolate(progress, [0, 1], [initialScale, 1])
const translateX = interpolatePx(progress, [0, 1], [initialTranslateX, 0])
const translateY = interpolatePx(progress, [0, 1], [initialTranslateY, 0])
const cropScaleX = interpolate(
progress,
[0, 1],
[croppedFinalWidth / finalWidth, 1],
)
const cropScaleY = interpolate(
progress,
[0, 1],
[croppedFinalHeight / finalHeight, 1],
)
return {
isHidden: false,
isResting: progress === 1,
scaleAndMoveTransform: [{translateX}, {translateY}, {scale}],
cropFrameTransform: [{scaleX: cropScaleX}, {scaleY: cropScaleY}],
cropContentTransform: [{scaleX: 1 / cropScaleX}, {scaleY: 1 / cropScaleY}],
}
}
function withClampedSpring(value: any, config: WithSpringConfig) { function withClampedSpring(value: any, config: WithSpringConfig) {
'worklet' 'worklet'
return withSpring(value, {...config, overshootClamping: true}) return withSpring(value, {...config, overshootClamping: true})
+6 -105
View File
@@ -1,6 +1,5 @@
import { import {
type JSX, type JSX,
memo,
useCallback, useCallback,
useContext, useContext,
useImperativeHandle, useImperativeHandle,
@@ -11,25 +10,13 @@ import {
import {View} from 'react-native' import {View} from 'react-native'
import {DrawerGestureContext} from 'react-native-drawer-layout' import {DrawerGestureContext} from 'react-native-drawer-layout'
import {Gesture, GestureDetector} from 'react-native-gesture-handler' import {Gesture, GestureDetector} from 'react-native-gesture-handler'
import PagerView, { import {type SharedValue, useSharedValue} from 'react-native-reanimated'
type PagerViewOnPageScrollEventData,
type PagerViewOnPageSelectedEvent,
type PagerViewOnPageSelectedEventData,
type PageScrollStateChangedNativeEventData,
} from 'react-native-pager-view'
import Animated, {
runOnJS,
type SharedValue,
useEvent,
useHandler,
useSharedValue,
} from 'react-native-reanimated'
import {useFocusEffect} from '@react-navigation/native' import {useFocusEffect} from '@react-navigation/native'
import {useSetDrawerSwipeDisabled} from '#/state/shell' import {useSetDrawerSwipeDisabled} from '#/state/shell'
import {atoms as a, native} from '#/alf' import {atoms as a, native} from '#/alf'
export type PageSelectedEvent = PagerViewOnPageSelectedEvent export type PageSelectedEvent = any
export interface PagerRef { export interface PagerRef {
setPage: (index: number) => void setPage: (index: number) => void
@@ -58,23 +45,18 @@ interface Props {
testID?: string testID?: string
} }
const AnimatedPagerView = Animated.createAnimatedComponent(PagerView)
const MemoizedAnimatedPagerView = memo(AnimatedPagerView)
export function Pager({ export function Pager({
ref, ref,
children, children,
initialPage = 0, initialPage = 0,
renderTabBar, renderTabBar,
onPageSelected: parentOnPageSelected,
onTabPressed: parentOnTabPressed, onTabPressed: parentOnTabPressed,
onPageScrollStateChanged: parentOnPageScrollStateChanged,
testID, testID,
}: React.PropsWithChildren<Props>) { }: React.PropsWithChildren<Props>) {
const [selectedPage, setSelectedPage] = useState(initialPage) const [selectedPage] = useState(initialPage)
const pagerView = useRef<PagerView>(null) const pagerView = useRef<any>(null)
const [isIdle, setIsIdle] = useState(true) const [isIdle] = useState(true)
const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
@@ -92,14 +74,6 @@ export function Pager({
}, },
})) }))
const onPageSelectedJSThread = useCallback(
(nextPosition: number) => {
setSelectedPage(nextPosition)
parentOnPageSelected?.(nextPosition)
},
[setSelectedPage, parentOnPageSelected],
)
const onTabBarSelect = useCallback( const onTabBarSelect = useCallback(
(index: number) => { (index: number) => {
parentOnTabPressed?.(index) parentOnTabPressed?.(index)
@@ -110,38 +84,6 @@ export function Pager({
const dragState = useSharedValue<'idle' | 'settling' | 'dragging'>('idle') const dragState = useSharedValue<'idle' | 'settling' | 'dragging'>('idle')
const dragProgress = useSharedValue(selectedPage) const dragProgress = useSharedValue(selectedPage)
const didInit = useSharedValue(false)
const handlePageScroll = usePagerHandlers(
{
onPageScroll(e: PagerViewOnPageScrollEventData) {
'worklet'
if (didInit.get() === false) {
// On iOS, there's a spurious scroll event with 0 position
// even if a different page was supplied as the initial page.
// Ignore it and wait for the first confirmed selection instead.
return
}
dragProgress.set(e.offset + e.position)
},
onPageScrollStateChanged(e: PageScrollStateChangedNativeEventData) {
'worklet'
runOnJS(setIsIdle)(e.pageScrollState === 'idle')
if (dragState.get() === 'idle' && e.pageScrollState === 'settling') {
// This is a programmatic scroll on Android.
// Stay "idle" to match iOS and avoid confusing downstream code.
return
}
dragState.set(e.pageScrollState)
parentOnPageScrollStateChanged?.(e.pageScrollState)
},
onPageSelected(e: PagerViewOnPageSelectedEventData) {
'worklet'
didInit.set(true)
runOnJS(onPageSelectedJSThread)(e.position)
},
},
[parentOnPageScrollStateChanged],
)
return ( return (
<View testID={testID} style={[a.flex_1, native(a.overflow_hidden)]}> <View testID={testID} style={[a.flex_1, native(a.overflow_hidden)]}>
@@ -151,15 +93,7 @@ export function Pager({
dragProgress, dragProgress,
dragState, dragState,
})} })}
<DrawerGestureRequireFail> <DrawerGestureRequireFail>{children}</DrawerGestureRequireFail>
<MemoizedAnimatedPagerView
ref={pagerView}
style={a.flex_1}
initialPage={initialPage}
onPageScroll={handlePageScroll}>
{children}
</MemoizedAnimatedPagerView>
</DrawerGestureRequireFail>
</View> </View>
) )
} }
@@ -177,36 +111,3 @@ function DrawerGestureRequireFail({children}: {children: React.ReactNode}) {
return <GestureDetector gesture={nativeGesture}>{children}</GestureDetector> return <GestureDetector gesture={nativeGesture}>{children}</GestureDetector>
} }
function usePagerHandlers(
handlers: {
onPageScroll: (e: PagerViewOnPageScrollEventData) => void
onPageScrollStateChanged: (e: PageScrollStateChangedNativeEventData) => void
onPageSelected: (e: PagerViewOnPageSelectedEventData) => void
},
dependencies: unknown[],
) {
const {doDependenciesDiffer} = useHandler(handlers as any, dependencies)
const subscribeForEvents = [
'onPageScroll',
'onPageScrollStateChanged',
'onPageSelected',
]
return useEvent(
event => {
'worklet'
const {onPageScroll, onPageScrollStateChanged, onPageSelected} = handlers
if (event.eventName.endsWith('onPageScroll')) {
onPageScroll(event as any as PagerViewOnPageScrollEventData)
} else if (event.eventName.endsWith('onPageScrollStateChanged')) {
onPageScrollStateChanged(
event as any as PageScrollStateChangedNativeEventData,
)
} else if (event.eventName.endsWith('onPageSelected')) {
onPageSelected(event as any as PagerViewOnPageSelectedEventData)
}
},
subscribeForEvents,
doDependenciesDiffer,
)
}
-5
View File
@@ -16806,11 +16806,6 @@ react-native-mmkv@^2.12.2:
resolved "https://registry.yarnpkg.com/react-native-mmkv/-/react-native-mmkv-2.12.2.tgz#4bba0f5f04e2cf222494cce3a9794ba6a4894dee" resolved "https://registry.yarnpkg.com/react-native-mmkv/-/react-native-mmkv-2.12.2.tgz#4bba0f5f04e2cf222494cce3a9794ba6a4894dee"
integrity sha512-6058Aq0p57chPrUutLGe9fYoiDVDNMU2PKV+lLFUJ3GhoHvUrLdsS1PDSCLr00yqzL4WJQ7TTzH+V8cpyrNcfg== integrity sha512-6058Aq0p57chPrUutLGe9fYoiDVDNMU2PKV+lLFUJ3GhoHvUrLdsS1PDSCLr00yqzL4WJQ7TTzH+V8cpyrNcfg==
react-native-pager-view@6.8.0:
version "6.8.0"
resolved "https://registry.yarnpkg.com/react-native-pager-view/-/react-native-pager-view-6.8.0.tgz#5bac05203d911bf9bf039d47db41b1313dbd1a7a"
integrity sha512-/wgFV8nB4TLnQ6j9e3TvNrHbPF5TINMZHaXt86GOV0NSJNMVGkWguniJVKrYLm85LL8KVhRkgdh43Rdu7PvW1A==
react-native-progress@bluesky-social/react-native-progress: react-native-progress@bluesky-social/react-native-progress:
version "5.0.0" version "5.0.0"
resolved "https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4" resolved "https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4"