Untitled
unknown
plain_text
2 years ago
22 kB
12
Indexable
//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;
Editor is loading...