@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
887 lines (835 loc) • 27.4 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,
Text,
StyleSheet,
Dimensions,
TouchableHighlight,
PanResponder,
LogBox,
Image,
SafeAreaView,
StatusBar,
} from 'react-native';
import FastImage from 'react-native-fast-image';
import HotspotElement from '../hotspot/HotspotElement';
import {connect} from 'react-redux';
import constants from '../../config/constants';
import {useDispatch} from 'react-redux';
import {setCurrentOrientation} from '../../redux/actions/currentViewAction';
import {setWidgetsVisible} from '../../redux/actions/currentViewAction';
import {
setCurrentMessageOff,
setCurrentMessageOn,
} from '../../redux/actions/messageHintAction';
import Animated from 'react-native-reanimated';
import ProgressBar from '../common/ProgressBar';
import {setCurrentHotspot} from '../../redux/actions/currentHotspotAction';
import Orientation from 'react-native-orientation-locker';
import DragToRotateViewMessage from '../common/overlay/DragToRotateViewMessage';
import {getAspectRatio, useScreenDimensions} from '../../utils/device';
import {responsiveAppStyleConfig} from '../responsiveAppStyleConfig';
import ProgressiveImage from '../common/ProgressiveImage';
const screen = Dimensions.get('window');
let screenWidth = screen.width;
let screenHeight = screen.height;
LogBox.ignoreLogs(['Warning: ...']);
const AnimatedFastImage = Animated.createAnimatedComponent(FastImage);
const clearMyInterval = interval => {
clearInterval(interval);
};
const calcDistance = (x1, y1, x2, y2) => {
let dx = Math.abs(x1 - x2);
let dy = Math.abs(y1 - y2);
return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
};
const calcCenter = (x1, y1, x2, y2) => {
const middle = (p1, p2) => {
return p1 > p2 ? p1 - (p1 - p2) / 2 : p2 - (p2 - p1) / 2;
};
return {
x: middle(x1, x2),
y: middle(y1, y2),
};
};
const maxOffset = (offset, windowDimension, imageDimension) => {
let max = windowDimension - imageDimension;
if (max >= 0) {
return 0;
}
return offset < max ? max : offset;
};
const calcOffsetByZoom = (width, height, imageWidth, imageHeight, zoom) => {
let xDiff = imageWidth * zoom - width;
let yDiff = imageHeight * zoom - height;
return {
left: -xDiff / 2,
top: -yDiff / 2,
};
};
let startX = 0;
let startRotation = 0;
let rotation = 0;
let myInterval;
let _currentIndex = 0;
let isZooming = false;
let initialDistance = null;
let initialTop = 0;
let initialLeft = 0;
let initialX = null;
let initialY = null;
let initialTopWithoutZoom = 0;
let initialLeftWithoutZoom = 0;
let initialZoom = 1;
let minZoom = null;
let maxZoom = 8;
let initialPan = {x: 0, y: 0};
let _zoom = 1;
let _isDragging = false;
let _isLoading = true;
let _panCoords = {x: 0, y: 0};
let lastPress = 0;
let _currentHotspotState = null;
let reverseAutoRotateTimer;
let _orientation = constants.PORTRAIT;
let _noOfImagesLoaded = 0;
const Exterior360 = ({
// height = 300,
highResImages = [],
srcset = [],
srcsetCompressed = [],
hotspots = [],
rotationRatio = 0.25,
currentHotspotState,
filteredHotspotMetadata,
currentViewState,
hotspotMode,
orientation,
enableHotspots,
messageHint,
enableHotspotTooltip,
enableCarAutorotateOnCardSwipe,
enableCardSwipeOnCarRotation,
hotspotPressed,
onInteraction,
customFontFamily,
exterior360ResizeMode,
}) => {
const getImageWidth = () => {
return orientation.includes(constants.LANDSCAPE)
? currentHotspotState
? Dimensions.get('window').width / 1.65
: Dimensions.get('window').width
: Dimensions.get('window').width;
};
const getImageHeight = () => {
return orientation.includes(constants.LANDSCAPE)
? Dimensions.get('window').height
: Dimensions.get('window').width / 1.8;
};
const dispatch = useDispatch();
// let imageWidth =
// orientation.includes(constants.LANDSCAPE)'50%
// ? currentHotspotState
// ? Dimensions.get('window').width / 1.65
// : Dimensions.get('window').width
// : Dimensions.get('window').width;
// let imageHeight =
// orientation.includes(constants.LANDSCAPE)
// ? Dimensions.get('window').height
// : Dimensions.get('window').width / 1.9;
let imageWidth = getImageWidth();
let imageHeight = getImageHeight();
const [isLoading, setIsLoading] = useState(true);
const [noOfImagesLoaded, setNoOfImagesLoaded] = useState(0);
const rotationPeriod = 360 / srcset.length;
//Expression used to set the current frame index to the 45 degree left front view
//TODO - the index should be fetched from the prop data directly
//Below index calculation should just be a fallback
const [currentIndex, setCurrentIndex] = useState(
Math.round(45 / (360 / srcset.length)),
);
const [width, setwidth] = useState(0);
const [height, setheight] = useState(0);
const [zoom, setzoom] = useState(1);
const [layoutKnown, setlayoutKnown] = useState(false);
const [offsetTop, setoffsetTop] = useState(0);
const [panCoords, setPanCoords] = useState({x: 0, y: 0});
const [top, settop] = useState(0);
const [left, setleft] = useState(0);
const [highResMode, setHighResMode] = useState(false);
const [highResLoaded, setHighResLoaded] = useState(false);
const frameSkipAutoRotate = 3;
_orientation = orientation;
const [hotspotsVisible, setHotspotsVisible] = useState(
zoom <= 1 &&
enableHotspots &&
hotspotMode === constants.SHOW_ALL_HOTSPOTS &&
!isLoading,
);
useEffect(() => {
setHotspotsVisible(
zoom <= 1 &&
enableHotspots &&
hotspotMode === constants.SHOW_ALL_HOTSPOTS &&
!isLoading,
);
}, [hotspotMode]);
useEffect(() => {
if (zoom > 1) setHighResMode(true);
else setHighResMode(false);
if (zoom > 1 || _zoom > 1) {
if (orientation.includes(constants.LANDSCAPE)) {
dispatch(setWidgetsVisible(false));
} else {
let imageButtonOffsetFromBottom =
Dimensions.get('window').height * 0.08 * 0.67 + 24;
let imageButtonPositionFromTop =
Dimensions.get('window').height - imageButtonOffsetFromBottom;
if (imageHeight * zoom >= imageButtonPositionFromTop) {
dispatch(setWidgetsVisible(false));
} else {
dispatch(setWidgetsVisible(true));
}
}
} else {
dispatch(setWidgetsVisible(true));
}
}, [zoom]);
useEffect(() => {
if (_isDragging && orientation.includes(constants.LANDSCAPE)) {
dispatch(setWidgetsVisible(false));
}
}, [_isDragging]);
const userDragHandler_ReverseAutoRotate = () => {
if (_currentHotspotState) {
let hotspotToToggle = hotspots.filter(
({frameId, display}) => frameId === _currentIndex && display[2],
);
if (hotspotToToggle.length) {
let pin = hotspotToToggle[0];
window.clearTimeout(reverseAutoRotateTimer);
reverseAutoRotateTimer = setTimeout(() => {
dispatch(
setCurrentHotspot(
filteredHotspotMetadata.find(
({hotspotRefId: id}) => pin.hotspotRefId === id,
),
),
);
}, 25);
}
// displayedHotspots.filter(pin => = false )
}
};
const panResponder = React.useRef(
PanResponder.create({
onMoveShouldSetPanResponder: (evt, gestureState) => {
const {dx, dy} = gestureState;
return dx > 2 || dx < -2 || dy > 2 || dy < -2;
},
onPanResponderGrant: (evt, gestureState) => {
dispatch(
setCurrentMessageOff(constants.MESSAGES.EXTERIOR_ROTATE, true),
);
startX = gestureState.moveX;
let touches = evt.nativeEvent.touches;
if (touches.length == 1 && _zoom <= 1) {
if (onInteraction) {
onInteraction({
currentView: constants.EXTERIOR_VIEW,
interaction: {
type: constants.INTERACTION_TYPES.DRAG_START,
},
});
}
}
if (touches.length == 1 && _zoom > 1 && !isZooming) {
if (onInteraction) {
onInteraction({
currentView: constants.EXTERIOR_VIEW,
interaction: {
type: constants.INTERACTION_TYPES.PAN_START,
},
});
}
let maxPanX = (imageWidth * _zoom - imageWidth) / (2 * _zoom);
if (maxPanX >= Math.abs(_panCoords.x)) {
initialX = gestureState.dx;
initialY = gestureState.dy;
initialPan = {x: _panCoords.x, y: _panCoords.y};
}
}
// setstartX(gestureState.moveX);
startRotation = rotation;
},
onPanResponderMove: (evt, gestureState) => {
let touches = evt.nativeEvent.touches;
if (!_isLoading) {
if (touches.length == 2) {
processPinch(
touches[0].pageX,
touches[0].pageY,
touches[1].pageX,
touches[1].pageY,
);
} else if (touches.length == 1) {
if (_zoom > 1.0001 && !isZooming) {
let maxPanX =
(getImageWidth() * _zoom - Dimensions.get('window').width) /
(2 * _zoom);
let maxPanY =
(getImageHeight() * _zoom -
Dimensions.get('window').height / 1) /
(1 * _zoom);
let xVal = initialPan.x + gestureState.dx - initialX;
let yVal = initialPan.y + gestureState.dy - initialY;
_panCoords = {
x: maxPanX > Math.abs(xVal) ? xVal : _panCoords.x,
y: maxPanY > Math.abs(yVal) ? yVal : _panCoords.y,
};
setPanCoords(_panCoords);
} else if (_zoom <= 1.0001) {
initialX = 0;
initialY = 0;
_panCoords = {x: 0, y: 0};
_isDragging = true;
setPanCoords(_panCoords);
if (enableCardSwipeOnCarRotation) {
userDragHandler_ReverseAutoRotate();
}
rotateCar(gestureState);
}
}
}
},
onPanResponderRelease: (evt, gestureState) => {
if (isZooming) {
let touches = evt.nativeEvent.touches;
if (onInteraction) {
onInteraction({
currentView: constants.EXTERIOR_VIEW,
interaction: {
type: constants.INTERACTION_TYPES.ZOOM_END,
},
});
}
isZooming = false;
} else {
if (_panCoords.x == 0 && _panCoords.y == 0) {
if (!currentHotspotState) {
dispatch(setWidgetsVisible(true));
}
if (onInteraction) {
onInteraction({
currentView: constants.EXTERIOR_VIEW,
interaction: {
type: constants.INTERACTION_TYPES.DRAG_END,
},
});
}
} else {
if (onInteraction) {
onInteraction({
currentView: constants.EXTERIOR_VIEW,
interaction: {
type: constants.INTERACTION_TYPES.PAN_END,
},
});
}
}
}
_isDragging = false;
},
}),
).current;
const processPinch = (x1, y1, x2, y2) => {
let distance = calcDistance(x1, y1, x2, y2);
let center = calcCenter(x1, y1, x2, y2);
if (!isZooming) {
let offsetByZoom = calcOffsetByZoom(
width,
height,
imageWidth,
imageHeight,
_zoom,
);
if (onInteraction) {
onInteraction({
currentView: constants.EXTERIOR_VIEW,
interaction: {
type: constants.INTERACTION_TYPES.ZOOM_START,
},
});
}
isZooming = true;
initialDistance = distance;
initialX = center.x;
initialY = center.y;
initialTop = top;
initialLeft = left;
initialZoom = _zoom;
initialTopWithoutZoom = top - offsetByZoom.top;
initialLeftWithoutZoom = left - offsetByZoom.left;
} else {
let touchZoom = distance / initialDistance;
let zoomVal =
touchZoom * initialZoom > minZoom ? touchZoom * initialZoom : minZoom;
zoomVal = maxZoom >= zoomVal ? zoomVal : maxZoom;
let offsetByZoom = calcOffsetByZoom(
width,
height,
imageWidth,
imageHeight,
zoomVal,
);
let left = initialLeftWithoutZoom * touchZoom + offsetByZoom.left;
let top = initialTopWithoutZoom * touchZoom + offsetByZoom.top;
_zoom = zoomVal;
setzoom(zoomVal);
setleft(0);
settop(0);
setleft(left > 0 ? 0 : maxOffset(left, width, imageWidth * zoomVal));
settop(top > 0 ? 0 : maxOffset(top, height, imageHeight * zoomVal));
if (zoomVal <= 1) {
isZooming = false;
setPanCoords({x: 0, y: 0});
}
}
};
const _onLayout = event => {
let layout = event.nativeEvent.layout;
if (layout.width === width && layout.height === height) {
return;
}
let zoom = layout.width / getImageWidth();
let offsetTop =
layout.height > getImageHeight() * zoom
? (layout.height - getImageHeight() * zoom) / 2
: 0;
setlayoutKnown(true);
setwidth(layout.width);
setheight(layout.height);
_zoom = zoom;
setzoom(zoom.toFixed());
setoffsetTop(offsetTop);
minZoom = zoom.toFixed();
};
const rotateCar = gestureState => {
setHighResLoaded(false);
const fs = 1;
const deltaRotation =
((gestureState.moveX - startX) * 180) / (rotationRatio * screenWidth);
rotation = startRotation - deltaRotation;
const mRotation = rotation - Math.floor(rotation / 360) * 360;
const index = Math.floor(mRotation / rotationPeriod);
if (_currentIndex > index)
_currentIndex = index + fs > srcset.length - 1 ? 0 : index + fs;
else _currentIndex = index - fs < 0 ? srcset.length - 1 : index - fs;
setCurrentIndex(_currentIndex);
};
const displayAutoRotateMessage = () => {
if (
!orientation.includes(constants.LANDSCAPE) &&
!messageHint[constants.MESSAGES.AUTO_ROTATE_MESSAGE].show &&
!messageHint[constants.MESSAGES.AUTO_ROTATE_MESSAGE].never
) {
dispatch(setCurrentMessageOn(constants.MESSAGES.AUTO_ROTATE_MESSAGE));
}
};
const displayDragToRotateMessage = () => {
if (
!messageHint[constants.MESSAGES.EXTERIOR_ROTATE].show &&
!messageHint[constants.MESSAGES.EXTERIOR_ROTATE].never
) {
dispatch(setCurrentMessageOn(constants.MESSAGES.EXTERIOR_ROTATE));
}
};
useEffect(() => {
if (!orientation.includes(constants.PORTRAIT)) {
if (messageHint[constants.MESSAGES.AUTO_ROTATE_MESSAGE].show) {
dispatch(setCurrentMessageOff(constants.MESSAGES.AUTO_ROTATE_MESSAGE));
}
}
_zoom = 1;
setzoom(_zoom);
_panCoords = {x: 0, y: 0};
setPanCoords(_panCoords);
_orientation = orientation;
}, [orientation]);
const updateStartRotation = () => {
rotation = _currentIndex * rotationPeriod;
};
const autoRotateCar = (rotateTo = null) => {
if (currentHotspotState || rotateTo) {
let triggerFrame = rotateTo || currentHotspotState.triggerFrame;
myInterval = setInterval(() => {
if (_currentIndex + frameSkipAutoRotate < triggerFrame) {
if (_currentIndex + frameSkipAutoRotate < srcset.length) {
_currentIndex += frameSkipAutoRotate;
} else {
_currentIndex = 0;
}
setCurrentIndex(_currentIndex);
updateStartRotation();
} else if (_currentIndex - frameSkipAutoRotate > triggerFrame) {
if (_currentIndex - frameSkipAutoRotate >= 0) {
_currentIndex -= frameSkipAutoRotate;
} else {
_currentIndex = srcset.length - 1;
}
setCurrentIndex(_currentIndex);
updateStartRotation();
} else {
_currentIndex = triggerFrame;
setCurrentIndex(_currentIndex);
updateStartRotation();
clearMyInterval(myInterval);
}
}, 1);
}
};
useEffect(() => {
if (enableCarAutorotateOnCardSwipe) {
if (!_isDragging) autoRotateCar();
_currentHotspotState = currentHotspotState;
}
}, [currentHotspotState]);
const renderHotspots = () => {
return hotspots
.filter(
d => d.frameId === srcset.findIndex(d => d === srcset[currentIndex]),
)
.map(hotspot => {
return (
<HotspotElement
customFontFamily={customFontFamily}
key={hotspot.id + Math.random()}
address={
filteredHotspotMetadata.find(
hp => hp.hotspotRefId === hotspot.hotspotRefId,
).address
}
hotspot={hotspot}
hotspotPressed={hotspotPressed}
hotspotLocation={
filteredHotspotMetadata.find(
hp => hp.hotspotRefId === hotspot.hotspotRefId,
).type
}
hotspotType={
filteredHotspotMetadata.find(
hp => hp.hotspotRefId === hotspot.hotspotRefId,
).hotspotType
}
enableHotspotTooltip={enableHotspotTooltip}
currentHotspotState={currentHotspotState}
width={imageWidth}
height={imageHeight}
orientationState={orientation}
/>
);
});
};
useEffect(() => {
if (messageHint[constants.MESSAGES.EXTERIOR_ROTATE].show) {
setHotspotsVisible(false);
} else {
setHotspotsVisible(
zoom <= 1 &&
enableHotspots &&
hotspotMode === constants.SHOW_ALL_HOTSPOTS &&
!isLoading,
);
}
}, [messageHint]);
useEffect(() => {
if (
srcset.length === noOfImagesLoaded &&
currentViewState.type === constants.EXTERIOR_VIEW
) {
setIsLoading(false);
displayDragToRotateMessage();
_isLoading = false;
}
}, [noOfImagesLoaded, currentViewState]);
useEffect(() => {
displayAutoRotateMessage();
}, []);
const renderImage = (link, linkIndex) => {
return (
<>
<FastImage
onLoadEnd={_ => {
_noOfImagesLoaded += 1;
setNoOfImagesLoaded(_noOfImagesLoaded);
}}
key={link}
fadeDuration={0}
resizeMode={
exterior360ResizeMode &&
[
FastImage.resizeMode.center,
FastImage.resizeMode.contain,
FastImage.resizeMode.cover,
FastImage.resizeMode.stretch,
].includes(exterior360ResizeMode)
? exterior360ResizeMode
: FastImage.resizeMode.cover
}
source={{
uri: link,
}}
style={{
// position: 'absolute',
width: imageWidth,
height: imageHeight,
transform: [
{
scale: highResLoaded && zoom > 4 ? 4 : parseFloat(zoom),
},
{translateX: panCoords.x},
{translateY: panCoords.y},
],
opacity:
currentIndex && !isNaN(currentIndex)
? srcset[currentIndex] === link
? 1
: 0
: 1,
zIndex: zoom < 3 && !highResLoaded ? 1 : -1,
}}></FastImage>
{/* <ProgressiveImage
noOfImagesLoaded={noOfImagesLoaded}
setNoOfImagesLoaded={(val)=>{
setNoOfImagesLoaded(val)
}}
isLoading={isLoading}
key={link}
fadeDuration={0}
thumbnailSource={{ uri: srcsetCompressed[linkIndex]}}
resizeMode={exterior360ResizeMode && [FastImage.resizeMode.center, FastImage.resizeMode.contain, FastImage.resizeMode.cover, FastImage.resizeMode.stretch].includes(exterior360ResizeMode) ? exterior360ResizeMode : FastImage.resizeMode.cover}
source={{
uri: link,
}}
style={{
// position: 'absolute',
width: imageWidth,
height: imageHeight,
transform: [
{
scale: highResLoaded && zoom > 4 ? 4 : parseFloat(zoom),
},
{translateX: panCoords.x},
{translateY: panCoords.y},
],
opacity:
currentIndex && !isNaN(currentIndex)
? srcset[currentIndex] === link
? 1
: 0
: 1,
zIndex: zoom < 3 && !highResLoaded ? 1 : -1,
}}></ProgressiveImage> */}
{zoom > 3 && (
/* <FastImage
key={highResImages[currentIndex]}
fadeDuration={0}
onLoadStart={_ => {
setHighResLoaded(false);
}}
onLoadEnd={_ => {
setHighResLoaded(true);
}}
resizeMode={FastImage.resizeMode.contain}
source={{
uri: highResImages[currentIndex],
priority: 'high',
}}
style={{
position: 'absolute',
width: imageWidth,
height: imageHeight,
transform: [
{
scale: parseFloat(zoom),
},
{translateX: panCoords.x},
{translateY: panCoords.y},
],
zIndex: highResLoaded ? 0 : -1,
elevation: highResLoaded ? 1 : -1,
}}
/> */
/** Used to handle high quality image on adaptive zoom */
<Image
key={highResImages[currentIndex]}
fadeDuration={0}
onLoadStart={_ => {
setHighResLoaded(false);
}}
onLoadEnd={_ => {
setHighResLoaded(true);
}}
resizeMode={'contain'}
source={{
// uri: link,
uri: highResImages[currentIndex],
}}
style={{
position: 'absolute',
width: imageWidth,
height: imageHeight,
transform: [
{
scale: parseFloat(zoom),
},
{translateX: panCoords.x},
{translateY: panCoords.y},
],
zIndex: zoom < 3 && !highResLoaded ? -1 : 1,
}}
/>
)}
</>
);
};
useEffect(() => {
if (currentViewState.type === constants.EXTERIOR_VIEW) {
//random bug whereby exterior 360 image does not show when switching back and forth between views
//reason is that these values are set to NaN for some reason
//they need to be reset every time the exterior view loads so as to prevent this bug
startRotation = 0;
rotation = 0;
}
}, [currentViewState]);
const screenDimensions = useScreenDimensions();
const exteriorViewContainerStyles = responsiveAppStyleConfig.stylePicker(
'exteriorView',
screenDimensions.width,
);
const exteriorViewImageWrapperStyles = responsiveAppStyleConfig.stylePicker(
'exteriorViewImageWrapper',
screenDimensions.width,
);
return (
<View
style={[
{
...exteriorViewContainerStyles.container,
...(orientation.includes(constants.LANDSCAPE)
? {width: currentHotspotState ? '50%' : '100%'}
: currentHotspotState
? {...exteriorViewContainerStyles.containerHotspotMode}
: {...exteriorViewContainerStyles.container}),
},
{
opacity: currentViewState.type === constants.EXTERIOR_VIEW ? 1 : 0,
zIndex: currentViewState.type === constants.EXTERIOR_VIEW ? 0 : -10,
},
]}
onLayout={_onLayout}>
<View
{...panResponder.panHandlers}
style={exteriorViewImageWrapperStyles}>
{srcset.map((link, linkIndex) => (
<TouchableHighlight
key={link}
activeOpacity={0}
style={{
position: 'absolute',
// top: imageHeight * zoom,
width: imageWidth,
height: imageHeight,
//sometimes currentIndex may be NaN
//if this happens, the image will not show
//reset opacity to 1 if this happens
opacity:
currentIndex && !isNaN(currentIndex)
? srcset[currentIndex] === link
? 1
: 0
: 1,
}}
onPress={e => {
const time = new Date().getTime();
const delta = time - lastPress;
const DOUBLE_PRESS_DELAY = 400;
if (delta < DOUBLE_PRESS_DELAY) {
dispatch(
setCurrentMessageOff(
constants.MESSAGES.EXTERIOR_ROTATE,
true,
),
);
if (onInteraction) {
onInteraction({
currentView: constants.EXTERIOR_VIEW,
interaction: {
type: constants.INTERACTION_TYPES.DOUBLE_TAP,
},
});
}
let zoomVal = 1;
if (zoom <= 2) zoomVal = 4;
else {
_panCoords = {x: 0, y: 0};
initialX = 0;
initialY = 0;
setPanCoords({x: 0, y: 0});
isZooming = false;
}
_zoom = zoomVal;
setzoom(zoomVal);
dispatch(setWidgetsVisible(false));
} else {
if (currentHotspotState) {
}
dispatch(setWidgetsVisible(true));
}
lastPress = time;
}}>
{renderImage(link, linkIndex)}
</TouchableHighlight>
))}
{hotspotsVisible && zoom <= 1 && renderHotspots()}
{isLoading && (
<ProgressBar
imageHeight={imageHeight}
totalImages={srcset.length}
noOfImagesLoaded={noOfImagesLoaded}
orientation={orientation}
/>
)}
{!_isDragging &&
messageHint[constants.MESSAGES.EXTERIOR_ROTATE].show && (
<DragToRotateViewMessage
imageHeight={imageHeight}
customFontFamily={customFontFamily}
/>
)}
</View>
</View>
);
};
const mapStateToProps = ({
currentView,
currentHotspot,
hotspotMode,
messageHint,
}) => {
return {
currentViewState: currentView.present,
orientation: currentView.orientation,
widgetsVisible: currentView.widgetsVisible,
currentHotspotState: currentHotspot,
hotspotMode: hotspotMode,
messageHint: messageHint,
};
};
export default connect(mapStateToProps)(Exterior360);