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

Added Image Loading Customisation #155

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
354 changes: 189 additions & 165 deletions src/ImageViewing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,187 +6,211 @@
*
*/

import React, { ComponentType, useCallback, useRef, useEffect } from "react";
import React, {
ComponentType,
useCallback,
useRef,
useEffect,
ReactNode
} from 'react'
import {
Animated,
Dimensions,
StyleSheet,
View,
VirtualizedList,
ModalProps,
Modal,
} from "react-native";
Animated,
Dimensions,
StyleSheet,
View,
VirtualizedList,
ModalProps,
Modal
} from 'react-native'

import ImageItem from "./components/ImageItem/ImageItem";
import ImageDefaultHeader from "./components/ImageDefaultHeader";
import StatusBarManager from "./components/StatusBarManager";
import ImageItem from './components/ImageItem/ImageItem'
import ImageDefaultHeader from './components/ImageDefaultHeader'
import StatusBarManager from './components/StatusBarManager'

import useAnimatedComponents from "./hooks/useAnimatedComponents";
import useImageIndexChange from "./hooks/useImageIndexChange";
import useRequestClose from "./hooks/useRequestClose";
import { ImageSource } from "./@types";
import useAnimatedComponents from './hooks/useAnimatedComponents'
import useImageIndexChange from './hooks/useImageIndexChange'
import useRequestClose from './hooks/useRequestClose'
import { ImageSource } from './@types'

type Props = {
images: ImageSource[];
keyExtractor?: (imageSrc: ImageSource, index: number) => string;
imageIndex: number;
visible: boolean;
onRequestClose: () => void;
onLongPress?: (image: ImageSource) => void;
onImageIndexChange?: (imageIndex: number) => void;
presentationStyle?: ModalProps["presentationStyle"];
animationType?: ModalProps["animationType"];
backgroundColor?: string;
swipeToCloseEnabled?: boolean;
doubleTapToZoomEnabled?: boolean;
delayLongPress?: number;
HeaderComponent?: ComponentType<{ imageIndex: number }>;
FooterComponent?: ComponentType<{ imageIndex: number }>;
};
images: ImageSource[]
keyExtractor?: (imageSrc: ImageSource, index: number) => string
imageIndex: number
visible: boolean
onRequestClose: () => void
onLongPress?: (image: ImageSource) => void
onImageIndexChange?: (imageIndex: number) => void
presentationStyle?: ModalProps['presentationStyle']
animationType?: ModalProps['animationType']
backgroundColor?: string
swipeToCloseEnabled?: boolean
doubleTapToZoomEnabled?: boolean
delayLongPress?: number
HeaderComponent?: ComponentType<{ imageIndex: number }>
FooterComponent?: ComponentType<{ imageIndex: number }>
hideDefaultLoadingComponent?: boolean
customLoadingComponent?: ReactNode
onImageLoad?: () => void
}

const DEFAULT_ANIMATION_TYPE = "fade";
const DEFAULT_BG_COLOR = "#000";
const DEFAULT_DELAY_LONG_PRESS = 800;
const SCREEN = Dimensions.get("screen");
const SCREEN_WIDTH = SCREEN.width;
const DEFAULT_ANIMATION_TYPE = 'fade'
const DEFAULT_BG_COLOR = '#000'
const DEFAULT_DELAY_LONG_PRESS = 800
const SCREEN = Dimensions.get('screen')
const SCREEN_WIDTH = SCREEN.width

function ImageViewing({
images,
keyExtractor,
imageIndex,
visible,
onRequestClose,
onLongPress = () => {},
onImageIndexChange,
animationType = DEFAULT_ANIMATION_TYPE,
backgroundColor = DEFAULT_BG_COLOR,
presentationStyle,
swipeToCloseEnabled,
doubleTapToZoomEnabled,
delayLongPress = DEFAULT_DELAY_LONG_PRESS,
HeaderComponent,
FooterComponent,
images,
keyExtractor,
imageIndex,
visible,
onRequestClose,
onLongPress = () => {},
onImageIndexChange,
animationType = DEFAULT_ANIMATION_TYPE,
backgroundColor = DEFAULT_BG_COLOR,
presentationStyle,
swipeToCloseEnabled,
doubleTapToZoomEnabled,
delayLongPress = DEFAULT_DELAY_LONG_PRESS,
HeaderComponent,
FooterComponent,
hideDefaultLoadingComponent,
customLoadingComponent,
onImageLoad
}: Props) {
const imageList = useRef<VirtualizedList<ImageSource>>(null);
const [opacity, onRequestCloseEnhanced] = useRequestClose(onRequestClose);
const [currentImageIndex, onScroll] = useImageIndexChange(imageIndex, SCREEN);
const [headerTransform, footerTransform, toggleBarsVisible] =
useAnimatedComponents();
const imageList = useRef<VirtualizedList<ImageSource>>(null)
const [opacity, onRequestCloseEnhanced] = useRequestClose(onRequestClose)
const [currentImageIndex, onScroll] = useImageIndexChange(
imageIndex,
SCREEN
)
const [headerTransform, footerTransform, toggleBarsVisible] =
useAnimatedComponents()

useEffect(() => {
if (onImageIndexChange) {
onImageIndexChange(currentImageIndex);
}
}, [currentImageIndex]);
useEffect(() => {
if (onImageIndexChange) {
onImageIndexChange(currentImageIndex)
}
}, [currentImageIndex])

const onZoom = useCallback(
(isScaled: boolean) => {
// @ts-ignore
imageList?.current?.setNativeProps({ scrollEnabled: !isScaled });
toggleBarsVisible(!isScaled);
},
[imageList]
);
const onZoom = useCallback(
(isScaled: boolean) => {
// @ts-ignore
imageList?.current?.setNativeProps({ scrollEnabled: !isScaled })
toggleBarsVisible(!isScaled)
},
[imageList]
)

if (!visible) {
return null;
}
if (!visible) {
return null
}

return (
<Modal
transparent={presentationStyle === "overFullScreen"}
visible={visible}
presentationStyle={presentationStyle}
animationType={animationType}
onRequestClose={onRequestCloseEnhanced}
supportedOrientations={["portrait"]}
hardwareAccelerated
>
<StatusBarManager presentationStyle={presentationStyle} />
<View style={[styles.container, { opacity, backgroundColor }]}>
<Animated.View style={[styles.header, { transform: headerTransform }]}>
{typeof HeaderComponent !== "undefined" ? (
React.createElement(HeaderComponent, {
imageIndex: currentImageIndex,
})
) : (
<ImageDefaultHeader onRequestClose={onRequestCloseEnhanced} />
)}
</Animated.View>
<VirtualizedList
ref={imageList}
data={images}
horizontal
pagingEnabled
windowSize={2}
initialNumToRender={1}
maxToRenderPerBatch={1}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
initialScrollIndex={imageIndex}
getItem={(_, index) => images[index]}
getItemCount={() => images.length}
getItemLayout={(_, index) => ({
length: SCREEN_WIDTH,
offset: SCREEN_WIDTH * index,
index,
})}
renderItem={({ item: imageSrc }) => (
<ImageItem
onZoom={onZoom}
imageSrc={imageSrc}
onRequestClose={onRequestCloseEnhanced}
onLongPress={onLongPress}
delayLongPress={delayLongPress}
swipeToCloseEnabled={swipeToCloseEnabled}
doubleTapToZoomEnabled={doubleTapToZoomEnabled}
/>
)}
onMomentumScrollEnd={onScroll}
//@ts-ignore
keyExtractor={(imageSrc, index) =>
keyExtractor
? keyExtractor(imageSrc, index)
: typeof imageSrc === "number"
? `${imageSrc}`
: imageSrc.uri
}
/>
{typeof FooterComponent !== "undefined" && (
<Animated.View
style={[styles.footer, { transform: footerTransform }]}
>
{React.createElement(FooterComponent, {
imageIndex: currentImageIndex,
})}
</Animated.View>
)}
</View>
</Modal>
);
return (
<Modal
transparent={presentationStyle === 'overFullScreen'}
visible={visible}
presentationStyle={presentationStyle}
animationType={animationType}
onRequestClose={onRequestCloseEnhanced}
supportedOrientations={['portrait']}
hardwareAccelerated
>
<StatusBarManager presentationStyle={presentationStyle} />
<View style={[styles.container, { opacity, backgroundColor }]}>
<Animated.View
style={[styles.header, { transform: headerTransform }]}
>
{typeof HeaderComponent !== 'undefined' ? (
React.createElement(HeaderComponent, {
imageIndex: currentImageIndex
})
) : (
<ImageDefaultHeader
onRequestClose={onRequestCloseEnhanced}
/>
)}
</Animated.View>
<VirtualizedList
ref={imageList}
data={images}
horizontal
pagingEnabled
windowSize={2}
initialNumToRender={1}
maxToRenderPerBatch={1}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
initialScrollIndex={imageIndex}
getItem={(_, index) => images[index]}
getItemCount={() => images.length}
getItemLayout={(_, index) => ({
length: SCREEN_WIDTH,
offset: SCREEN_WIDTH * index,
index
})}
renderItem={({ item: imageSrc }) => (
<ImageItem
onZoom={onZoom}
imageSrc={imageSrc}
onRequestClose={onRequestCloseEnhanced}
onLongPress={onLongPress}
delayLongPress={delayLongPress}
swipeToCloseEnabled={swipeToCloseEnabled}
doubleTapToZoomEnabled={doubleTapToZoomEnabled}
hideDefaultLoadingComponent={
hideDefaultLoadingComponent
}
customLoadingComponent={customLoadingComponent}
onImageLoad={onImageLoad}
/>
)}
onMomentumScrollEnd={onScroll}
//@ts-ignore
keyExtractor={(imageSrc, index) =>
keyExtractor
? keyExtractor(imageSrc, index)
: typeof imageSrc === 'number'
? `${imageSrc}`
: imageSrc.uri
}
/>
{typeof FooterComponent !== 'undefined' && (
<Animated.View
style={[styles.footer, { transform: footerTransform }]}
>
{React.createElement(FooterComponent, {
imageIndex: currentImageIndex
})}
</Animated.View>
)}
</View>
</Modal>
)
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#000",
},
header: {
position: "absolute",
width: "100%",
zIndex: 1,
top: 0,
},
footer: {
position: "absolute",
width: "100%",
zIndex: 1,
bottom: 0,
},
});
container: {
flex: 1,
backgroundColor: '#000'
},
header: {
position: 'absolute',
width: '100%',
zIndex: 1,
top: 0
},
footer: {
position: 'absolute',
width: '100%',
zIndex: 1,
bottom: 0
}
})

const EnhancedImageViewing = (props: Props) => (
<ImageViewing key={props.imageIndex} {...props} />
);
<ImageViewing key={props.imageIndex} {...props} />
)

export default EnhancedImageViewing;
export default EnhancedImageViewing
Loading