@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
292 lines (247 loc) • 9.17 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, useState, useRef} from 'react';
import {
View,
Text,
Dimensions,
LogBox,
StyleSheet,
TouchableWithoutFeedback,
AppState
} from 'react-native';
import Interior360 from './interior/Interior360';
import Exterior360 from './exterior/Exterior360';
import BottomToolbar from './widgets/BottomToolbar';
import {connect} from 'react-redux';
import {
setCurrentOrientation,
setWidgetsVisible,
} from '../redux/actions/currentViewAction';
import {useDispatch} from 'react-redux';
import constants from '../config/constants';
import HotspotOverlay from './hotspot/HotspotOverlay';
import OverlayButtons from './widgets/OverlayButtons';
import {preProcess} from '../utils/propsPreprocessing';
import {Slide} from '@s20.ai/safecam-components-rn';
import MissingDataHandlerOverlay from './common/overlay/MissingDataHandlerOverlay';
import AnimationOverlay from './common/overlay/AnimationOverlay';
import {
setCurrentMessageOff,
setCurrentMessageOn,
} from '../redux/actions/messageHintAction';
import Orientation from 'react-native-orientation-locker';
import appStyleConfig from "../components/appStyleConfig";
import { getOrientation } from '../utils/device';
import WidgetOverlay from './widgets/WidgetOverlay';
import { SafeAreaProvider } from 'react-native-safe-area-context';
LogBox.ignoreLogs(['new NativeEventEmitter']);
const window = Dimensions.get('window');
const width = window.width;
const height = window.height;
const MainWrapper = ({
currentViewState,
interiorData,
exteriorData,
currentHotspotState,
widgetsVisible,
orientationState,
enableHotspots,
enableGalleryButton,
enableHotspotTooltip,
galleryButtonPressed,
enableBackButton,
backButtonPressed,
enableCarAutorotateOnCardSwipe,
enableCardSwipeOnCarRotation,
bottomToolbarButtonPressed,
bottomToolbarSwitchToggled,
hotspotPressed,
orientationChanged,
onInteraction,
customInteriorUrl,
messageHint,
customFontFamily,
exterior360ResizeMode,
interior360InitialPosition,
customGalleryButton,
customBackButton
}) => {
const [
exteriorDataProcessed,
interiorDataProcessed,
combinedHotspotMetadata,
] = preProcess(exteriorData, interiorData);
const dispatch = useDispatch();
const appState = useRef(AppState.currentState);
const handleOrientationChangeAll = async (orientation) => {
if(orientation && orientation !== constants.UNKNOWN && orientation !== constants.PORTRAIT_UPSIDE_DOWN && orientation !== constants.FACE_UP){
dispatch(setCurrentOrientation(orientation))
}
if(orientationChanged){
//when actual orientation is FACE_UP, android returns UNKNOWN
if(orientation === constants.UNKNOWN){
orientationChanged(constants.FACE_UP)
}
else{
orientationChanged(orientation)
}
}
};
const handleAppStateChange = (nextAppState) => {
if(appState.current === "background" && nextAppState === "active"){
//required incase someone puts the viewer in the background on landscape mode and then comes back to the viewer
//OR if the component is not unmounted but the app is in the background
//failure to do this would result in the orientation being portrait
setTimeout(()=>{
Orientation.unlockAllOrientations()
}, 200)
}
if (
appState.current.match(/inactive|background/) &&
nextAppState === "active"
) {
// console.log("App has come to the foreground!");
}
appState.current = nextAppState;
}
useEffect(() => {
//Required to get initial orientation - listener won't be fired if the view loads in landscape by default
Orientation.getDeviceOrientation((orientation)=>{
if(orientation && (orientation.includes(constants.PORTRAIT) || orientation.includes(constants.LANDSCAPE))){
dispatch(setCurrentOrientation(orientation))
if(orientationChanged){
orientationChanged(orientation)
}
}
else{
//TODO - extra work may be required
//Fallback incase no orientation or Orientation = UNKNOWN is returned
if(Orientation.getInitialOrientation()){
dispatch(setCurrentOrientation(Orientation.getInitialOrientation()))
if(orientationChanged){
orientationChanged(Orientation.getInitialOrientation())
}
}
else{
dispatch(setCurrentOrientation(getOrientation()))
if(orientationChanged){
orientationChanged(getOrientation())
}
}
}
})
Orientation.unlockAllOrientations();
Orientation.addDeviceOrientationListener(
handleOrientationChangeAll
);
const appStateSubscription = AppState.addEventListener("change", handleAppStateChange);
return () => {
Orientation.removeAllListeners()
appStateSubscription.remove()
}
}, []);
useEffect(() => {
if (currentHotspotState) {
dispatch(setWidgetsVisible(false));
} else {
dispatch(setWidgetsVisible(true));
}
}, [currentHotspotState]);
useEffect(() => {
if (currentViewState.type === constants.INTERIOR_VIEW) {
if(!messageHint[constants.MESSAGES.EXTERIOR_ROTATE].never){
dispatch(setCurrentMessageOff(constants.MESSAGES.EXTERIOR_ROTATE, false));
}
}
if (currentViewState.type === constants.EXTERIOR_VIEW) {
if(!messageHint[constants.MESSAGES.INTERIOR_ROTATE].never){
dispatch(setCurrentMessageOff(constants.MESSAGES.INTERIOR_ROTATE, false));
}
}
}, [currentViewState]);
return (
<SafeAreaProvider>
<View style={appStyleConfig.mainWrapperContainer}>
{
Object.keys(exteriorDataProcessed).length > 0 && (
<Exterior360
customFontFamily={customFontFamily}
width={width}
height={height / 2}
srcset={exteriorDataProcessed.imageSource}
srcsetCompressed={exteriorDataProcessed.lowResImages}
highResImages={exteriorDataProcessed.highResImages}
filteredHotspotMetadata={
exteriorDataProcessed.filteredHotspotMetadata
}
hotspots={exteriorDataProcessed.hotspots}
enableHotspots={enableHotspots}
enableHotspotTooltip={enableHotspotTooltip}
enableCarAutorotateOnCardSwipe={enableCarAutorotateOnCardSwipe}
enableCardSwipeOnCarRotation={enableCardSwipeOnCarRotation}
hotspotPressed={hotspotPressed}
onInteraction={onInteraction}
exterior360ResizeMode={exterior360ResizeMode}
/>
)}
{
Object.keys(interiorDataProcessed).length > 0 && (
<Interior360
interiorData={interiorDataProcessed}
enableHotspots={enableHotspots}
hotspotPressed={hotspotPressed}
onInteraction={onInteraction}
customInteriorUrl={customInteriorUrl}
customFontFamily={customFontFamily}
interior360InitialPosition={interior360InitialPosition}
/>
)}
{currentHotspotState &&
Object.keys(exteriorDataProcessed).length > 0 &&
Object.keys(interiorDataProcessed).length > 0 && (
<HotspotOverlay
exteriorData={exteriorDataProcessed}
interiorData={interiorDataProcessed}
customFontFamily={customFontFamily}
/>
)}
<WidgetOverlay
galleryButtonPressed={galleryButtonPressed}
enableGalleryButton={enableGalleryButton}
enableBackButton={enableBackButton}
backButtonPressed= {backButtonPressed}
combinedHotspotMetadata={combinedHotspotMetadata}
bottomToolbarButtonPressed={bottomToolbarButtonPressed}
bottomToolbarSwitchToggled={bottomToolbarSwitchToggled}
exteriorDataProcessed={exteriorDataProcessed}
interiorDataProcessed={interiorDataProcessed}
enableHotspots={enableHotspots}
customFontFamily={customFontFamily}
customGalleryButton={ customGalleryButton}
customBackButton={customBackButton}
/>
<MissingDataHandlerOverlay
interiorDataProcessed={interiorDataProcessed}
exteriorDataProcessed={exteriorDataProcessed}
currentViewState={currentViewState}
customFontFamily={customFontFamily}
/>
<AnimationOverlay/>
</View>
</SafeAreaProvider>
);
};
const mapStateToProps = ({currentView, currentHotspot, messageHint}) => {
return {
currentViewState: currentView.present,
orientationState: currentView.orientation,
widgetsVisible: currentView.widgetsVisible,
currentHotspotState: currentHotspot,
messageHint: messageHint,
};
};
export default connect(mapStateToProps)(MainWrapper);