Skip to content

Commit

Permalink
Merge pull request #33756 from margelo/@chrispader/restructure-multi-…
Browse files Browse the repository at this point in the history
…gesture-canvas

Extract gesture logic from `MultiGestureCanvas` to hooks and improve code
  • Loading branch information
pecanoro authored Jan 29, 2024
2 parents b2e1e21 + 8ddbf36 commit 523656d
Show file tree
Hide file tree
Showing 33 changed files with 1,442 additions and 1,223 deletions.
1 change: 0 additions & 1 deletion src/components/AttachmentModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,6 @@ function AttachmentModal(props) {
report={props.report}
onNavigate={onNavigate}
source={props.source}
onClose={closeModal}
onToggleKeyboard={updateConfirmButtonVisibility}
setDownloadButtonVisibility={setDownloadButtonVisibility}
/>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type {ForwardedRef} from 'react';
import {createContext} from 'react';
import type PagerView from 'react-native-pager-view';
import type {SharedValue} from 'react-native-reanimated';

type AttachmentCarouselPagerContextValue = {
pagerRef: ForwardedRef<PagerView>;
isPagerScrolling: SharedValue<boolean>;
isScrollEnabled: SharedValue<boolean>;
onTap: () => void;
onScaleChanged: (scale: number) => void;
};

const AttachmentCarouselPagerContext = createContext<AttachmentCarouselPagerContextValue | null>(null);

export default AttachmentCarouselPagerContext;
export type {AttachmentCarouselPagerContextValue};
172 changes: 0 additions & 172 deletions src/components/Attachments/AttachmentCarousel/Pager/index.js

This file was deleted.

150 changes: 150 additions & 0 deletions src/components/Attachments/AttachmentCarousel/Pager/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import type {ForwardedRef} from 'react';
import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import {View} from 'react-native';
import type {NativeViewGestureHandlerProps} from 'react-native-gesture-handler';
import {createNativeWrapper} from 'react-native-gesture-handler';
import type {PagerViewProps} from 'react-native-pager-view';
import PagerView from 'react-native-pager-view';
import Animated, {useAnimatedProps, useSharedValue} from 'react-native-reanimated';
import useThemeStyles from '@hooks/useThemeStyles';
import AttachmentCarouselPagerContext from './AttachmentCarouselPagerContext';
import usePageScrollHandler from './usePageScrollHandler';

const WrappedPagerView = createNativeWrapper(PagerView) as React.ForwardRefExoticComponent<
PagerViewProps & NativeViewGestureHandlerProps & React.RefAttributes<React.Component<PagerViewProps>>
>;
const AnimatedPagerView = Animated.createAnimatedComponent(WrappedPagerView);

type AttachmentCarouselPagerHandle = {
setPage: (selectedPage: number) => void;
};

type PagerItem = {
key: string;
url: string;
source: string;
};

type AttachmentCarouselPagerProps = {
items: PagerItem[];
renderItem: (props: {item: PagerItem; index: number; isActive: boolean}) => React.ReactNode;
initialIndex: number;
onPageSelected: () => void;
onRequestToggleArrows: (showArrows?: boolean) => void;
};

function AttachmentCarouselPager({items, renderItem, initialIndex, onPageSelected, onRequestToggleArrows}: AttachmentCarouselPagerProps, ref: ForwardedRef<AttachmentCarouselPagerHandle>) {
const styles = useThemeStyles();
const pagerRef = useRef<PagerView>(null);

const scale = useRef(1);
const isPagerScrolling = useSharedValue(false);
const isScrollEnabled = useSharedValue(true);

const activePage = useSharedValue(initialIndex);
const [activePageState, setActivePageState] = useState(initialIndex);

const pageScrollHandler = usePageScrollHandler((e) => {
'worklet';

activePage.value = e.position;
isPagerScrolling.value = e.offset !== 0;
}, []);

useEffect(() => {
setActivePageState(initialIndex);
activePage.value = initialIndex;
}, [activePage, initialIndex]);

/**
* This callback is passed to the MultiGestureCanvas/Lightbox through the AttachmentCarouselPagerContext.
* It is used to react to zooming/pinching and (mostly) enabling/disabling scrolling on the pager,
* as well as enabling/disabling the carousel buttons.
*/
const handleScaleChange = useCallback(
(newScale: number) => {
if (newScale === scale.current) {
return;
}

scale.current = newScale;

const newIsScrollEnabled = newScale === 1;
if (isScrollEnabled.value === newIsScrollEnabled) {
return;
}

isScrollEnabled.value = newIsScrollEnabled;
onRequestToggleArrows(newIsScrollEnabled);
},
[isScrollEnabled, onRequestToggleArrows],
);

/**
* This callback is passed to the MultiGestureCanvas/Lightbox through the AttachmentCarouselPagerContext.
* It is used to trigger touch events on the pager when the user taps on the MultiGestureCanvas/Lightbox.
*/
const handleTap = useCallback(() => {
if (!isScrollEnabled.value) {
return;
}

onRequestToggleArrows();
}, [isScrollEnabled.value, onRequestToggleArrows]);

const contextValue = useMemo(
() => ({
pagerRef,
isPagerScrolling,
isScrollEnabled,
onTap: handleTap,
onScaleChanged: handleScaleChange,
}),
[isPagerScrolling, isScrollEnabled, handleTap, handleScaleChange],
);

const animatedProps = useAnimatedProps(() => ({
scrollEnabled: isScrollEnabled.value,
}));

/**
* This "useImperativeHandle" call is needed to expose certain imperative methods via the pager's ref.
* setPage: can be used to programmatically change the page from a parent component
*/
useImperativeHandle<AttachmentCarouselPagerHandle, AttachmentCarouselPagerHandle>(
ref,
() => ({
setPage: (selectedPage) => {
pagerRef.current?.setPage(selectedPage);
},
}),
[],
);

return (
<AttachmentCarouselPagerContext.Provider value={contextValue}>
<AnimatedPagerView
pageMargin={40}
offscreenPageLimit={1}
onPageScroll={pageScrollHandler}
onPageSelected={onPageSelected}
ref={pagerRef}
style={styles.flex1}
initialPage={initialIndex}
animatedProps={animatedProps}
>
{items.map((item, index) => (
<View
key={item.source}
style={styles.flex1}
>
{renderItem({item, index, isActive: index === activePageState})}
</View>
))}
</AnimatedPagerView>
</AttachmentCarouselPagerContext.Provider>
);
}
AttachmentCarouselPager.displayName = 'AttachmentCarouselPager';

export default React.forwardRef(AttachmentCarouselPager);
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type {PagerViewProps} from 'react-native-pager-view';
import {useEvent, useHandler} from 'react-native-reanimated';

type PageScrollHandler = NonNullable<PagerViewProps['onPageScroll']>;

type PageScrollEventData = Parameters<PageScrollHandler>[0]['nativeEvent'];
type PageScrollContext = Record<string, unknown>;

// Reanimated doesn't expose the type for animated event handlers, therefore we must infer it from the useHandler hook.
// The AnimatedPageScrollHandler type is the type of the onPageScroll prop from react-native-pager-view as an animated handler.
type AnimatedHandlers = Parameters<typeof useHandler<PageScrollEventData, PageScrollContext>>[0];
type AnimatedPageScrollHandler = AnimatedHandlers[string];

type Dependencies = Parameters<typeof useHandler>[1];

/**
* This hook is used to create a wrapped handler for the onPageScroll event from react-native-pager-view.
* The produced handler can react to the onPageScroll event and allows to use it with animated shared values (from REA)
* This hook is a wrapper around the useHandler and useEvent hooks from react-native-reanimated.
* @param onPageScroll The handler for the onPageScroll event from react-native-pager-view
* @param dependencies The dependencies for the useHandler hook
* @returns A wrapped/animated handler for the onPageScroll event from react-native-pager-view
*/
const usePageScrollHandler = (onPageScroll: AnimatedPageScrollHandler, dependencies: Dependencies): PageScrollHandler => {
const {context, doDependenciesDiffer} = useHandler({onPageScroll}, dependencies);
const subscribeForEvents = ['onPageScroll'];

return useEvent(
(event) => {
'worklet';

if (onPageScroll && event.eventName.endsWith('onPageScroll')) {
onPageScroll(event, context);
}
},
subscribeForEvents,
doDependenciesDiffer,
);
};

export default usePageScrollHandler;
Loading

0 comments on commit 523656d

Please sign in to comment.