Repository: SOEN-390-pmzone/SOEN-_390-Q-QD
Pull request #166: Find outdoor point of interest
Author: login: soulinaaa
Created: 2025-03-13T03:16:58Z | Merged: 2025-03-23T14:23:57Z

### **Pull Request: Add Outdoor Points of Interest (POI) Functionality**
This pull request implements comprehensive outdoor navigation functionality for Concordia's points of interest (POI), addressing user stories related to cafes, restaurants, and other outdoor locations.

**Key Features**
Users can now view outdoor points of interest (POI) such as cafes and restaurants on the map. Each POI is represented by a custom marker.
Users can click on a POI marker to view a detailed popup with the name, address, and an option to get directions.
The app now supports dynamic popups for outdoor POIs, ensuring a seamless user experience similar to the outdoorbuilding navigation feature.

**Test**
- [x] Tested POI Marker Rendering

- [x] Tested Popup Functionality

![image](https://github.com/user-attachments/assets/3e3b8ef8-4eaa-4045-b83f-40a673d6b7e4)






Diff:
diff --git a/ConcordiaMaps/__tests__/HomeScreen.test.js b/ConcordiaMaps/__tests__/HomeScreen.test.js
index 302a5eb0..4f38e8c8 100644
--- a/ConcordiaMaps/__tests__/HomeScreen.test.js
+++ b/ConcordiaMaps/__tests__/HomeScreen.test.js
@@ -31,8 +31,8 @@ describe("HomeScreen", () => {
       <NavigationContainer>
         <ModalContext.Provider
           value={{
-            toggleModal: mockToggleModal,
-            setModalData: mockSetModalData,
+            toggleModal: mockToggleModal, // Mock function for toggleModal
+            setModalData: mockSetModalData, // Mock function for setModalData
           }}
         >
           <LocationContext.Provider value={{ location: mockLocation }}>
@@ -160,4 +160,74 @@ describe("HomeScreen", () => {
 
     expect(getByTestId("map-view")).toBeTruthy();
   });
+  const mockAnimateToRegion = jest.fn();
+  const mockMapRef = {
+    current: {
+      animateToRegion: mockAnimateToRegion,
+    },
+  };
+
+  jest.mock("react-native-maps", () => ({
+    __esModule: true,
+    Marker: jest.fn(),
+    MapView: jest.fn().mockImplementation(({ children, ...props }) => {
+      return (
+        <div {...props}>
+          <div data-testid="map">{children}</div>
+        </div>
+      );
+    }),
+  }));
+
+  describe("HomeScreen", () => {
+    // Test setup to mock setTimeout and mapRef
+    beforeEach(() => {
+      jest.useFakeTimers(); // Use fake timers to mock setTimeout
+      mockAnimateToRegion.mockClear(); // Clear mock calls before each test
+    });
+
+    afterEach(() => {
+      jest.useRealTimers(); // Restore real timers after tests
+    });
+
+    it("should update map region and animate to the selected location on place select", async () => {
+      const setMapRegion = jest.fn();
+      const setSelectedLocation = jest.fn();
+
+      // Set up the necessary props and render the component with the correct context
+      renderComponent();
+
+      // Mock the region object (example)
+      const region = {
+        latitude: 40.7128,
+        longitude: -74.006,
+        latitudeDelta: 0.005,
+        longitudeDelta: 0.005,
+      };
+
+      // Trigger the handlePlaceSelect logic
+      act(() => {
+        // Assuming handlePlaceSelect is called with a region object
+        // You need to trigger the code that calls the setTimeout in your component.
+        setTimeout(() => {
+          setMapRegion(region);
+          setSelectedLocation(region);
+          mockMapRef.current?.animateToRegion(region, 1000);
+        }, 100);
+      });
+
+      // Fast-forward all timers by 100ms
+      act(() => {
+        jest.runAllTimers();
+      });
+
+      // Assertions
+      expect(setMapRegion).toHaveBeenCalledWith(region); // Check if setMapRegion was called with the correct region
+      expect(setSelectedLocation).toHaveBeenCalledWith(region); // Check if setSelectedLocation was called with the correct region
+      expect(mockMapRef.current?.animateToRegion).toHaveBeenCalledWith(
+        region,
+        1000,
+      ); // Check if animateToRegion was called with the region and correct duration
+    });
+  });
 });
diff --git a/ConcordiaMaps/__tests__/components/popupOPI.test.js b/ConcordiaMaps/__tests__/components/popupOPI.test.js
new file mode 100644
index 00000000..8e29febb
--- /dev/null
+++ b/ConcordiaMaps/__tests__/components/popupOPI.test.js
@@ -0,0 +1,80 @@
+import React from "react";
+import { render, fireEvent } from "@testing-library/react-native";
+import PopupOPI from "../../components/PopupOPI";
+import "@testing-library/jest-native/extend-expect";
+import { Alert } from "react-native";
+
+describe("<PopupOPI/>", () => {
+  const mockOnClose = jest.fn();
+  //Creating mock data
+  const mockData = {
+    name: "Test Cafe",
+    address: "123 Test St.",
+  };
+  it("renders correctly when visible", () => {
+    const { getByText } = render(
+      <PopupOPI isVisible={true} data={mockData} onClose={mockOnClose} />,
+    );
+
+    expect(getByText("Test Cafe")).toBeTruthy();
+    expect(getByText("123 Test St.")).toBeTruthy();
+    expect(getByText("Close")).toBeTruthy();
+    expect(getByText("Get Directions")).toBeTruthy();
+  });
+  it("displays default test when data is missing", () => {
+    const { getByText } = render(
+      <PopupOPI isVisible={true} data={{}} onClose={mockOnClose} />,
+    );
+
+    expect(getByText("Cafe/Restaurant Name")).toBeTruthy();
+    expect(getByText("Address not available")).toBeTruthy();
+  });
+  it("calls onClose when Close button is pressed", () => {
+    const { getByText } = render(
+      <PopupOPI isVisible={true} data={mockData} onClose={mockOnClose} />,
+    );
+
+    fireEvent.press(getByText("Close"));
+
+    expect(mockOnClose).toHaveBeenCalled();
+  });
+  it("displays the modal when visible is true", () => {
+    const { getByText } = render(
+      <PopupOPI isVisible={true} data={mockData} onClose={mockOnClose} />,
+    );
+    expect(getByText("Test Cafe")).toBeTruthy();
+    expect(getByText("123 Test St.")).toBeTruthy();
+  });
+  it("does not display the modal when visible is false", () => {
+    const { queryByText } = render(
+      <PopupOPI isVisible={false} data={mockData} onClose={mockOnClose} />,
+    );
+    expect(queryByText("Test Cafe")).toBeNull();
+    expect(queryByText("123 Test St.")).toBeNull();
+  });
+  it("Get Directions alert appears when Get Directions is pressed", () => {
+    const alertSpy = jest.spyOn(Alert, "alert"); // Spy on Alert.alert
+
+    const { getByText } = render(
+      <PopupOPI isVisible={true} data={mockData} onClose={mockOnClose} />,
+    );
+
+    fireEvent.press(getByText("Get Directions"));
+
+    // Check if Alert.alert was called with correct arguments
+    expect(alertSpy).toHaveBeenCalledWith(
+      "Get Directions",
+      "Directions pressed",
+    );
+
+    alertSpy.mockRestore(); // Restore original Alert.alert function
+  });
+  it("handles completely omitted data prop by using defaults", () => {
+    const { getByText } = render(
+      <PopupOPI isVisible={true} onClose={mockOnClose} />,
+    );
+
+    expect(getByText("Cafe/Restaurant Name")).toBeTruthy();
+    expect(getByText("Address not available")).toBeTruthy();
+  });
+});
diff --git a/ConcordiaMaps/assets/cafeICON.png b/ConcordiaMaps/assets/cafeICON.png
new file mode 100644
index 00000000..fb987388
Binary files /dev/null and b/ConcordiaMaps/assets/cafeICON.png differ
diff --git a/ConcordiaMaps/assets/parkICON.png b/ConcordiaMaps/assets/parkICON.png
new file mode 100644
index 00000000..c90c9264
Binary files /dev/null and b/ConcordiaMaps/assets/parkICON.png differ
diff --git a/ConcordiaMaps/assets/restoICON.png b/ConcordiaMaps/assets/restoICON.png
new file mode 100644
index 00000000..6fc5ab0a
Binary files /dev/null and b/ConcordiaMaps/assets/restoICON.png differ
diff --git a/ConcordiaMaps/assets/storeICON.png b/ConcordiaMaps/assets/storeICON.png
new file mode 100644
index 00000000..f653f76f
Binary files /dev/null and b/ConcordiaMaps/assets/storeICON.png differ
diff --git a/ConcordiaMaps/components/PopupOPI.js b/ConcordiaMaps/components/PopupOPI.js
new file mode 100644
index 00000000..6e2b9f6a
--- /dev/null
+++ b/ConcordiaMaps/components/PopupOPI.js
@@ -0,0 +1,53 @@
+import React from "react";
+import { Modal, View, Text, TouchableOpacity, Alert } from "react-native";
+import PropTypes from "prop-types";
+import styles from "../styles/DirectionBox.style"; // Use the same styles as the building popup
+
+const DEFAULT_NAME = "Cafe/Restaurant Name";
+const DEFAULT_ADDRESS = "Address not available";
+
+const PopupOPI = ({ isVisible, data = {}, onClose }) => {
+  const { name = DEFAULT_NAME, address = DEFAULT_ADDRESS } = data;
+
+  return (
+    <Modal
+      transparent
+      animationType="slide"
+      visible={isVisible}
+      onRequestClose={onClose}
+    >
+      <View style={styles.modalOverlay}>
+        <View style={styles.modalContainer}>
+          <Text style={styles.modalTitle}>{name}</Text>
+          <Text style={styles.modalText}>{address}</Text>
+
+          <View style={styles.buttonsContainer}>
+            <TouchableOpacity style={styles.closeButton} onPress={onClose}>
+              <Text style={styles.closeButtonText}>Close</Text>
+            </TouchableOpacity>
+
+            <TouchableOpacity
+              style={styles.getDirectionsButton}
+              onPress={() =>
+                Alert.alert("Get Directions", "Directions pressed")
+              }
+            >
+              <Text style={styles.getDirectionsButtonText}>Get Directions</Text>
+            </TouchableOpacity>
+          </View>
+        </View>
+      </View>
+    </Modal>
+  );
+};
+
+PopupOPI.propTypes = {
+  isVisible: PropTypes.bool.isRequired,
+  data: PropTypes.shape({
+    name: PropTypes.string.isRequired,
+    address: PropTypes.string,
+  }).isRequired,
+  onClose: PropTypes.func.isRequired,
+};
+
+export default PopupOPI;
diff --git a/ConcordiaMaps/constants/OutdoorPtsOfDirections.js b/ConcordiaMaps/constants/OutdoorPtsOfDirections.js
new file mode 100644
index 00000000..098ff1f0
--- /dev/null
+++ b/ConcordiaMaps/constants/OutdoorPtsOfDirections.js
@@ -0,0 +1,91 @@
+const restaurantMarker = require("../assets/restoICON.png");
+const cafeMarker = require("../assets/cafeICON.png");
+const parkMarker = require("../assets/parkICON.png");
+const storeMarker = require("../assets/storeICON.png");
+
+export const PointsOfInterest = [
+  {
+    name: "Poulet Rouge",
+    coordinate: { latitude: 45.4947454, longitude: -73.5783503 },
+    address: "1623 Rue Sainte-Catherine, Montréal, QC H3H 1L8",
+    markerImage: restaurantMarker,
+  },
+  {
+    name: "Marche Newon",
+    coordinate: { latitude: 45.4943362, longitude: -73.5785438 },
+    address: "1616 Rue Sainte-Catherine 302 unit, Montréal, QC H3H 1L7",
+    markerImage: restaurantMarker,
+  },
+  {
+    name: "Tim Horton's",
+    coordinate: { latitude: 45.4948692, longitude: -73.5782373 },
+    address: "1611 Rue Sainte-Catherine , Montréal, QC H3H 1L8",
+    markerImage: cafeMarker,
+  },
+  {
+    name: "Subway",
+    coordinate: { latitude: 45.49601, longitude: -73.5801927 },
+    address: "2144 guy st, Montréal, QC H3H 2N4",
+    markerImage: restaurantMarker,
+  },
+  {
+    name: "Second Cup",
+    coordinate: { latitude: 45.4992233, longitude: -73.5735757 },
+    address: "Rue Sainte-Catherine ST W Suite 1166 , Montréal, QC H3B 1K1",
+    markerImage: cafeMarker,
+  },
+  {
+    name: "Java U",
+    coordinate: { latitude: 45.4958331, longitude: -73.5791174 },
+    address: "1455 Guy St , Montréal, QC H3H 2L5",
+    markerImage: cafeMarker,
+  },
+  {
+    name: "Raku",
+    coordinate: { latitude: 45.46041076810318, longitude: -73.63561362331475 },
+    address: "6929 Sherbrooke St W, Montreal, Quebec H4B 1P8",
+    markerImage: restaurantMarker,
+  },
+  {
+    name: "Loyola Park",
+    coordinate: { latitude: 45.46121016288844, longitude: -73.64596844799468 },
+    address: "4877 Av. Doherty, Montréal, QC H4V 2B2",
+    markerImage: parkMarker,
+  },
+  {
+    name: "Chicago Pizza",
+    coordinate: { latitude: 45.45300050371994, longitude: -73.6460230368441 },
+    address: "54 Westminster Ave N, Montreal West, Quebec H4X 1Z2",
+    markerImage: restaurantMarker,
+  },
+  {
+    name: "RESTAURANT TESFU",
+    coordinate: { latitude: 45.453654351956224, longitude: -73.63725347582756 },
+    address: "7427 Rue Saint-Jacques, Montréal, QC H4B 1Y2",
+    markerImage: restaurantMarker,
+  },
+  {
+    name: "Hingston Cafe",
+    coordinate: { latitude: 45.459321334818476, longitude: -73.64163638016397 },
+    address: "7141 Sherbrooke St W, Montreal, Quebec H4B 1R6",
+    markerImage: cafeMarker,
+  },
+  {
+    name: "Second Cup Café",
+    coordinate: { latitude: 45.45610103268165, longitude: -73.64082945641897 },
+    address: "7335-7345 Sherbrooke St W, Montreal, Quebec H4B 1R9",
+    markerImage: cafeMarker,
+  },
+  {
+    name: "Couche-Tard",
+    coordinate: { latitude: 45.46063658996555, longitude: -73.63556785904616 },
+    address: "6905 Sherbrooke St W, Montreal, Quebec H4B 1P8",
+    markerImage: storeMarker,
+  },
+  {
+    name: "Infinithe",
+    coordinate: { latitude: 45.45576662226191, longitude: -73.64108986672184 },
+    address: "7363 Sherbrooke St W, Montreal, Quebec H4B 1S1",
+    markerImage: cafeMarker,
+  },
+];
diff --git a/ConcordiaMaps/screen/HomeScreen.js b/ConcordiaMaps/screen/HomeScreen.js
index 4fd358b9..be194de1 100644
--- a/ConcordiaMaps/screen/HomeScreen.js
+++ b/ConcordiaMaps/screen/HomeScreen.js
@@ -7,7 +7,6 @@ import TemporaryModal from "../components/temporaryModal";
 import { LocationContext } from "../contexts/LocationContext";
 import Footer from "../components/Footer";
 import styles from "../styles";
-const customMarkerImage = require("../assets/PinLogo.png");
 import { Building } from "../components/MapMarkers";
 import { ModalContext } from "../App";
 import BuildingColoring from "../components/buildingColoring";
@@ -21,22 +20,31 @@ import {
 } from "../components/AsyncPersistence";
 import convertToCoordinates from "../components/convertToCoordinates";
 import PropTypes from "prop-types";
+import PopupOPI from "../components/PopupOPI"; // Import the new popup component
+import { PointsOfInterest } from "../constants/OutdoorPtsOfDirections"; // Import the new Points of Interest data
+
+// Marker image assets for Restaurant and Cafe
+const customMarkerImage = require("../assets/PinLogo.png");
 
 function HomeScreen({ asyncKey = "Campus" }) {
   const loyolaPostalCode = process.env.EXPO_PUBLIC_LOYOLA_POSTAL_CODE;
   const sgwPostalCode = process.env.EXPO_PUBLIC_SGW_POSTAL_CODE;
 
   const location = useContext(LocationContext);
-  const { toggleModal, setModalData } = useContext(ModalContext); // Access setModalData
+  const { toggleModal, setModalData } = useContext(ModalContext);
 
   const [postalCode, setPostalCode] = useState(null);
   const [coordinates, setCoordinates] = useState(null);
   const [error, setError] = useState("");
   const [selectedLocation, setSelectedLocation] = useState(null);
   const [, setMapRegion] = useState(null);
-  const borderColor = "#912338"; // Initial border color (red)
+  const borderColor = "#912338";
   const mapRef = useRef(null);
-  const toggleModalTime = "10000";
+  const TOGGLE_MODAL_TIMEOUT = 10000;
+
+  // State for OPI (Points of Interest) popup
+  const [opiPopupVisible, setOpiPopupVisible] = useState(false);
+  const [selectedOPI, setSelectedOPI] = useState(null);
 
   useEffect(() => {
     const fetchLastCampus = async () => {
@@ -88,7 +96,7 @@ function HomeScreen({ asyncKey = "Campus" }) {
           longitudeDelta: 0.01,
         },
         2500,
-      ); // Duration of the animation in milliseconds
+      );
     }
   }, [coordinates]);
 
@@ -113,15 +121,19 @@ function HomeScreen({ asyncKey = "Campus" }) {
     }, 100);
   };
 
-  // Function to handle marker press and pass data to the modal
   const handleMarkerPress = (building) => {
     setModalData({
       name: building.name,
-      coordinate: building.coordinat,
+      coordinate: building.coordinate,
       address: building.address,
       fullBuildingName: building.fullBuildingName,
-    }); // Update modalData
-    toggleModal(); // Show modal
+    });
+    toggleModal();
+  };
+
+  const handleOPIMarkerPress = (poi) => {
+    setSelectedOPI(poi);
+    setOpiPopupVisible(true);
   };
 
   const [modalState, setModalState] = useState(true);
@@ -129,11 +141,12 @@ function HomeScreen({ asyncKey = "Campus" }) {
     if (modalState) {
       const timer = setTimeout(() => {
         setModalState(false);
-      }, toggleModalTime); // Modal will disappear after 3 seconds
+      }, TOGGLE_MODAL_TIMEOUT);
 
-      return () => clearTimeout(timer); // Cleanup the timer if the component unmounts
+      return () => clearTimeout(timer);
     }
   }, [modalState]);
+
   return (
     <View style={styles.container} testID="home-screen">
       <Header />
@@ -145,7 +158,7 @@ function HomeScreen({ asyncKey = "Campus" }) {
         <>
           <TemporaryModal
             text="Press the button to switch campuses"
-            time={toggleModalTime}
+            time={TOGGLE_MODAL_TIMEOUT}
             modalState={modalState}
             onRequestClose={() => setModalState(false)}
             TestID="toggleModal"
@@ -164,7 +177,7 @@ function HomeScreen({ asyncKey = "Campus" }) {
                     longitudeDelta: 0.005,
                   }
                 : {
-                    latitude: coordinates.latitude, // Default center (SGW campus)
+                    latitude: coordinates.latitude,
                     longitude: coordinates.longitude,
                     latitudeDelta: 0.01,
                     longitudeDelta: 0.01,
@@ -191,6 +204,20 @@ function HomeScreen({ asyncKey = "Campus" }) {
                 />
               </Marker>
             ))}
+            {PointsOfInterest.map((poi) => (
+              <Marker
+                key={poi.name}
+                coordinate={poi.coordinate}
+                title={poi.name}
+                description={poi.address}
+                onPress={() => handleOPIMarkerPress(poi)}
+              >
+                <Image
+                  source={poi.markerImage}
+                  style={styles.customMarkerImage}
+                />
+              </Marker>
+            ))}
             <BuildingColoring />
             {selectedLocation && (
               <Marker
@@ -218,7 +245,7 @@ function HomeScreen({ asyncKey = "Campus" }) {
               <Image
                 style={styles.buttonImage}
                 source={require("../assets/ToggleButton.png")}
-                resizeMode={"cover"} // cover or contain its up to you view look
+                resizeMode={"cover"}
               />
             </TouchableOpacity>
           </View>
@@ -229,10 +256,19 @@ function HomeScreen({ asyncKey = "Campus" }) {
       )}
       {error ? <Text>Error: {error}</Text> : null}
       <Footer />
+
+      {/* OPI Popup */}
+      <PopupOPI
+        isVisible={opiPopupVisible}
+        data={selectedOPI || { name: "", address: "" }}
+        onClose={() => setOpiPopupVisible(false)}
+      />
     </View>
   );
 }
+
 HomeScreen.propTypes = {
   asyncKey: PropTypes.string,
 };
+
 export default HomeScreen;


Reviews:
user: login: MomoG221
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-18T19:21:04Z
user: login: shanvinluo
body: Looks good, just some Refactoring fixes
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-19T00:46:43Z
user: login: shanvinluo
body: Just missing the Loyola OPIs. I approved the changes since we talked about working on it in another branch. If subject to change, we will reopen this branch or continue devving on this branch
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-19T00:53:19Z
user: login: Atai-Askarov
body: @soulinaaa Please take a look at my in line comments. Thank you!

state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-19T21:15:24Z
user: login: shanvinluo
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T06:49:57Z
user: login: shanvinluo
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T06:50:31Z
user: login: shanvinluo
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T07:24:19Z
user: login: shanvinluo
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T07:26:26Z
user: login: shanvinluo
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T07:29:26Z
user: login: shanvinluo
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T07:40:35Z
user: login: shanvinluo
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T07:41:36Z
user: login: shanvinluo
body: I have fixed all outstanding issues and added the Loyola Outdoor Points of interest markers. All that is missing is linking the "GetDirections" Button to the actual function. This will be taken care of as soon as its merged to develop since another branch implemented it.

Also, I have tested everything related to the new outdoor points of interest (POI) functionality.

- I verified that POI markers render correctly on the map.
- I tested the popup functionality, ensuring that the name, address, and directions are displayed when clicking on a POI marker.
- I also ensured the app behaves as expected, providing a seamless experience for users when interacting with outdoor POIs.
Everything has been tested thoroughly and works as expected.

Ready to merge!

state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-23T08:34:05Z
user: login: Atai-Askarov
body: All the indicated refactoring has been conducted. Pop ups and markers are visible on both campuses.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-23T14:23:42Z

Review comments:
diff_hunk: @@ -0,0 +1,52 @@
+import React from "react";
+import { Modal, View, Text, TouchableOpacity, Alert } from "react-native";
+import PropTypes from "prop-types";
+import styles from "../styles/DirectionBox.style"; // Use the same styles as the building popup
+
+const PopupOPI = ({ isVisible, data, onClose }) => {
+  const { name, address } = data;
+
+  return (
+    <Modal
+      transparent
+      animationType="slide"
+      visible={isVisible}
+      onRequestClose={onClose}
+    >
+      <View style={styles.modalOverlay}>
+        <View style={styles.modalContainer}>
+          <Text style={styles.modalTitle}>{name || "Cafe/Restaurant Name"}</Text>
+          <Text style={styles.modalText}>
+            {address || "Address not available"}
+          </Text>
+
+          <View style={styles.buttonsContainer}>
+            <TouchableOpacity style={styles.closeButton} onPress={onClose}>
+              <Text style={styles.closeButtonText}>Close</Text>
+            </TouchableOpacity>
+
+            <TouchableOpacity
+              style={styles.getDirectionsButton}
+              onPress={() =>
path: ConcordiaMaps/components/PopupPOI.js
user: login: MomoG221
body: Extract the inline onPress function for the "Get Directions" button into a separate function like handleGetDirections(). This improves readability and makes the code easier to maintain. This follows the "Extract Function" refactoring from the [Catalog](https://github.com/SOEN-390-pmzone/SOEN-_390-Q-QD/wiki/Refactoring-Template-for-QA), which helps reduce duplication and improve modularity
created_at: 2025-03-18T19:17:30Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,52 @@
+import React from "react";
+import { Modal, View, Text, TouchableOpacity, Alert } from "react-native";
+import PropTypes from "prop-types";
+import styles from "../styles/DirectionBox.style"; // Use the same styles as the building popup
+
+const PopupOPI = ({ isVisible, data, onClose }) => {
+  const { name, address } = data;
+
+  return (
+    <Modal
+      transparent
+      animationType="slide"
+      visible={isVisible}
+      onRequestClose={onClose}
+    >
+      <View style={styles.modalOverlay}>
+        <View style={styles.modalContainer}>
+          <Text style={styles.modalTitle}>{name || "Cafe/Restaurant Name"}</Text>
path: ConcordiaMaps/components/PopupPOI.js
user: login: MomoG221
body:  The default name "Cafe/Restaurant Name" and address "Address not available" are hardcoded strings. Moving them into constants like DEFAULT_NAME and DEFAULT_ADDRESS improves maintainability. This follows the "Replace Magic Literal" refactoring from the [Catalog](https://github.com/SOEN-390-pmzone/SOEN-_390-Q-QD/wiki/Refactoring-Template-for-QA). 
created_at: 2025-03-18T19:20:44Z
author_association: COLLABORATOR
diff_hunk: @@ -21,23 +20,73 @@ import {
 } from "../components/AsyncPersistence";
 import convertToCoordinates from "../components/convertToCoordinates";
 import PropTypes from "prop-types";
+import PopupOPI from "../components/PopupOPI"; // Import the new popup component
+
+// Marker image assets for Restaurant and Cafe
+const restaurantMarker = require("../assets/restoICON.png");
+const cafeMarker = require("../assets/cafeICON.png");
+const customMarkerImage = require("../assets/PinLogo.png");
+
+// Example data for PointsOfInterest
+const PointsOfInterest = [
+  {
+    name: "Poulet Rouge",
+    coordinate: { latitude: 45.4947454, longitude: -73.5783503 },
+    address: "1623 Rue Sainte-Catherine, Montréal, QC H3H 1L8",
+    markerImage: restaurantMarker,
+  },
+  {
+    name: "Marche Newon",
+    coordinate: { latitude: 45.4943362, longitude: -73.5785438 },
+    address: "1616 Rue Sainte-Catherine 302 unit, Montréal, QC H3H 1L7",
+    markerImage: restaurantMarker,
+  },
+  {
+    name: "Tim Horton's",
+    coordinate: { latitude: 45.4948692, longitude: -73.5782373 },
+    address: "1611 Rue Sainte-Catherine , Montréal, QC H3H 1L8",
+    markerImage: cafeMarker,
+  },
+  {
+    name: "Subway",
+    coordinate: { latitude: 45.49601, longitude: -73.5801927 },
+    address: "2144 guy st, Montréal, QC H3H 2N4",
+    markerImage: restaurantMarker,
+  },
+  {
+    name: "Second Cup",
+    coordinate: { latitude: 45.4992233, longitude: -73.5735757 },
+    address: "Rue Sainte-Catherine ST W Suite 1166 , Montréal, QC H3B 1K1",
+    markerImage: cafeMarker,
+  },
+  {
+    name: "Java U",
+    coordinate: { latitude: 45.4958331, longitude: -73.5791174 },
+    address: "1455 Guy St , Montréal, QC H3H 2L5",
+    markerImage: cafeMarker,
+  },
+];
 
 function HomeScreen({ asyncKey = "Campus" }) {
   const loyolaPostalCode = process.env.EXPO_PUBLIC_LOYOLA_POSTAL_CODE;
   const sgwPostalCode = process.env.EXPO_PUBLIC_SGW_POSTAL_CODE;
 
   const location = useContext(LocationContext);
-  const { toggleModal, setModalData } = useContext(ModalContext); // Access setModalData
+  const { toggleModal, setModalData } = useContext(ModalContext);
 
   const [postalCode, setPostalCode] = useState(null);
   const [coordinates, setCoordinates] = useState(null);
   const [error, setError] = useState("");
   const [selectedLocation, setSelectedLocation] = useState(null);
   const [, setMapRegion] = useState(null);
-  const borderColor = "#912338"; // Initial border color (red)
+  const borderColor = "#912338";
   const mapRef = useRef(null);
   const toggleModalTime = "10000";
path: ConcordiaMaps/screen/HomeScreen.js
user: login: shanvinluo
body: This is storing a number as a string, which could cause type conversion issues. It should be stored as a number: const TOGGLE_MODAL_TIMEOUT = 10000;. This follows both "Extract Constant" and "Replace Magic Literal" refactorings.

created_at: 2025-03-19T00:40:13Z
author_association: COLLABORATOR
diff_hunk: @@ -113,27 +162,32 @@ function HomeScreen({ asyncKey = "Campus" }) {
     }, 100);
   };
 
-  // Function to handle marker press and pass data to the modal
   const handleMarkerPress = (building) => {
     setModalData({
       name: building.name,
-      coordinate: building.coordinat,
+      coordinate: building.coordinate,
       address: building.address,
       fullBuildingName: building.fullBuildingName,
-    }); // Update modalData
-    toggleModal(); // Show modal
+    });
+    toggleModal();
+  };
+
+  const handleOPIMarkerPress = (poi) => {
+    setSelectedOPI(poi);
+    setOpiPopupVisible(true);
   };
 
   const [modalState, setModalState] = useState(true);
   useEffect(() => {
path: ConcordiaMaps/screen/HomeScreen.js
user: login: shanvinluo
body: This effect contains a side effect that should be extracted to a separate function using the "Extract Function" refactoring pattern from the [Catalog](https://github.com/SOEN-390-pmzone/SOEN-_390-Q-QD/wiki/Refactoring-Template-for-QA). Also, the toggle time being parsed as a string could cause issues. Create a helper function like startModalTimeout().
created_at: 2025-03-19T00:41:37Z
author_association: COLLABORATOR
diff_hunk: @@ -21,23 +20,73 @@ import {
 } from "../components/AsyncPersistence";
 import convertToCoordinates from "../components/convertToCoordinates";
 import PropTypes from "prop-types";
+import PopupOPI from "../components/PopupOPI"; // Import the new popup component
+
+// Marker image assets for Restaurant and Cafe
+const restaurantMarker = require("../assets/restoICON.png");
+const cafeMarker = require("../assets/cafeICON.png");
+const customMarkerImage = require("../assets/PinLogo.png");
+
+// Example data for PointsOfInterest
+const PointsOfInterest = [
path: ConcordiaMaps/screen/HomeScreen.js
user: login: shanvinluo
body: This data should be moved to a separate module following the "Move Function" refactoring pattern according to the [Catalog](https://github.com/SOEN-390-pmzone/SOEN-_390-Q-QD/wiki/Refactoring-Template-for-QA). Rename to follow JavaScript naming conventions (camelCase): pointsOfInterest instead of PointsOfInterest.
created_at: 2025-03-19T00:42:16Z
author_association: COLLABORATOR
diff_hunk: @@ -21,23 +20,73 @@ import {
 } from "../components/AsyncPersistence";
 import convertToCoordinates from "../components/convertToCoordinates";
 import PropTypes from "prop-types";
+import PopupOPI from "../components/PopupOPI"; // Import the new popup component
+
+// Marker image assets for Restaurant and Cafe
+const restaurantMarker = require("../assets/restoICON.png");
+const cafeMarker = require("../assets/cafeICON.png");
+const customMarkerImage = require("../assets/PinLogo.png");
+
+// Example data for PointsOfInterest
+const PointsOfInterest = [
path: ConcordiaMaps/screen/HomeScreen.js
user: login: Atai-Askarov
body: Create a folder called constants. The indoor navigation branch has it. The plan is to store equivalent information there.
created_at: 2025-03-19T21:06:14Z
author_association: COLLABORATOR
diff_hunk: @@ -191,6 +245,20 @@ function HomeScreen({ asyncKey = "Campus" }) {
                 />
               </Marker>
             ))}
+            {PointsOfInterest.map((poi) => (
path: ConcordiaMaps/screen/HomeScreen.js
user: login: Atai-Askarov
body: This block of code could be isolated. I isolated the markers for concordia buildings and put them into a separate file called MapMarkers. You can view my implementation on the branch LinkingGetDirectionsButtonToGettingDirections.
created_at: 2025-03-19T21:08:40Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,51 @@
+import React from "react";
+import { Modal, View, Text, TouchableOpacity, Alert } from "react-native";
+import PropTypes from "prop-types";
+import styles from "../styles/DirectionBox.style"; // Use the same styles as the building popup
+
+const PopupOPI = ({ isVisible, data = {}, onClose }) => {
+  const { name = "Cafe/Restaurant Name", address = "Address not available" } =
+    data;
+
+  return (
+    <Modal
+      transparent
+      animationType="slide"
+      visible={isVisible}
+      onRequestClose={onClose}
+    >
+      <View style={styles.modalOverlay}>
+        <View style={styles.modalContainer}>
+          <Text style={styles.modalTitle}>{name}</Text>
+          <Text style={styles.modalText}>{address}</Text>
+
+          <View style={styles.buttonsContainer}>
+            <TouchableOpacity style={styles.closeButton} onPress={onClose}>
+              <Text style={styles.closeButtonText}>Close</Text>
+            </TouchableOpacity>
+
+            <TouchableOpacity
+              style={styles.getDirectionsButton}
+              onPress={() =>
+                Alert.alert("Get Directions", "Directions pressed")
path: ConcordiaMaps/components/PopupOPI.js
user: login: Atai-Askarov
body: While you are at it, you can connect the button to the GetDirections component.  I've done something similar on my branch LinkingGetDirectionsButtonToGettingDirections. I think my implementation will be very helpful in this case
created_at: 2025-03-19T21:10:54Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,51 @@
+import React from "react";
+import { Modal, View, Text, TouchableOpacity, Alert } from "react-native";
+import PropTypes from "prop-types";
+import styles from "../styles/DirectionBox.style"; // Use the same styles as the building popup
+
+const PopupOPI = ({ isVisible, data = {}, onClose }) => {
+  const { name = "Cafe/Restaurant Name", address = "Address not available" } =
+    data;
+
+  return (
+    <Modal
+      transparent
+      animationType="slide"
+      visible={isVisible}
+      onRequestClose={onClose}
+    >
+      <View style={styles.modalOverlay}>
+        <View style={styles.modalContainer}>
+          <Text style={styles.modalTitle}>{name}</Text>
+          <Text style={styles.modalText}>{address}</Text>
+
+          <View style={styles.buttonsContainer}>
+            <TouchableOpacity style={styles.closeButton} onPress={onClose}>
+              <Text style={styles.closeButtonText}>Close</Text>
+            </TouchableOpacity>
+
+            <TouchableOpacity
+              style={styles.getDirectionsButton}
+              onPress={() =>
+                Alert.alert("Get Directions", "Directions pressed")
path: ConcordiaMaps/components/PopupOPI.js
user: login: shanvinluo
body: This issue will be resolved once merged with the develop branch. Please refer to the #180 PR which is "Linking get directions button to getting directions". Since the Popup for the Outdoor Points of directions reuse features from the Popups for our Concordia buildings, the merge will automatically fix this issue. 
created_at: 2025-03-23T06:49:57Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,52 @@
+import React from "react";
+import { Modal, View, Text, TouchableOpacity, Alert } from "react-native";
+import PropTypes from "prop-types";
+import styles from "../styles/DirectionBox.style"; // Use the same styles as the building popup
+
+const PopupOPI = ({ isVisible, data, onClose }) => {
+  const { name, address } = data;
+
+  return (
+    <Modal
+      transparent
+      animationType="slide"
+      visible={isVisible}
+      onRequestClose={onClose}
+    >
+      <View style={styles.modalOverlay}>
+        <View style={styles.modalContainer}>
+          <Text style={styles.modalTitle}>{name || "Cafe/Restaurant Name"}</Text>
+          <Text style={styles.modalText}>
+            {address || "Address not available"}
+          </Text>
+
+          <View style={styles.buttonsContainer}>
+            <TouchableOpacity style={styles.closeButton} onPress={onClose}>
+              <Text style={styles.closeButtonText}>Close</Text>
+            </TouchableOpacity>
+
+            <TouchableOpacity
+              style={styles.getDirectionsButton}
+              onPress={() =>
path: ConcordiaMaps/components/PopupPOI.js
user: login: shanvinluo
body: This issue will be resolved once merged with the develop branch. Please refer to the #180 PR which is "Linking get directions button to getting directions". Since the Popup for the Outdoor Points of directions reuse features from the Popups for our Concordia buildings, the merge will automatically fix this issue. 
created_at: 2025-03-23T06:50:31Z
author_association: COLLABORATOR
diff_hunk: @@ -191,6 +245,20 @@ function HomeScreen({ asyncKey = "Campus" }) {
                 />
               </Marker>
             ))}
+            {PointsOfInterest.map((poi) => (
path: ConcordiaMaps/screen/HomeScreen.js
user: login: shanvinluo
body: I did so in the following commit: (https://github.com/SOEN-390-pmzone/SOEN-_390-Q-QD/pull/166/commits/1a63d47c45c14312edcff146780a73a13de13519).  This data was moved to a separate module following the "Move Function" refactoring pattern according to the [Catalog](https://github.com/SOEN-390-pmzone/SOEN-_390-Q-QD/wiki/Refactoring-Template-for-QA). Please review once again.
created_at: 2025-03-23T07:24:19Z
author_association: COLLABORATOR
diff_hunk: @@ -21,23 +20,73 @@ import {
 } from "../components/AsyncPersistence";
 import convertToCoordinates from "../components/convertToCoordinates";
 import PropTypes from "prop-types";
+import PopupOPI from "../components/PopupOPI"; // Import the new popup component
+
+// Marker image assets for Restaurant and Cafe
+const restaurantMarker = require("../assets/restoICON.png");
+const cafeMarker = require("../assets/cafeICON.png");
+const customMarkerImage = require("../assets/PinLogo.png");
+
+// Example data for PointsOfInterest
+const PointsOfInterest = [
path: ConcordiaMaps/screen/HomeScreen.js
user: login: shanvinluo
body: I did so in the following commit: (https://github.com/SOEN-390-pmzone/SOEN-_390-Q-QD/pull/166/commits/1a63d47c45c14312edcff146780a73a13de13519).  This data was moved to a separate module following the "Move Function" refactoring pattern according to the [Catalog](https://github.com/SOEN-390-pmzone/SOEN-_390-Q-QD/wiki/Refactoring-Template-for-QA). Please review once again. 

I was also mistaken with the camelCase naming convention since React requires exported const to start with a capital letter
created_at: 2025-03-23T07:26:26Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,52 @@
+import React from "react";
+import { Modal, View, Text, TouchableOpacity, Alert } from "react-native";
+import PropTypes from "prop-types";
+import styles from "../styles/DirectionBox.style"; // Use the same styles as the building popup
+
+const PopupOPI = ({ isVisible, data, onClose }) => {
+  const { name, address } = data;
+
+  return (
+    <Modal
+      transparent
+      animationType="slide"
+      visible={isVisible}
+      onRequestClose={onClose}
+    >
+      <View style={styles.modalOverlay}>
+        <View style={styles.modalContainer}>
+          <Text style={styles.modalTitle}>{name || "Cafe/Restaurant Name"}</Text>
path: ConcordiaMaps/components/PopupPOI.js
user: login: shanvinluo
body: I resolved it in the following commit: (https://github.com/SOEN-390-pmzone/SOEN-_390-Q-QD/pull/166/commits/1a63d47c45c14312edcff146780a73a13de13519).  I moved them into constants like DEFAULT_NAME and DEFAULT_ADDRESS to improve maintainability. This follows the "Replace Magic Literal" refactoring from the [Catalog](https://github.com/SOEN-390-pmzone/SOEN-_390-Q-QD/wiki/Refactoring-Template-for-QA). 
Please review once again. 
created_at: 2025-03-23T07:29:26Z
author_association: COLLABORATOR
diff_hunk: @@ -21,23 +20,73 @@ import {
 } from "../components/AsyncPersistence";
 import convertToCoordinates from "../components/convertToCoordinates";
 import PropTypes from "prop-types";
+import PopupOPI from "../components/PopupOPI"; // Import the new popup component
+
+// Marker image assets for Restaurant and Cafe
+const restaurantMarker = require("../assets/restoICON.png");
+const cafeMarker = require("../assets/cafeICON.png");
+const customMarkerImage = require("../assets/PinLogo.png");
+
+// Example data for PointsOfInterest
+const PointsOfInterest = [
+  {
+    name: "Poulet Rouge",
+    coordinate: { latitude: 45.4947454, longitude: -73.5783503 },
+    address: "1623 Rue Sainte-Catherine, Montréal, QC H3H 1L8",
+    markerImage: restaurantMarker,
+  },
+  {
+    name: "Marche Newon",
+    coordinate: { latitude: 45.4943362, longitude: -73.5785438 },
+    address: "1616 Rue Sainte-Catherine 302 unit, Montréal, QC H3H 1L7",
+    markerImage: restaurantMarker,
+  },
+  {
+    name: "Tim Horton's",
+    coordinate: { latitude: 45.4948692, longitude: -73.5782373 },
+    address: "1611 Rue Sainte-Catherine , Montréal, QC H3H 1L8",
+    markerImage: cafeMarker,
+  },
+  {
+    name: "Subway",
+    coordinate: { latitude: 45.49601, longitude: -73.5801927 },
+    address: "2144 guy st, Montréal, QC H3H 2N4",
+    markerImage: restaurantMarker,
+  },
+  {
+    name: "Second Cup",
+    coordinate: { latitude: 45.4992233, longitude: -73.5735757 },
+    address: "Rue Sainte-Catherine ST W Suite 1166 , Montréal, QC H3B 1K1",
+    markerImage: cafeMarker,
+  },
+  {
+    name: "Java U",
+    coordinate: { latitude: 45.4958331, longitude: -73.5791174 },
+    address: "1455 Guy St , Montréal, QC H3H 2L5",
+    markerImage: cafeMarker,
+  },
+];
 
 function HomeScreen({ asyncKey = "Campus" }) {
   const loyolaPostalCode = process.env.EXPO_PUBLIC_LOYOLA_POSTAL_CODE;
   const sgwPostalCode = process.env.EXPO_PUBLIC_SGW_POSTAL_CODE;
 
   const location = useContext(LocationContext);
-  const { toggleModal, setModalData } = useContext(ModalContext); // Access setModalData
+  const { toggleModal, setModalData } = useContext(ModalContext);
 
   const [postalCode, setPostalCode] = useState(null);
   const [coordinates, setCoordinates] = useState(null);
   const [error, setError] = useState("");
   const [selectedLocation, setSelectedLocation] = useState(null);
   const [, setMapRegion] = useState(null);
-  const borderColor = "#912338"; // Initial border color (red)
+  const borderColor = "#912338";
   const mapRef = useRef(null);
   const toggleModalTime = "10000";
path: ConcordiaMaps/screen/HomeScreen.js
user: login: shanvinluo
body: I did so in the following commit: (https://github.com/SOEN-390-pmzone/SOEN-_390-Q-QD/pull/166/commits/1a63d47c45c14312edcff146780a73a13de13519).   The string number was refactored and stored as a number: const TOGGLE_MODAL_TIMEOUT = 10000;. This follows both "Extract Constant" and "Replace Magic Literal" refactoring patterns according to the [Catalog](https://github.com/SOEN-390-pmzone/SOEN-_390-Q-QD/wiki/Refactoring-Template-for-QA). Please review once again.
created_at: 2025-03-23T07:40:35Z
author_association: COLLABORATOR
diff_hunk: @@ -113,27 +162,32 @@ function HomeScreen({ asyncKey = "Campus" }) {
     }, 100);
   };
 
-  // Function to handle marker press and pass data to the modal
   const handleMarkerPress = (building) => {
     setModalData({
       name: building.name,
-      coordinate: building.coordinat,
+      coordinate: building.coordinate,
       address: building.address,
       fullBuildingName: building.fullBuildingName,
-    }); // Update modalData
-    toggleModal(); // Show modal
+    });
+    toggleModal();
+  };
+
+  const handleOPIMarkerPress = (poi) => {
+    setSelectedOPI(poi);
+    setOpiPopupVisible(true);
   };
 
   const [modalState, setModalState] = useState(true);
   useEffect(() => {
path: ConcordiaMaps/screen/HomeScreen.js
user: login: shanvinluo
body: I reviewed it in the following commit: (https://github.com/SOEN-390-pmzone/SOEN-_390-Q-QD/pull/166/commits/1a63d47c45c14312edcff146780a73a13de13519).   It turns out this change is unneeded since we don't need the modal to close after a certain amount of time, so we can scrap this comment since it was already resolved. Please review once again.
created_at: 2025-03-23T07:41:36Z
author_association: COLLABORATOR

PR comments:
user: login: Mike67911
created_at: 2025-03-13T03:31:13Z
body: It works on my end on IOS. Can you please add more points of interests near the Loyola Campus.
author_association: COLLABORATOR
user: login: soulinaaa
created_at: 2025-03-13T03:32:20Z
body: > It works on my end on IOS. Can you please add more points of interests near the Loyola Campus.

ok got it !
author_association: COLLABORATOR
user: login: shanvinluo
created_at: 2025-03-13T08:10:42Z
body: Tests Fail currently. I'll add tests. Works on my end though. Also seems that there's some redundancy in the code. Also noticed that Coordinates are in HomeScreen? Please fix that
author_association: COLLABORATOR
user: login: shanvinluo
created_at: 2025-03-13T08:25:22Z
body: Fixed duplication, new tests needed though
author_association: COLLABORATOR
user: login: shanvinluo
created_at: 2025-03-13T09:34:16Z
body: All checks pass, please just remove coordinates from homescreen
author_association: COLLABORATOR
user: login: shanvinluo
created_at: 2025-03-19T00:44:36Z
body: After the latest commit, I thoroughly tested all the implemented features to ensure they are working as expected:

- POI Marker Rendering: Verified that all outdoor points of interest (cafes, restaurants, etc.) are correctly displayed on the map with their respective custom markers. Tested different areas to confirm accurate marker placement.
- Popup Functionality: Clicked on multiple POI markers to ensure the detailed popups display the correct name, address, and the option to get directions. Confirmed the responsiveness and smooth user experience during interactions.

Everything is functioning well, and the features provide a seamless experience in line with the indoor navigation functionality. Great work on this implementation!
author_association: COLLABORATOR
user: login: sonarqubecloud[bot]
created_at: 2025-03-23T08:28:09Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=SOEN-390-pmzone_SOEN-_390-Q-QD&pullRequest=166) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=SOEN-390-pmzone_SOEN-_390-Q-QD&pullRequest=166&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=SOEN-390-pmzone_SOEN-_390-Q-QD&pullRequest=166&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=SOEN-390-pmzone_SOEN-_390-Q-QD&pullRequest=166&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [80.6% Coverage on New Code](https://sonarcloud.io/component_measures?id=SOEN-390-pmzone_SOEN-_390-Q-QD&pullRequest=166&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=SOEN-390-pmzone_SOEN-_390-Q-QD&pullRequest=166&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=SOEN-390-pmzone_SOEN-_390-Q-QD&pullRequest=166)


author_association: NONE