UNPKG

@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

349 lines (279 loc) 10.1 kB
/* * 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, useContext, useRef} from 'react'; import {View, Text, Dimensions, Platform, StyleSheet, PanResponder, TouchableWithoutFeedback} from 'react-native' import {WebView} from 'react-native-webview'; import { useDispatch, connect } from 'react-redux'; import constants from '../../config/constants'; import { setCurrentHotspot } from '../../redux/actions/currentHotspotAction'; import { triggerHapticFeedback } from '../../utils/device'; import { setImmersive } from '../../utils/device'; import { setWidgetsVisible } from '../../redux/actions/currentViewAction'; import { setCurrentMessageOff, setCurrentMessageOn } from '../../redux/actions/messageHintAction'; import DragToRotateViewMessage from '../common/overlay/DragToRotateViewMessage'; const Interior360 = ({ interiorData, currentHotspotState, hotspotMode, enableHotspots, widgetsVisible, currentViewState, hotspotPressed, customInteriorUrl, onInteraction, messageHint, orientation, customFontFamily, interior360InitialPosition }) => { const injectedJs = (data) => ` window.postMessage(${JSON.stringify(data)}); true; `; let webref = useRef(null); let isZooming = false; let lastPress = 0; const dispatch = useDispatch(); useEffect(()=>{ // initializeWebview() }, []) useEffect(()=>{ if(webref && webref.current){ webref.current.injectJavaScript(injectedJs({"currentView":currentViewState.type})); } }, [currentViewState]) useEffect(()=>{ if(currentViewState.type === constants.INTERIOR_VIEW){ displayDragToRotateMessage() } }, [currentViewState]) const displayDragToRotateMessage = () => { if(!messageHint[constants.MESSAGES.INTERIOR_ROTATE].show && !messageHint[constants.MESSAGES.INTERIOR_ROTATE].never){ dispatch(setCurrentMessageOn(constants.MESSAGES.INTERIOR_ROTATE)); } }; const messageHandler = (e) => { if(e && e.nativeEvent.data){ try{ let message = JSON.parse(e.nativeEvent.data) if(message && message.hasOwnProperty("error")){ console.log(message.error) if(interiorData.hasOwnProperty("imageSourceCompressed")){ //if image fails to load on device, it is most likely due to a device limitation // pass compressed image to interior viewer incase this happens interiorData.imageSource = interiorData.imageSourceCompressed webref.current.injectJavaScript(injectedJs({"data":interiorData})); } } // if(message){ // console.log('message') // console.log(message) // } if(message && message.hasOwnProperty("panellumAction")){ if(message.panellumAction === constants.INTERACTION_TYPES.DOUBLE_TAP){ if(onInteraction){ onInteraction({ currentView: constants.INTERIOR_VIEW, interaction: { type: constants.INTERACTION_TYPES.DOUBLE_TAP, }, }); } if(webref && webref.current){ webref.current.injectJavaScript(injectedJs({"hotspotMode":"show_all_hotspots"})); } dispatch(setCurrentMessageOff(constants.MESSAGES.INTERIOR_ROTATE, true)) } if(message.panellumAction === constants.INTERACTION_TYPES.TAP || message.panellumAction === "click"){ dispatch(setWidgetsVisible(true)) } } if(message && message.hasOwnProperty("click")){ if(hotspotPressed){ hotspotPressed({ currentView: constants.INTERIOR_VIEW, hotspot:{ type: message.hotspot.hotspotType, title: message.hotspot.imageTitle, description: message.hotspot.description } }) } dispatch(setCurrentHotspot({...message.hotspot, "address":message.hotspot.address})) triggerHapticFeedback() } } catch(err){ console.log(err) console.log(e) } } } const styles = StyleSheet.create({ container:{ position: 'absolute', top:0, left:0, height: "100%", width: "100%", } }) // useEffect(()=>{ // if(webref && webref.current){ // webref.current.injectJavaScript(injectedJs({"hotspotMode":hotspotMode})); // } // }, [hotspotMode]) useEffect(()=>{ if(webref && webref.current){ webref.current.injectJavaScript(injectedJs({"currentHotspot":currentHotspotState})); } }, [currentHotspotState]) const processPinch = () => { if(!isZooming){ if(webref && webref.current){ webref.current.injectJavaScript(injectedJs({"hotspotMode":"show_all_hotspots"})); } dispatch(setCurrentMessageOff(constants.MESSAGES.INTERIOR_ROTATE, true)) dispatch(setWidgetsVisible(false)) if(onInteraction){ onInteraction({ currentView: constants.INTERIOR_VIEW, interaction: { type: constants.INTERACTION_TYPES.ZOOM_START, }, }); } isZooming = true } } 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) => { let touches = evt.nativeEvent.touches; if (touches.length == 1) { if(webref && webref.current){ webref.current.injectJavaScript(injectedJs({"hotspotMode":"show_all_hotspots"})); } dispatch(setCurrentMessageOff(constants.MESSAGES.INTERIOR_ROTATE, true)) if(onInteraction){ onInteraction({ currentView: constants.INTERIOR_VIEW, interaction: { type: constants.INTERACTION_TYPES.DRAG_START, }, }); } } }, onPanResponderMove: (evt, gestureState) => { // if(webref && webref.current){ // webref.current.injectJavaScript(injectedJs({"gesture":"move"})); // } let touches = evt.nativeEvent.touches; if (touches.length == 2) { processPinch(); } }, onPanResponderRelease: (evt, gestureState) => { if (isZooming) { if(onInteraction){ onInteraction({ currentView: constants.INTERIOR_VIEW, interaction: { type: constants.INTERACTION_TYPES.ZOOM_END, }, }); } isZooming = false; } else{ // if(webref && webref.current){ // webref.current.injectJavaScript(injectedJs({"gesture":"moveEnd"})); // } if(onInteraction){ onInteraction({ currentView: constants.INTERIOR_VIEW, interaction: { type: constants.INTERACTION_TYPES.DRAG_END, }, }); } } }, }), ).current; return ( interiorData && Object.keys(interiorData).length > 0? <View style={[styles.container, { opacity: currentViewState.type === constants.INTERIOR_VIEW?1:0, zIndex: currentViewState.type === constants.INTERIOR_VIEW?0: -10 }]} > <WebView {...panResponder.panHandlers} source={{uri: customInteriorUrl}} cacheEnabled={false} ref={r=>webref.current = r} // source={{uri: 'http://192.168.1.26:3000'}} onMessage={e => { messageHandler(e) }} onLoadEnd={()=>{ if(!enableHotspots){ interiorData.filteredHotspotMetadata = [] } if(webref && webref.current){ webref.current.injectJavaScript(injectedJs({"hotspotMode":"hide_hotspots"})); if(constants.INTERIOR_VIEWER_INITIAL_POSITIONS.includes(interior360InitialPosition)){ webref.current.injectJavaScript(injectedJs({"initialPosition": interior360InitialPosition})) } webref.current.injectJavaScript(injectedJs({"currentView":currentViewState.type})); setTimeout(()=>{ if(webref && webref.current){ webref.current.injectJavaScript(injectedJs({"data":interiorData})); } }, 100) } }} automaticallyAdjustContentInsets={false} scrollEnabled={false} javaScriptEnabled={true} allowFileAccess={true} allowFileAccessFromFileURLs={true} allowUniversalAccessFromFileURLs={true} originWhitelist={["*"]} // pullToRefreshEnabled={true} // injectedJavaScriptBeforeContentLoaded={srcBase64} /> {/* { messageHint[constants.MESSAGES.INTERIOR_ROTATE].show && <DragToRotateViewMessage customFontFamily={customFontFamily}/> } */} </View> : <Text style={{color: "#000"}}>Image not found. Please capture image</Text> ); }; const mapStateToProps = ({ currentHotspot, hotspotMode, currentView, messageHint }) => { return { currentHotspotState: currentHotspot, hotspotMode: hotspotMode, widgetsVisible: currentView.widgetsVisible, currentViewState: currentView.present, orientation: currentView.orientation, messageHint: messageHint }; }; export default connect(mapStateToProps)(Interior360);