mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2026-06-18 12:41:54 -04:00
Replace FlatList with Carousel
This commit is contained in:
@@ -4,15 +4,15 @@ import {
|
||||
} from "components/SharedComponents";
|
||||
import { View } from "components/styledComponents";
|
||||
import React, {
|
||||
useCallback, useMemo, useState,
|
||||
useMemo, useState,
|
||||
} from "react";
|
||||
import type { NativeScrollEvent, NativeSyntheticEvent } from "react-native";
|
||||
import { FlatList } from "react-native";
|
||||
import {
|
||||
Gesture,
|
||||
GestureDetector,
|
||||
GestureHandlerRootView,
|
||||
} from "react-native-gesture-handler";
|
||||
import type { CarouselRenderItem, ICarouselInstance } from "react-native-reanimated-carousel";
|
||||
import Carousel from "react-native-reanimated-carousel";
|
||||
import Photo from "realmModels/Photo";
|
||||
import useDeviceOrientation from "sharedHooks/useDeviceOrientation";
|
||||
import useTranslation from "sharedHooks/useTranslation";
|
||||
@@ -37,7 +37,7 @@ interface SoundItem {
|
||||
interface Props {
|
||||
autoPlaySound?: boolean; // automatically start playing a sound when it is visible
|
||||
editable?: boolean;
|
||||
horizontalScroll: React.Ref<FlatList>;
|
||||
horizontalScroll: React.Ref<ICarouselInstance>;
|
||||
onDeletePhoto: ( uri: string ) => void;
|
||||
onClose: ( ) => void;
|
||||
onDeleteSound: ( uri: string ) => void;
|
||||
@@ -63,19 +63,17 @@ const MainMediaDisplay = ( {
|
||||
const { screenWidth } = useDeviceOrientation( );
|
||||
const [displayHeight, setDisplayHeight] = useState( 0 );
|
||||
const [zooming, setZooming] = useState( false );
|
||||
const atFirstItem = selectedMediaIndex === 0;
|
||||
const items = useMemo( ( ) => ( [
|
||||
...photos.map( photo => ( { ...photo, type: "photo" as const } ) ),
|
||||
...sounds.map( sound => ( { ...sound, type: "sound" as const } ) ),
|
||||
] ), [photos, sounds] );
|
||||
const atLastItem = selectedMediaIndex === items.length - 1;
|
||||
|
||||
// t changes a lot, but these strings don't, so using them as useCallback
|
||||
// dependencies keeps that method from getting redefined a lot
|
||||
const deletePhotoLabel = t( "Delete-photo" );
|
||||
const deleteSoundLabel = t( "Delete-sound" );
|
||||
|
||||
const renderPhoto = useCallback( ( photo: PhotoItem ) => {
|
||||
const renderPhoto = ( photo: PhotoItem ) => {
|
||||
const uri = Photo.displayLocalOrRemoteLargePhoto( photo );
|
||||
const hasAttribution = photo?.attribution;
|
||||
return (
|
||||
@@ -112,14 +110,9 @@ const MainMediaDisplay = ( {
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}, [
|
||||
deletePhotoLabel,
|
||||
editable,
|
||||
onDeletePhoto,
|
||||
selectedMediaIndex,
|
||||
] );
|
||||
};
|
||||
|
||||
const renderSound = useCallback( ( sound: SoundItem ) => (
|
||||
const renderSound = ( sound: SoundItem ) => (
|
||||
<View
|
||||
className="justify-center items-center"
|
||||
style={{
|
||||
@@ -145,65 +138,20 @@ const MainMediaDisplay = ( {
|
||||
)
|
||||
}
|
||||
</View>
|
||||
), [
|
||||
autoPlaySound,
|
||||
deleteSoundLabel,
|
||||
displayHeight,
|
||||
editable,
|
||||
items,
|
||||
onDeleteSound,
|
||||
screenWidth,
|
||||
selectedMediaIndex,
|
||||
] );
|
||||
);
|
||||
|
||||
const renderItem = useCallback( ( { item }: { item: PhotoItem | SoundItem } ) => (
|
||||
const renderItem: CarouselRenderItem<PhotoItem | SoundItem> = ( { item } ) => (
|
||||
item.type === "photo"
|
||||
? renderPhoto( item )
|
||||
: renderSound( item )
|
||||
), [
|
||||
renderPhoto,
|
||||
renderSound,
|
||||
] );
|
||||
);
|
||||
|
||||
// need getItemLayout for setting initial scroll index
|
||||
const getItemLayout = useCallback( ( data, idx: number ) => ( {
|
||||
length: screenWidth,
|
||||
offset: screenWidth * idx,
|
||||
index: idx,
|
||||
} ), [screenWidth] );
|
||||
|
||||
const handleScrollLeft = useCallback( ( index: number ) => {
|
||||
if ( atFirstItem ) { return; }
|
||||
setSelectedMediaIndex( index );
|
||||
}, [atFirstItem, setSelectedMediaIndex] );
|
||||
|
||||
const handleScrollRight = useCallback( ( index: number ) => {
|
||||
if ( atLastItem ) { return; }
|
||||
setSelectedMediaIndex( index );
|
||||
}, [atLastItem, setSelectedMediaIndex] );
|
||||
|
||||
const handleScrollEndDrag = useCallback( ( e: NativeSyntheticEvent<NativeScrollEvent> ) => {
|
||||
const { contentOffset, layoutMeasurement } = e.nativeEvent;
|
||||
const { x } = contentOffset;
|
||||
|
||||
const currentOffset = screenWidth * selectedMediaIndex;
|
||||
|
||||
// https://gist.github.com/dozsolti/6d01d0f96d9abced3450a2e6149a2bc3?permalink_comment_id=4107663#gistcomment-4107663
|
||||
const index = Math.floor(
|
||||
Math.floor( x ) / Math.floor( layoutMeasurement.width ),
|
||||
);
|
||||
|
||||
if ( x > currentOffset ) {
|
||||
handleScrollRight( index );
|
||||
} else if ( x < currentOffset ) {
|
||||
handleScrollLeft( index );
|
||||
}
|
||||
}, [
|
||||
handleScrollLeft,
|
||||
handleScrollRight,
|
||||
screenWidth,
|
||||
selectedMediaIndex,
|
||||
] );
|
||||
// // need getItemLayout for setting initial scroll index
|
||||
// const getItemLayout = useCallback( ( data, idx: number ) => ( {
|
||||
// length: screenWidth,
|
||||
// offset: screenWidth * idx,
|
||||
// index: idx,
|
||||
// } ), [screenWidth] );
|
||||
|
||||
const swipeToCloseGesture = Gesture.Simultaneous(
|
||||
Gesture.Pan( )
|
||||
@@ -227,7 +175,22 @@ const MainMediaDisplay = ( {
|
||||
>
|
||||
<GestureHandlerRootView>
|
||||
<GestureDetector gesture={swipeToCloseGesture}>
|
||||
<FlatList
|
||||
<View collapsable={false}>
|
||||
<Carousel
|
||||
key={`MediaViewerCarousel-${screenWidth}`}
|
||||
testID="MediaViewer.carousel"
|
||||
ref={horizontalScroll}
|
||||
data={items}
|
||||
renderItem={renderItem}
|
||||
defaultIndex={selectedMediaIndex}
|
||||
loop={false}
|
||||
width={screenWidth}
|
||||
enabled={!zooming}
|
||||
onSnapToItem={setSelectedMediaIndex}
|
||||
// onConfigurePanGesture={onConfigurePanGesture}
|
||||
// windowSize={3}
|
||||
/>
|
||||
{/* <FlatList
|
||||
ref={horizontalScroll}
|
||||
data={items}
|
||||
renderItem={renderItem}
|
||||
@@ -239,7 +202,8 @@ const MainMediaDisplay = ( {
|
||||
scrollEnabled={!zooming}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
onMomentumScrollEnd={handleScrollEndDrag}
|
||||
/>
|
||||
/> */}
|
||||
</View>
|
||||
</GestureDetector>
|
||||
</GestureHandlerRootView>
|
||||
</View>
|
||||
|
||||
@@ -7,8 +7,8 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import type { FlatList } from "react-native";
|
||||
import { StatusBar } from "react-native";
|
||||
import type { ICarouselInstance } from "react-native-reanimated-carousel";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import Photo from "realmModels/Photo";
|
||||
import { BREAKPOINTS } from "sharedHelpers/breakpoint";
|
||||
@@ -78,7 +78,7 @@ const MediaViewer = ( {
|
||||
const { t } = useTranslation( );
|
||||
const [mediaToDelete, setMediaToDelete] = useState<MediaToDelete | null>( null );
|
||||
|
||||
const horizontalScroll = useRef<FlatList>( null );
|
||||
const horizontalScroll = useRef<ICarouselInstance>( null );
|
||||
|
||||
const { screenWidth } = useDeviceOrientation( );
|
||||
const isLargeScreen = screenWidth > BREAKPOINTS.md;
|
||||
@@ -87,7 +87,7 @@ const MediaViewer = ( {
|
||||
// when a user taps an item in the carousel, the UI needs to automatically
|
||||
// scroll to the index of the item they selected
|
||||
setSelectedMediaIndex( index );
|
||||
horizontalScroll?.current?.scrollToIndex( { index, animated: true } );
|
||||
horizontalScroll?.current?.scrollTo( { index, animated: true } );
|
||||
}, [setSelectedMediaIndex] );
|
||||
|
||||
// If we've removed an item the selectedPhoto index might refer to a item
|
||||
@@ -96,7 +96,7 @@ const MediaViewer = ( {
|
||||
if ( uris.length > 0 && selectedMediaIndex >= uris.length ) {
|
||||
const newIndex = Math.max( 0, selectedMediaIndex - 1 );
|
||||
setSelectedMediaIndex( newIndex );
|
||||
horizontalScroll?.current?.scrollToIndex( {
|
||||
horizontalScroll?.current?.scrollTo( {
|
||||
index: newIndex,
|
||||
animated: false,
|
||||
} );
|
||||
|
||||
Reference in New Issue
Block a user