Untitled

mail@pastecode.io avatar
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;