Untitled
unknown
plain_text
a year ago
22 kB
1
Indexable
Never
//DEPENDENCIES import React, { useState, useEffect, useContext } from 'react'; import MapboxGL from '@rnmapbox/maps'; // DOCS https://github.com/rnmapbox/maps/tree/main/docs import BackgroundGeolocation from 'react-native-background-geolocation'; import analytics from '@react-native-firebase/analytics'; import PushNotification, {Importance} from 'react-native-push-notification'; //HELPERS import { addMarkersToStorage, addPinsToStorage, getInterestsFromStorage, } from '../../helpers/async_storage/AsyncStorage'; //INTERFACES import {IMarker, MapViewProps} from './Interfaces'; import {Feature, Point} from 'geojson'; //COMPONENTS import {View, Image, Pressable, Text, Platform} from 'react-native'; import Marker from '../marker/Marker'; import ToggleModals from '../place-marker/PlaceMarker'; import TaskList from '../task-list/TaskList'; import Button from '../button/Button'; //import auth from '@react-native-firebase/auth'; import BetaFeature_Modal from '../beta-feature-modal/BetaFeature_Modal'; import Tasks from '../tasks/Tasks'; //STYLE import style from './PositionedMapView_Style'; import CategoryInterpretor from '../category-interpretor/CategoryInterpretor'; //refresh stuff import {useSelector,useDispatch} from 'react-redux' import locations from '../../assets/location-data/Locations'; import { likedInterests,setLikedInterests } from '../../reducers/InterestsSlice'; import { userLocationCoordinates } from '../../reducers/UserLocationSlice'; MapboxGL.setAccessToken( 'sk.eyJ1IjoibmVvbmFkYSIsImEiOiJjbGU4eHVienUwMTM0M29vMmxvdmE3MDQxIn0.u9xxTJ2sL6BhK6qJfyisYw', ); const PositionedMapView = ({ debugToggle, isDebug, }: MapViewProps) => { const currentLocation= useSelector(userLocationCoordinates) // MapView states const [markers, setMarkers] = useState<Array<IMarker>>([]); const [renderedMap, setRenderedMap] = useState(true); const [isMarkersPlaced, setIsMarkersPlaced] = useState(true); const [markerMode, setMarkerMode] = useState(false); const [selectedMarker, setSelectedMarker] = useState(''); // BG Events const [geofenceEvent, setGeofenceEvent] = useState(null); const [geofencesChangeEvent, setGeofencesChangeEvent] = useState(null); // Collection of BG event subscriptions const subscriptions = []; // Modal states const [isPlaceMarkerVisible, setIsPlaceMarkerVisible] = useState(false); const [isAddDescriptionVisible, setAddDescriptionVisible] = useState(false); const [description, setDescription] = useState(''); // description se moze uraditi preko mapboxa popUp kad se klikne na marker const [isTaskListVisible, setIsTaskListVisible] = useState(false); const [isAllTasksVisible, setIsAllTasksVisible] = useState(false); // Other const [isBetaFeatureModalVisible, setIsBetaFeatureModalVisible] = useState(false); const [latitude, setLatitude] = useState(); const [longitude, setLongitude] = useState(); const [pins, setPins] = useState([]); //interest location data const interests= useSelector(likedInterests); const dispatch=useDispatch() useEffect(() => { placeMarkersOnMap(); // BG event-listeners // Listens location, we don't need this we get location from Mapbox // subscribe(BackgroundGeolocation.onLocation(setLocation, (error) => { // console.log('[onLocation] ERROR: ', error); // })); // Listens if device is moving or stationary // Listens for geofence hit ! subscribe(BackgroundGeolocation.onGeofence(setGeofenceEvent)); // With this we can show on UI when they are "on" or "off" (listens which geofences are actively monitored) subscribe(BackgroundGeolocation.onGeofencesChange(setGeofencesChangeEvent)); getInterestsFromStorage().then(result => { if (result) { dispatch(setLikedInterests(result)); } else { dispatch(setLikedInterests(locations)); } }) return () => { // Important for with live-reload to remove BackgroundGeolocation event subscriptions -> [TO TEST] unsubscribe(); }; }, []); // onGeofence effect -> useEffect(() => { if (!geofenceEvent) return; onGeofence(); }, [geofenceEvent]); /// GeofenceEvent effect-handler /// Renders geofence event markers to MapView // check if marker is added to geofences -> no need // send notification const onGeofence = () => { console.log('[onGeofence] event: ', geofenceEvent); if (geofenceEvent.action == 'ENTER') { PushNotification.localNotification({ channelId: 'com.maptodoTSLocationManager', // (required) channelId, if the channel doesn't exist, notification will not trigger. ticker: 'MapToBe', // (optional) showWhen: true, // (optional) default: true autoCancel: false, // (optional) default: true largeIcon: 'ic_launcher', // (optional) default: "ic_launcher". Use "" for no large icon. largeIconUrl: 'https://www.example.tld/picture.jpg', // (optional) default: undefined smallIcon: 'ic_notification', // (optional) default: "ic_notification" with fallback for "ic_launcher". Use "" for default small icon. bigText: 'My big text that will be shown when notification is expanded. Styling can be done using HTML tags(see android docs for details)', // (optional) default: "message" prop subText: 'This is a subText', // (optional) default: none bigPictureUrl: 'https://www.example.tld/picture.jpg', // (optional) default: undefined bigLargeIcon: 'ic_launcher', // (optional) default: undefined bigLargeIconUrl: 'https://www.example.tld/bigicon.jpg', // (optional) default: undefined color: 'red', // (optional) default: system default vibrate: true, // (optional) default: true vibration: 300, // vibration length in milliseconds, ignored if vibrate=false, default: 1000 tag: 'some_tag', // (optional) add tag to message group: 'group', // (optional) add group to message groupSummary: false, // (optional) set this notification to be the group summary for a group of notifications, default: false ongoing: false, // (optional) set whether this is an "ongoing" notification priority: 'high', // (optional) set notification priority, default: high visibility: 'public', // (optional) set notification visibility, default: private ignoreInForeground: false, // (optional) if true, the notification will not be visible when the app is in the foreground (useful for parity with how iOS notifications appear). should be used in combine with `com.dieam.reactnativepushnotification.notification_foreground` setting shortcutId: 'shortcut-id', // (optional) If this notification is duplicative of a Launcher shortcut, sets the id of the shortcut, in case the Launcher wants to hide the shortcut, default undefined onlyAlertOnce: false, // (optional) alert will open only once with sound and notify, default: false when: null, // (optional) Add a timestamp (Unix timestamp value in milliseconds) pertaining to the notification (usually the time the event occurred). For apps targeting Build.VERSION_CODES.N and above, this time is not shown anymore by default and must be opted into by using `showWhen`, default: null. usesChronometer: false, // (optional) Show the `when` field as a stopwatch. Instead of presenting `when` as a timestamp, the notification will show an automatically updating display of the minutes and seconds since when. Useful when showing an elapsed time (like an ongoing phone call), default: false. timeoutAfter: null, // (optional) Specifies a duration in milliseconds after which this notification should be canceled, if it is not already canceled, default: null actions: ['Yes', 'No'], // (Android only) See the doc for notification actions to know more invokeApp: true, // (optional) This enable click on actions to bring back the application to foreground or stay in background, default: true title: 'MapToBe', // (optional) id: 3, message: geofenceEvent.extras.description, // (required) picture: 'https://www.example.tld/picture.jpg', // (optional) Display an picture with the notification, alias of `bigPictureUrl` for Android. default: undefined userInfo: {}, // (optional) default: {} (using null throws a JSON value '<null>' error) playSound: false, // (optional) default: true soundName: 'default', // (optional) Sound to play when the notification is shown. Value of 'default' plays the default sound. It can be set to a custom sound such as 'android.resource://com.xyz/raw/my_sound'. It will look for the 'my_sound' audio file in 'res/raw' directory and play it. default: 'default' (default sound is played) number: 10, // (optional) Valid 32 bit integer specified as string. default: none (Cannot be zero) repeatType: 'day', // (optional) Repeating interval. Check 'Repeating Notifications' section for more info. }); } }; // Add BG event subscription to collection const subscribe = subscription => { subscriptions.push(subscription); }; // Clear BG subscriptions const unsubscribe = () => { subscriptions.forEach(subscription => subscription.remove()); subscriptions.splice(0, subscriptions.length); }; // [STORAGE] Function for adding pins to storage const addPins = async arr => { let newArray = []; //const data = await getPinsFromStorage(); //if(data) newArray = [...data]; console.log('new array:', newArray); arr.forEach(pin => { newArray.push(pin); }); setPins(newArray); addPinsToStorage(newArray); console.log('pins:', pins); console.log('new array:', newArray); }; //fetch search api and get location by search text const getLocation = async searchText => { const url = `https://api.mapbox.com/search/v1/forward/${searchText}?language=en&limit=1&country=BA&access_token=MAPBOX_ACCESS_TOKEN`; try { const response = await fetch(url); const data = await response.json(); // coordinates array: [lat, lng] const coordinates = data.features[0].geometry.coordinates; console.log('coordinates:', coordinates); return coordinates; } catch (error) { console.log('Error:', error); } }; /** * [STORAGE] ** Sets state renderedMap to true ** For displaying components after map is finished rendering ** placeMarkersOnMap method is called for displaying markers from storage */ const setRenderedMapState = () => { //TODO: why set to true if renderedMap default state is true? //Also, onDidFinishRenderingMapFully is never triggered, //so placeMarkersOnMap is moved to useEffect on initial render of component, //so do we really need this handler at all? setRenderedMap(true); }; const BetaFeature_Modal_Open = () => { setIsBetaFeatureModalVisible(value => !value); }; /** ** Sets state markerMode to true ** Markers can be added on map */ const createMarkerMode = () => { setIsPlaceMarkerVisible(true); setMarkerMode(true); }; // const handleSignOut = () => { // auth().signOut().then(() => { // console.log("ODJAVA"); // // return <SignUp /> // }).catch((error) => { // // An error happened. // }); // } /** * [STORAGE] ** Gets markers from storage and updates markers state ** CHANGE THIS: If there is no markers in storage adds current location in markers state ** 2023.02.02 UPDATE: ** CURRENT LOCATION SHOULD NOT BE STORED ** */ const placeMarkersOnMap = () => { BackgroundGeolocation.getGeofences() .then(geofences => { const markersFromGeofenceStorage = geofences.map(marker => ({ coordinates: [marker.longitude, marker.latitude], description: marker.extras.description, })); setMarkers(markersFromGeofenceStorage); }) .catch(error => console.log('Error getting geofences: ', error)); }; /** * [STORAGE] TBD ** Updates markers state and storage with coordinates passed from * @param {GeoJSON.Feature} touch ** Displays marker on the map and SetupMarker modal */ const createMarker = (touch: Feature<Point>) => { // if (markerMode && !isAddDescriptionVisible) { console.log('geometry', touch.geometry.coordinates); if (markerMode) { let updatedMarkers; if (typeof markers !== 'undefined') { updatedMarkers = [...markers]; } else { updatedMarkers = []; } let marker = { coordinates: touch.geometry.coordinates, description: description, }; updatedMarkers.push(marker); setMarkers(updatedMarkers); // setMarkers( currentMarkers => [...currentMarkers, // { // coordinates: { // latitude: touch.geometry.coordinates[1], // longitude: touch.geometry.coordinates[0] // }, // description: description // } // ]); setAddDescriptionVisible(true); analytics().logEvent('marker_added', { coordinates: touch.geometry.coordinates, }); } }; /** ** Removes the touched marker from view, storage and geofence * @param marker - touched marker */ const removeMarker = marker => { let updatedMarkers; //remove marker geofence: BackgroundGeolocation.getGeofences().then(geofences => { for (let i = geofences.length - 1; i >= 0; --i) { if ( geofences[i].latitude === marker.coordinates[1] && geofences[i].longitude === marker.coordinates[0] ) { BackgroundGeolocation.removeGeofence(geofences[i].identifier); } } }); //remove marker from UI and storage: for (let i = markers.length - 1; i >= 0; --i) { if (markers[i] == marker) { updatedMarkers = markers.filter(currentMarker => { return currentMarker !== marker; }); setMarkers(updatedMarkers); addMarkersToStorage(updatedMarkers); } } analytics().logEvent('marker_removed', { coordinates: marker.coordinate, }); }; const editMarker = (newValue, oldValue) => { //TODO: instead of editing/removing like this, save identifiers of markers in state. console.log('New Value', newValue); console.log('Old Value: ', oldValue); let findMarkerWithDescription; for (var x of markers) { if (x.description == oldValue) { x.description = newValue; findMarkerWithDescription = x; } } const updatedMarkers = [...markers]; let geofenceIdentifier; updatedMarkers.pop(); updatedMarkers.push(findMarkerWithDescription); BackgroundGeolocation.getGeofences().then(geofences => { for (let i = geofences.length - 1; i >= 0; --i) { if ( geofences[i].latitude === findMarkerWithDescription.coordinates[1] && geofences[i].longitude === findMarkerWithDescription.coordinates[0] ) { geofenceIdentifier = geofences[i].identifier; BackgroundGeolocation.removeGeofence(geofences[i].identifier).then( success => { BackgroundGeolocation.addGeofence({ identifier: geofenceIdentifier, radius: 200, latitude: findMarkerWithDescription.coordinates[1], longitude: findMarkerWithDescription.coordinates[0], notifyOnEntry: true, extras: { description: newValue, }, }) .then(success => { console.log('added geofence description success'); }) .catch(error => { console.log('couldnt add geofence, error: ', error); }); }, ); } } }); }; // there is an option in mapbox should be checked const seeMarkerInfo = marker => { setSelectedMarker(marker); setIsTaskListVisible(true); analytics().logEvent('marker_pressed', { marker: marker.coordinate, }); }; return ( <View style={ Platform.OS === 'ios' ? {...style.wrapper, paddingTop: '8%'} : style.wrapper }> {/* <Button title="Log Out" onPress={handleSignOut}></Button> */} <MapboxGL.MapView onPress={createMarker} styleURL={MapboxGL.StyleURL.Street} logoEnabled={false} style={{zIndex: -1, height: '100%', width: '100%'}} onDidFinishRenderingMapFully={setRenderedMapState} // userTrackingMode={MapboxGL.UserTrackingModes.Follow} -> Functionality via BG plugin rotateEnabled={false} scaleBarEnabled={false} pitchEnabled={true} scrollEnabled={true} zoomEnabled={true}> <MapboxGL.Camera zoomLevel={15} centerCoordinate={currentLocation} followZoomLevel={15} // followUserMode={'normal'} /> { ( <MapboxGL.MarkerView id="currentLocation" coordinate={currentLocation}> <Image source={require('../../assets/icons/currentLocationMarker.png')} /> </MapboxGL.MarkerView> )} { //markers for interests locations //check if there are liked interests and display them, if not display all available interests.filter(arg => arg.liked).length == 0 ? //display all interests interests.map((item) => { return ( <MapboxGL.MarkerView id={item.name} key={item.name} coordinate={[item.lng, item.lat]} allowOverlap={true} > <CategoryInterpretor item={item} /> </MapboxGL.MarkerView>) }) : //display liked interests interests.map((item) => { return ( item.liked ? <MapboxGL.MarkerView id={item.name} key={item.name} coordinate={[item.lng, item.lat]} allowOverlap={true} > <CategoryInterpretor item={item} /> </MapboxGL.MarkerView> : <MapboxGL.MarkerView id={item.name} key={item.name} coordinate={[item.lng, item.lat]} allowOverlap={true} > </MapboxGL.MarkerView> ) }) } {markers && markers.map(marker => ( <Marker key={marker.coordinates} // id={marker.coordinates.toString()} coordinate={marker.coordinates} seeMarkerInfo={() => seeMarkerInfo(marker)} selected={marker === selectedMarker && isTaskListVisible} /> ))} </MapboxGL.MapView> {/* ADD MARKER BUTTON */} {!markerMode && !isTaskListVisible && ( <Pressable style={{...style.button}} onPress={()=>{analytics().logEvent('pressed_add_marker_PositionedMapView'); createMarkerMode()}}> <Image source={require('../../assets/icons/add.png')} /> </Pressable> )} {/** Without these conditions, PlaceMarker and TaskList modals are ovelapping */} { <ToggleModals isPlaceMarkerVisible={isPlaceMarkerVisible} setIsPlaceMarkerVisible={setIsPlaceMarkerVisible} isAddDescriptionVisible={isAddDescriptionVisible} setAddDescriptionVisible={setAddDescriptionVisible} description={description} setDescription={setDescription} markers={markers} setMarkers={setMarkers} setIsMarkersPlaced={setIsMarkersPlaced} longitude={longitude} latitude={latitude} setMarkerMode={setMarkerMode} /> } {/* BETA FEATURE MODAL THAT CONTAINS EXTRA FEATURES */} { !markerMode && !isAddDescriptionVisible && !isTaskListVisible &&( <Pressable style={style.button2} onPress={()=>{analytics().logEvent('pressed_logs'); BetaFeature_Modal_Open()}}> <Text>LOGS</Text> </Pressable> )} <BetaFeature_Modal isBetaFeatureModalVisible={isBetaFeatureModalVisible} setIsBetaFeatureModalVisible={setIsBetaFeatureModalVisible} debugToggle={debugToggle} isDebug={isDebug} /> {/** If these conditions are fulfilled then render modal, otherwise without it modals are ovelapping */} {/* {(!markerMode && !isAddDescriptionVisible && isTaskListVisible) && */} {!markerMode && isTaskListVisible && ( <TaskList isTaskListVisible={ !markerMode && !isAddDescriptionVisible && isTaskListVisible } setIsTaskListVisible={setIsTaskListVisible} children={selectedMarker.description} removeMarker={() => { removeMarker(selectedMarker); setSelectedMarker(''); setIsTaskListVisible(false); }} setDescription={setDescription} editMarker={editMarker} setIsMarkersPlaced={setIsMarkersPlaced} /> )} {markers && isAllTasksVisible && ( <Tasks tasks={markers} close={() => setIsAllTasksVisible(false)} /> )} </View> ); }; export default PositionedMapView;