Parcourir la source

finished reporting feature

aidan il y a 1 an
Parent
commit
a2bf40e672

+ 29 - 3
src/components/Map/MainMapComponent/OutdoorMap.tsx

@@ -65,6 +65,7 @@ interface OutdoorMapProps {
     applyFilters: (landmarks: Landmark[]) => Landmark[]
     promptAddLandmark: (longitude: number, latitude: number) => void
     setMarkerWindowPosition: (point: {x: number, y: number}) => void
+    getLmUri: (uri: String) => void
 }
 
 const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
@@ -72,6 +73,7 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
     const {setAlert} = useAuth()
     const mapStateOutdoor = useOutdoorMapState()
     const mapState = useMapState()
+    let lmUri = new String()
 
     const mapNavIndex = useNavigationState(state => state)
 
@@ -218,6 +220,24 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
         }
     }
 
+    const delay = ms => new Promise(res => setTimeout(res, ms));
+    
+    /**
+     * Method that takes a screenshot of the outdoor map
+     */
+    const takeSnapshot = async() => {
+        await delay(500);  // Wait for screen to center on landmark
+        let snapshot = mapStateOutdoor.mapRef.current.takeSnapshot({
+            format: 'jpg',
+            quality: 1,
+            result: 'base64',
+        });
+      
+      snapshot.then((uri) => {
+        lmUri = uri;  // Store the uri
+    });
+    }
+
     return (
         <TouchableWithoutFeedback>
             <>
@@ -278,9 +298,15 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
                             return (
                                 <Marker
                                     tracksViewChanges={trackChanges}
-                                    onPress={e => props.focusLandmark(landmark, e)}
-                                    key={landmark.id}
-                                    coordinate={{ latitude: landmark.latitude as number, longitude: landmark.longitude as number }} >
+                                    coordinate={{ latitude: landmark.latitude as number, longitude: landmark.longitude as number }}
+                                    onPress={async e => {
+                                        e.persist()
+                                        props.focusLandmark(landmark, e)
+                                        await takeSnapshot();
+                                        await delay(500)
+                                        props.getLmUri(lmUri)
+                                    }}
+                                    key={landmark.id} >
                                     {landmark.landmark_type ? <Image style={{ height: 35, width: 25, zIndex: 10 }} source={lmTypes[landmark.landmark_type]?.image} /> : null}
                                 </Marker>)
                         }

+ 4 - 1
src/components/Map/MainMapComponent/useMapState.ts

@@ -57,6 +57,8 @@ export const useMapState = () => {
 
     const [place, setPlace] = useState<string>("Outdoor")
 
+    const [uri, setUri] = useState<String>('')
+
     return { 
         onlyOwned, toggleOnlyOwned,
         lmFilteredTypes, setLmTypeFilter,
@@ -67,7 +69,8 @@ export const useMapState = () => {
         selectedLandmarkId, setSelectedLandmarkId,
         newLandmark, setNewLandmark,
         lmAddVisible, toggleLmAdd,
-        place, setPlace
+        place, setPlace,
+        uri, setUri
      }
 }
 

+ 82 - 20
src/components/Map/Panels/LandmarkDetailsPanel/CommentView.tsx

@@ -5,18 +5,22 @@
  * <dev@clicknpush.ca>, January 2022
  */
 
+import React from "react"
 import { FontAwesome } from "@expo/vector-icons"
 import { format, parseISO } from "date-fns"
-import React from "react"
-import { TouchableOpacity, View, Text } from "react-native"
-import { LMComment } from "../../../../data/comments"
+import { TouchableOpacity, View, Text, StyleSheet, ImageSourcePropType } from "react-native"
 import { useAuth } from "../../../../data/Auth/AuthContext"
 import { Report } from "./Report"
+import { useReportComment, LMComment, useGetLandmark } from "../../../../data/comments"
+import ViewShot, { captureRef } from "react-native-view-shot"
+import { Svg, Image as ImageSVG } from "react-native-svg"
+import IndoorFloor from "../../IndoorFloor"
+import { lmTypes } from "../../../../utils/GlobalUtils"
 
 /**
  * Props for the {@link Comment} component.
  */
- export interface CommentProps {
+export interface CommentProps {
     /**
      * The [comment]{@link LMComment} object being displayed by the {@link Comment} component.
      */
@@ -28,36 +32,94 @@ import { Report } from "./Report"
     focusComment: (id: string) => void
     startEditingComment: (comment: LMComment) => void
     deleteComment: (id: string) => void
+    uri: String
 }
 
 /**
  * Component that displays a {@link LMComment} object in a clean format.
  * @component
  */
- export const CommentView: React.FC<CommentProps> = ({comment, selected, focusComment: selectComment, startEditingComment: startEditingComment, deleteComment}) => {
-    const {userId} = useAuth()
+export const CommentView: React.FC<CommentProps> = ({ comment, selected, focusComment: selectComment, startEditingComment: startEditingComment, deleteComment, uri }) => {
+    const { userId } = useAuth()
+    const getLandmarkMutation = useGetLandmark(comment.landmark)
+    const landmark = getLandmarkMutation.data.landmark
+    const reportCommentMutation = useReportComment()
+
+    const capture: any = React.createRef();
+    const imgWidth = 346
+    const imgHeight = 448
+    const imageDim = 25
+    
+    /**
+     * Calls the {@linkcode reportComment} mutation from the {@link useReportComment} hook.
+     */
+    const reportComment = async (comment, reasons, extraComments) => {
+        if (landmark.floor !== null) {  // Indoor landmark screenshot
+            const uri = await captureRef(capture, {
+            format: "jpg",
+            quality: 1,
+            result: 'base64'
+            })
+            comment['indoorLmLocImg'] = uri
+        }
+        else {  // Outdoor landmark screenshot
+            console.log('outdoor comment reported')
+            comment['indoorLmLocImg'] = uri
+        }
+        comment['reasons'] = reasons
+        comment['isLandmark'] = false
+        comment['extraComments'] = extraComments
+        comment['landmarkInfo'] = landmark
+
+        await reportCommentMutation.mutateAsync(comment);
+    }
 
     return (
-        <TouchableOpacity style={[{paddingHorizontal: 10}, selected ? {backgroundColor: '#E8E8E8'}: null]} onPress={() => selectComment(comment.id)}>
-            <View style={{paddingTop: 10,  flexDirection: 'row', justifyContent: 'space-between'}}>
-                <Text style={{fontWeight: 'bold'}}>{comment.poster_name}:</Text>
-                <Text style={{fontSize: 12, color: 'gray'}}>{format(parseISO(comment.timestamp.toString()), "MMMM do, yyyy h:mma")}</Text>
+        <TouchableOpacity style={[{ paddingHorizontal: 10 }, selected ? { backgroundColor: '#E8E8E8' } : null]} onPress={() => selectComment(comment.id)}>
+            <View style={{ paddingTop: 10, flexDirection: 'row', justifyContent: 'space-between' }}>
+                <Text style={{ fontWeight: 'bold' }}>{comment.poster_name}:</Text>
+                <Text style={{ fontSize: 12, color: 'gray' }}>{format(parseISO(comment.timestamp.toString()), "MMMM do, yyyy h:mma")}</Text>
             </View>
-            <View style={{marginVertical: 10}}>
-                <Text style={{paddingBottom: 10}} >{comment.content}</Text>
-                <View style={{flexDirection: 'row', alignSelf: 'flex-end'}}>
-                    {comment.edited ? <Text style={{color: 'grey', alignSelf: 'flex-end'}}>Edited</Text> : null}
+            <View style={{ marginVertical: 10 }}>
+                <Text style={{ paddingBottom: 10 }} >{comment.content}</Text>
+                <View style={{ flexDirection: 'row', alignSelf: 'flex-end' }}>
+                    {comment.edited ? <Text style={{ color: 'grey', alignSelf: 'flex-end' }}>Edited</Text> : null}
                     {selected ?
                         comment.poster == userId ?
-                            <View style={{marginTop: 10, flexDirection: 'row', alignSelf: 'flex-end'}}>
-                                <FontAwesome size={25} name="edit" style={{paddingTop: 1, marginLeft: 20}} onPress={() => startEditingComment(comment)}/>
-                                <FontAwesome color="red" size={25} style={{marginLeft: 15}} name="trash" onPress={() => deleteComment(comment.id)}/>
+                            <View style={{ marginTop: 10, flexDirection: 'row', alignSelf: 'flex-end' }}>
+                                <FontAwesome size={25} name="edit" style={{ paddingTop: 1, marginLeft: 20 }} onPress={() => startEditingComment(comment)} />
+                                <FontAwesome color="red" size={25} style={{ marginLeft: 15 }} name="trash" onPress={() => deleteComment(comment.id)} />
                             </View> :
-                            <View style={{marginTop: 10, flexDirection: 'row', alignSelf: 'flex-end'}}>
-                                <Report isLandmark={false} comment = {comment}/>
+                            <View style={{ marginTop: 10, flexDirection: 'row', alignSelf: 'flex-end' }}>
+                                {userId ?  // Only show the report button if the user is logged in
+                                    <Report isLandmark={false} comment={comment} reportComment={reportComment} />
+                                    : null}
                             </View>
                         : null}
                 </View>
+                <ViewShot style={{ width: imgWidth + 20, height: imgHeight + 20, position: 'absolute', right: -2000 }} ref={capture} >
+                    {landmark == null || landmark.floor == null || landmark.landmark_type == null ? <></> :
+                        <View style={styles.container}>
+                            <Svg>
+                                <IndoorFloor floorNum={landmark.floor} />
+                                <ImageSVG x={landmark.longitude * imgWidth - 3} y={landmark.latitude * imgHeight - 3} width={imageDim} height={imageDim} href={lmTypes[landmark.landmark_type]['image'] as ImageSourcePropType} />
+                            </Svg>
+                        </View>
+                    }
+                </ViewShot>
             </View>
         </TouchableOpacity>
-    )}
+    )
+}
+
+const styles = StyleSheet.create({
+    container: {
+        aspectRatio: 8 / 10,  // (caters to portrait mode) (width is 80% the value of height dimension)
+        width: '100%',
+        height: '100%',
+        maxWidth: "100%",
+        maxHeight: "100%",
+        backgroundColor: "white",
+        padding: 10
+    }
+})

+ 2 - 1
src/components/Map/Panels/LandmarkDetailsPanel/CommentsContainer.tsx

@@ -34,6 +34,7 @@ interface CommentsContainerProps {
     keyboardOpened: boolean
     authNavigation: MainTabsNavigationProp
     toggleLmDetails: (state: boolean) => void
+    uri: String
 }
 
 /**
@@ -56,7 +57,7 @@ export const CommentsContainer: React.FC<CommentsContainerProps> = (props) => {
             <View>
                 {/* Some conditional rendering to make the borders look nice */}
                 {latestComment ? null : <View style={[selected || belowSelected ? {marginVertical: 0} : {marginHorizontal: 10}, {height: 1, borderBottomWidth: 1, borderColor: 'lightgray'}]}></View> }
-                <CommentView comment={item} selected={selected} focusComment={props.focusComment} startEditingComment={props.startEditingComment} deleteComment={props.deleteComment} />
+                <CommentView comment={item} selected={selected} focusComment={props.focusComment} startEditingComment={props.startEditingComment} deleteComment={props.deleteComment} uri={props.uri} />
             </View>
         );
     };

+ 3 - 1
src/components/Map/Panels/LandmarkDetailsPanel/DetailsBody.tsx

@@ -56,6 +56,7 @@ interface DetailsBodyProps {
     processingPhoto: boolean
     setProcessingPhoto: (state: boolean) => void
     authNavigation: MainTabsNavigationProp
+    uri: String
 }
 
 /**
@@ -210,7 +211,8 @@ export const DetailsBody: React.FC<DetailsBodyProps> = (props) => {
                 newCommentId={props.newCommentId}
                 editComment={props.editComment}
                 startEditingComment={props.startEditingComment}
-                deleteComment={props.deleteComment} /> : null}
+                deleteComment={props.deleteComment}
+                uri={props.uri} /> : null}
             {!props.editingEnabled && !props.keyboardOpened ?
             <LandmarkPhotos 
                 profileId={props.profileId}

+ 57 - 18
src/components/Map/Panels/LandmarkDetailsPanel/DetailsHeader.tsx

@@ -5,14 +5,18 @@
  * <dev@clicknpush.ca>, January 2022
  */
 
+import React from "react";
 import { FontAwesome } from "@expo/vector-icons";
-import React, { useState } from "react";
-import { Button, Alert, StyleSheet, Text, TextInput, TouchableOpacity, View, Dimensions, Platform, Pressable } from "react-native";
+import { StyleSheet, Text, TouchableOpacity, View, ImageSourcePropType, SafeAreaView, FlatList } from "react-native";
+import { Svg, Image as ImageSVG } from "react-native-svg";
+import ViewShot, { captureRef } from "react-native-view-shot";
 import { QueryStatus } from "react-query";
 import { useAuth } from "../../../../data/Auth/AuthContext";
 import { Landmark, useReportLandmark } from "../../../../data/landmarks";
 import { UserProfile } from "../../../../data/profiles";
 import { MainTabsNavigationProp } from "../../../../navigation/MainTabsNavigator";
+import { lmTypes } from "../../../../utils/GlobalUtils";
+import IndoorFloor from "../../IndoorFloor";
 import { Report } from "./Report";
 import TouchOpaq from './TouchOpaq';
 // import { TextInput } from "react-native-paper";
@@ -34,6 +38,7 @@ interface DetailsHeaderProps {
     authNavigation: MainTabsNavigationProp
     toggleLmDetails: (state: boolean) => void
     place: string
+    uri: String
 }
 
 /**
@@ -48,13 +53,35 @@ export const DetailsHeader: React.FC<DetailsHeaderProps> = (props) => {
             props.deletePhotoStatus == "loading"
     }
 
-    // const reportLandmarkMutation = useReportLandmark()
-    // /**
-    //  * Calls the {@linkcode rateLandmarkAsunc} mutation from the {@link useLandmarks} hook. If 1, the landmark will be upvoted. If -1, it will be downvoted
-    //  */
-    //  const reportLandmark = async () => {
-    //     await reportLandmarkMutation.mutateAsync();
-    // }
+    const reportLandmarkMutation = useReportLandmark()
+    const capture: any = React.createRef();
+    const imgWidth = 346
+    const imgHeight = 448
+    const imageDim = 25
+
+    /**
+     * Calls the {@linkcode reportLandmark} mutation from the {@link useReportLandmark} hook.
+     */
+    const reportLandmark = async (landmark, reasons, extraComments) => {
+        if (landmark.floor !== null) {  // Indoor landmark screenshot
+            console.log('indoor landmark reported')
+            const uri = await captureRef(capture, {
+                format: "jpg",
+                quality: 1,
+                result: 'base64'
+            })
+            landmark['indoorLmLocImg'] = uri
+        }
+        else {  // Outdoor landmark screenshot
+            console.log('outdoor landmark reported')
+            landmark['indoorLmLocImg'] = props.uri
+        }
+        landmark['reasons'] = reasons
+        landmark['isLandmark'] = true
+        landmark['extraComments'] = extraComments
+
+        await reportLandmarkMutation.mutateAsync(landmark);
+    }
 
     const HeaderContent: React.FC = () => {
         // landmark is owned by user
@@ -146,14 +173,7 @@ export const DetailsHeader: React.FC<DetailsHeaderProps> = (props) => {
                             </View>
                         </TouchableOpacity>}
                     <View style={{ flexDirection: 'row' }}>
-                        <Report isLandmark = {true} landmark = {props.landmark} />
-                        {/* <Report isLandmark = {true} landmark = {props.landmark} reportLandmark = {reportLandmark}/> */}
-                            <TouchOpaq
-                                func={() => props.removeLandmark()}
-                                size={25}
-                                col={"white"}
-                                name={"trash"}
-                            />
+                        {anonUserId ? null : <Report isLandmark={true} landmark={props.landmark} reportLandmark={reportLandmark} />}
                         <TouchOpaq
                             func={() => props.toggleDetailsPanel(false)}
                             col={"white"}
@@ -161,7 +181,17 @@ export const DetailsHeader: React.FC<DetailsHeaderProps> = (props) => {
                             name={"close"}
                         />
                     </View>
-                </View>
+                    <ViewShot style={{ width: imgWidth + 20, height: imgHeight + 20, position: 'absolute', right: -2000 }} ref={capture} >
+                        {props.landmark == null || props.landmark.floor == null || props.landmark.landmark_type == null ? <></> :
+                            <View style={styles.container}>
+                                <Svg>
+                                    <IndoorFloor floorNum={props.landmark.floor} />
+                                    <ImageSVG x={props.landmark.longitude * imgWidth - 3} y={props.landmark.latitude * imgHeight - 3} width={imageDim} height={imageDim} href={lmTypes[props.landmark.landmark_type]['image'] as ImageSourcePropType} />
+                                </Svg>
+                            </View>
+                        }
+                    </ViewShot>
+                </View >
             )
         }
     }
@@ -186,4 +216,13 @@ const styles = StyleSheet.create({
         paddingVertical: 10,
         paddingHorizontal: 20
     },
+    container: {
+        aspectRatio: 8 / 10,  // (caters to portrait mode) (width is 80% the value of height dimension)
+        width: '100%',
+        height: '100%',
+        maxWidth: "100%",
+        maxHeight: "100%",
+        backgroundColor: "white",
+        padding: 10
+    }
 })

+ 7 - 3
src/components/Map/Panels/LandmarkDetailsPanel/LandmarkDetails.tsx

@@ -48,6 +48,7 @@ export interface LandmarkDetailsProps {
     authNavigation: MainTabsNavigationProp
     markerWindowPosition: {x: number, y: number}
     place: string;
+    uri: String
 }
 
 /**
@@ -55,7 +56,7 @@ export interface LandmarkDetailsProps {
  * @component
  * @category Map
  */
-const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition, authNavigation, landmarkId, setLandmark, toggleDetailsPanel, setEditing, editingEnabled, visible, toggleLmDetails, place}) => {
+const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition, authNavigation, landmarkId, setLandmark, toggleDetailsPanel, setEditing, editingEnabled, visible, toggleLmDetails, place, uri}) => {
     const {userId, landmarkOwnedByUser} = useAuth()
 
     // /**
@@ -278,6 +279,7 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition,
         }
         else {
             setFocusedComment(id);
+            // console.log(uri.substring(uri.length-10, uri.length))
         }
     }
 
@@ -393,7 +395,8 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition,
                     removeLandmark={removeLandmark}
                     updatedLandmark={updatedLandmark}
                     profile={profile}
-                    place={place} />
+                    place={place}
+                    uri = {uri} />
                 <DetailsBody
                     authNavigation={authNavigation} 
                     setProcessingPhoto={setProcessingPhoto}
@@ -423,7 +426,8 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition,
                     deletePhotoStatus={deleteLandmarkPhotoMutation.status}
                     toggleLmDetails={toggleLmDetails}
                     addPhoto={addLandmarkPhotoMutation.mutateAsync}
-                    addPhotoStatus={addLandmarkPhotoMutation.status}/>
+                    addPhotoStatus={addLandmarkPhotoMutation.status}
+                    uri={uri}/>
                 </> :
                 <View style={{height: '100%', justifyContent: "space-evenly", alignItems: "center", marginHorizontal: 20}}>
                     <Text style={{color: 'white', fontSize: 20}}>{

+ 24 - 36
src/components/Map/Panels/LandmarkDetailsPanel/Report.tsx

@@ -19,51 +19,46 @@ interface ReportProps {
     landmark?: Landmark,
     comment?: LMComment
     isLandmark: boolean,
-    // reportLandmark: (string) => void
+    reportLandmark?: (landmark, reasons, extraComments) => void
+    reportComment?: (landmark, reasons, extraComments) => void
 }
 
 export const Report: React.FC<ReportProps> = (props) => {
-
     const [keyboardIsVisible, setKeyboardIsVisible] = useState(false)
+
     useEffect(() => {
+        let isMounted = true;
         const showListener = Keyboard.addListener("keyboardDidShow", () => {
-            setKeyboardIsVisible(true)
+            if (isMounted) setKeyboardIsVisible(true)
         })
         const hideListener = Keyboard.addListener("keyboardDidHide", () => {
-            setKeyboardIsVisible(false)
+            if (isMounted) setKeyboardIsVisible(false)
         })
 
         return () => {
             showListener.remove()
             hideListener.remove()
+            isMounted = false;
         }
     }, [])
 
-    const toggleModal = () => {
-        setModalVisible(!modalVisible)
+    const onSelectedReasonsChange = (selectedReasons) => {
+        setSelectedReasons(selectedReasons)
     }
 
+    /**
+     * Method that obtains the selected report reasons as strings and sends the appropriate report
+     */
     const sendReport = () => {
-        var report = props.isLandmark ?
-            "Report submitted for landmark '" + props.landmark?.title + "':\n\tID: " + props.landmark?.id
-            + "\n\tLandmark author: " + props.landmark?.user
-            + "\n\tCoordinates: " + props.landmark?.longitude + " " + props.landmark?.latitude
-            + "\n\tLandmark type: " + props.landmark?.landmark_type
-            :
-            "Report submitted for a comment on landmark " + props.comment?.landmark
-            + '\n\tComment: "' + props.comment?.content + '"'
-        report += "\nReason(s) for report: \n\t"
+        let reasonsToString = []
         for (let i = 0; i < selectedReasons.length; i++) {
-            var tempItem = reasons.find(item => item.value === selectedReasons[i]).label
-            console.log(tempItem)
-            report += tempItem + ((i == selectedReasons.length - 1) ? "\n" : "\n\t")
+            reasonsToString.push(reasons.find(item => item.value === selectedReasons[i]).label)
         }
-        text ? report += "Extra comments: " + text : null
-        console.log(report)
-        // props.reportLandmark(report)
+        props.isLandmark ?
+            props.reportLandmark(props.landmark, reasonsToString, text)
+            : props.reportComment(props.comment, reasonsToString, text)
         Alert.alert('Report submitted', 'Thank you for making The Atlas a safer place!')
         setModalVisible(!modalVisible)
-        // console.log((landmarkOwnedByUser(props.landmark)))
     }
 
     const [modalVisible, setModalVisible] = useState(false)
@@ -90,25 +85,18 @@ export const Report: React.FC<ReportProps> = (props) => {
             { value: 'djsjudksje', label: 'Impersonation' }
         ]
 
-    const onSelectedReasonsChange = (selectedReasons) => {
-        setSelectedReasons(selectedReasons)
-        for (let i = 0; i < selectedReasons.length; i++) {
-            var tempItem = reasons.find(item => item.value === selectedReasons[i])
-        }
-    }
-
     return (
         <View>
-            <TouchableOpacity onPress={toggleModal}>
+            <TouchableOpacity onPress={() => { setModalVisible(!modalVisible) }}>
                 <View>
-                    {props.isLandmark ?
+                    {props.isLandmark ?  // Adds clickable report button
                         <FontAwesome style={{ marginLeft: 5, marginTop: 2, marginRight: 10 }} color="white" size={25} name="flag" />
                         :
                         <FontAwesome style={{ paddingTop: 1, marginLeft: 20 }} color="red" size={25} name="flag" />
                     }
                 </View>
             </TouchableOpacity>
-            <Modal
+            <Modal  // Pop-up modal that contains the entire report interface
                 backdropTransitionOutTiming={0}
                 hideModalContentWhileAnimating={true}
                 isVisible={modalVisible}
@@ -127,7 +115,7 @@ export const Report: React.FC<ReportProps> = (props) => {
                         <View style={{ justifyContent: 'flex-start' }}>
                             <Text style={{ marginRight: 10, marginBottom: 5 }}>Reason(s) for reporting:</Text>
                             <View style={{ alignSelf: 'stretch', borderColor: "blue", borderWidth: 0, width: '100%', justifyContent: 'center' }}>
-                                <Select
+                                <Select  // Dropdown list to select any number of reasons for the report
                                     styleRowList={{ borderColor: "red", borderWidth: 0 }}
                                     textColor='black'
                                     itemTextColor='black'
@@ -141,18 +129,18 @@ export const Report: React.FC<ReportProps> = (props) => {
                                 </Select>
                             </View>
                         </View>
-                        <TextInput
+                        <TextInput  // Text box if the user wants to add extra comments to the report
                             style={styles.input}
                             onChangeText={onChangeText}
                             value={text}
-                            placeholder="Other comments..."
+                            placeholder="Optional extra feedback..."
                             keyboardType="default"
                         />
                     </View>
                     {!keyboardIsVisible && <View style={{ flexDirection: 'row' }}>
                         <Pressable
                             style={styles.cancel}
-                            onPress={toggleModal}>
+                            onPress={() => { setModalVisible(!modalVisible) }}>
                             <Text style={{ color: 'white', textAlign: 'center' }}>Cancel</Text>
                         </Pressable>
                         <Pressable

+ 53 - 0
src/data/comments.ts

@@ -8,6 +8,7 @@
 import { useMutation, useQuery, useQueryClient } from "react-query";
 import { API_URL } from "../utils/RequestUtils";
 import { useAuth } from "./Auth/AuthContext";
+import { Landmark } from "./landmarks";
 import { queryKeys } from "./query-keys";
 
 /**
@@ -156,4 +157,56 @@ export const useDeleteComment = () => {
         onSuccess: () => queryClient.invalidateQueries(queryKeys.getComments),  
         onError: () => queryClient.invalidateQueries(queryKeys.getComments),  
     })
+}
+
+export const useReportComment = () => {
+    const {sendApiRequestAsync} = useAuth()
+    const queryClient = useQueryClient();
+
+    const reportComment =  async (content: string) => {
+        if (content) {
+            const response = await sendApiRequestAsync({
+                axiosConfig:{
+                    method: 'POST',
+                    data: content,
+                    url: `/api/comments/report/`,
+                },
+                authorized: true,
+                errorMessage: 'Something went wrong when reporting a comment',
+                loggingCategory: 'COMMENTS'
+            });   
+            return response?.data;
+        }
+    }
+
+    return useMutation(reportComment, {
+        onSuccess: () => queryClient.invalidateQueries(queryKeys.getComments),  
+        onError: () => queryClient.invalidateQueries(queryKeys.getComments),  
+    })
+}
+
+export const useGetLandmark = (landmarkId: string) => {
+    const {sendApiRequestAsync, userId} = useAuth()
+    const queryClient = useQueryClient();
+
+     const getLandmark = async (landmarkId?: string) => {
+        if (landmarkId) {
+            const response = await sendApiRequestAsync({
+                axiosConfig: {
+                    method: 'GET',
+                    url: `/api/landmark/${landmarkId}/`,
+                },
+                authorized: !!userId,
+                errorMessage: 'Something went wrong when retrieving the parent landmark',
+                loggingCategory: 'LANDMARKS'
+            });   
+            return response?.data 
+        }
+    }
+
+    return useQuery<{landmark: Landmark, ratedByUser: boolean}, Error>([queryKeys.getLandmark, landmarkId], () => getLandmark(landmarkId), {
+        placeholderData: () => queryClient.getQueryData(queryKeys.getLandmark),
+        refetchOnReconnect: true,
+        refetchOnMount: false
+    })
 }

+ 13 - 2
src/navigation/MapNavigator.tsx

@@ -111,6 +111,14 @@ const MapNavigator: React.FC = ({ }) => {
         }
     }
 
+    /**
+     * Separate function so that we can focus on the landmark first, then retrieve the uri from takeSnapshot() in OutdoorMap.tsx
+     * @param uri 
+     */
+    const getLmUri = (uri: String) => {
+        mapState.setUri(uri);
+    }
+
     /**
  * Triggered by long pressing on the map. 
  * Sets {@linkcode newLandmarkState} to a skeleton {@link Landmark} object that only contains the pressed coordinates.
@@ -145,7 +153,8 @@ const MapNavigator: React.FC = ({ }) => {
                             landmarks={landmarksQuery?.data}
                             selectedLandmarkId={mapState.selectedLandmarkId}
                             setSelectedLandmarkId={mapState.setSelectedLandmarkId}
-                            setMarkerWindowPosition={setMarkerWindowPosition} />}
+                            setMarkerWindowPosition={setMarkerWindowPosition}
+                            getLmUri={getLmUri} />}
                 </MapStackNavigator.Screen>
 
                 <MapStackNavigator.Screen name="Indoor" >
@@ -281,7 +290,9 @@ const MapNavigator: React.FC = ({ }) => {
                 setEditing={mapState.toggleLmDetailsEditing}
                 editingEnabled={mapState.lmDetailsEditing}
                 landmarkId={mapState.selectedLandmarkId}
-                place = {mapState.place} />
+                place = {mapState.place}
+                uri = {mapState.uri}
+                />
             <FilterPanel
                 visible={mapState.filterVisible}
                 toggleOnlyOwned={mapState.toggleOnlyOwned}

+ 3 - 3
src/utils/RequestUtils.ts

@@ -11,11 +11,11 @@
 //export const API_URL = 'http://192.168.3.81:8000'
 // export const API_URL = 'https://staging.clicknpush.ca'
 
-// export const API_URL = 'https://app.clicknpush.ca'
+export const API_URL = 'https://app.clicknpush.ca'
 // export const API_URL = 'http://192.168.1.106:8000' // Nathan
 //export const API_URL = 'http://192.168.1.64:8000'   // Chase
 //export const API_URL = 'http://192.168.0.22:8000'       // Eric
-export const API_URL = 'http://192.168.1.131:8000'  // Aidan surface
-// export const API_URL = 'http://'  // Aidan home
+// export const API_URL = 'http://192.168.1.131:8000'  // Aidan surface
+// export const API_URL = 'http://192.168.1.99:8000'  // Aidan home
 
 // export const API_URL = Config.API_URL