@s20.ai/safecam-360-rn
Version:
A React Native component to view the interior panorama and exterior 360 degree views of a product featuring suppport for hotspots. Compatible with Android and iOS
229 lines (189 loc) • 6.74 kB
JavaScript
/*
* Copyright © 2022 S20.AI India Pvt. Ltd.
* Contact - origin@s20.ai
* License - Licensed as per https://docs.s20.ai/license.html
*/
import React, {useEffect, useRef, useState} from 'react';
import {View, Dimensions, StyleSheet, LogBox, Platform} from 'react-native';
import Carousel from 'react-native-snap-carousel';
import {Card, IconButton} from '@s20.ai/safecam-components-rn';
import {connect, useDispatch} from 'react-redux';
import constants from '../../config/constants';
import HotspotDetailCard from './HotspotDetailCard';
import {carouselConfig } from '../../config/carouselConfig';
import {setCurrentHotspot} from '../../redux/actions/currentHotspotAction';
import { triggerHapticFeedback, useScreenDimensions } from '../../utils/device';
import { responsiveStyleConfig } from '../responsiveStyleConfig';
import { responsiveAppStyleConfig } from '../responsiveAppStyleConfig';
const HotspotCarousel = ({
interiorData,
exteriorData,
currentViewState,
currentHotspotState,
orientation,
customFontFamily
}) => {
const carouselRef = useRef(null);
const dispatch = useDispatch();
const [previousItemIndex, setPreviousItemIndex] = useState(null)
const {width: viewportWidth, height: viewportHeight} =
useScreenDimensions()
const getCarouselLeftOffset = () => {
if(currentViewState.type === constants.EXTERIOR_VIEW){
if(exteriorData.filteredHotspotMetadata.length === 1){
return 0
}
else{
if(orientation.includes(constants.PORTRAIT)){
if(currentHotspotState.address === 0) return 20
else return 0
}
else{
return 0
}
}
}
else{
if(interiorData.filteredHotspotMetadata.length === 1){
return 0
}
else{
if(orientation.includes(constants.PORTRAIT)){
if(currentHotspotState.address === 0) return 20
else return 0
}
else{
return 0
}
}
}
}
const getActiveSlideAlignment = () => {
if(currentViewState.type === constants.EXTERIOR_VIEW){
if(exteriorData.filteredHotspotMetadata.length === 1){
return "center"
}
else{
if(orientation.includes(constants.PORTRAIT )&& currentHotspotState.address === 0){
return "start"
}else{
return "center"
}
}
}
else{
if(interiorData.filteredHotspotMetadata.length === 1){
return "center"
}
else{
if(orientation.includes(constants.PORTRAIT )&& currentHotspotState.address === 0){
return "start"
}else{
return "center"
}
}
}
}
const styles = StyleSheet.create({
containerCustomStyle:{
marginTop: 0,
overflow: 'visible', // for custom animations
width: '100%',
height: '100%',
zIndex: 0,
left: getCarouselLeftOffset(),
},
contentContainerCustomStyle: {
paddingVertical: orientation.includes(constants.LANDSCAPE)? 0: 10, // for custom animation
paddingTop: 0,
paddingBottom: 10,
zIndex: 0
},
});
const _renderItem = ({item, index}) => {
return (
<HotspotDetailCard
item={item}
exteriorData={exteriorData}
interiorData={interiorData}
customFontFamily={customFontFamily}
/>
);
};
useEffect(() => {
//Change carousel slide based on current hotspot
if (carouselRef.current && currentHotspotState) {
carouselRef.current.triggerRenderingHack()
carouselRef.current.snapToItem(currentHotspotState.address);
}
}, [currentHotspotState]);
const screenDimensions = useScreenDimensions()
let cardStyles = responsiveStyleConfig.stylePicker('card', screenDimensions.width)
return (
<View style={{...responsiveAppStyleConfig.stylePicker("carouselContainer", viewportWidth)}}>
<Carousel
{
...{ ...carouselConfig,
sliderHeight: orientation.includes(constants.LANDSCAPE) ? viewportHeight : NaN,
vertical: orientation.includes(constants.LANDSCAPE),
// inactiveSlideOpacity: orientation.includes(constants.LANDSCAPE) ? 0.7 : 0.7
}
}
//original dimensions
// itemWidth = {300}
// itemHeight = {210}
itemWidth = {cardStyles.width}
itemHeight = {cardStyles.height}
swipeThreshold={10}
containerCustomStyle={styles.containerCustomStyle}
contentContainerCustomStyle={styles.contentContainerCustomStyle}
activeSlideAlignment={getActiveSlideAlignment()}
inactiveSlideScale={0.90}
inactiveSlideOpacity={1}
sliderWidth={viewportWidth}
lockScrollWhileSnapping={true}
ref={ref => {
carouselRef.current = ref;
}}
data={
currentViewState.type === constants.EXTERIOR_VIEW
? exteriorData.filteredHotspotMetadata
: interiorData.filteredHotspotMetadata
}
renderItem={_renderItem}
onSnapToItem={(index,) => {
//If previous item index is null and index is 0, it would mean that a hotspot other than the first
//one has been pressed from the main screen
//if no previous item index is maintained, the below code would execute for index = 0 and the actual index that needs to be set
//the effect of this would be that the hotspot will always load on the first hotspot
if(previousItemIndex !== null || index !== 0){
if (currentViewState.type === constants.EXTERIOR_VIEW) {
dispatch(
setCurrentHotspot({
...exteriorData.filteredHotspotMetadata[index],
address: exteriorData.filteredHotspotMetadata[index].address,
}),
);
} else {
dispatch(
setCurrentHotspot({
...interiorData.filteredHotspotMetadata[index],
address: interiorData.filteredHotspotMetadata[index].address,
}),
);
}
setPreviousItemIndex(index - 1)
}
}}
/>
</View>
);
};
const mapStateToProps = ({currentView, currentHotspot}) => {
return {
currentViewState: currentView.present,
orientation: currentView.orientation,
currentHotspotState: currentHotspot,
};
};
export default connect(mapStateToProps)(HotspotCarousel);