Pārlūkot izejas kodu

added preview pins, highlighting selected landmark, and ability to edit the location of indoor landmarks

aidan 1 gadu atpakaļ
vecāks
revīzija
2653dfa012

+ 1 - 1
app.json

@@ -46,7 +46,7 @@
     "runtimeVersion": "1.0.1",
     "extra": {
       "eas": {
-        "projectId": "082de7a5-d9b8-4c96-bd1b-5706cca20324"
+        "projectId": "4cba8a8d-b6b2-4e66-9113-3e228cce80fa"
       }
     }
   }

BIN
assets/preview.png


+ 3 - 3
eas.json

@@ -9,14 +9,14 @@
         "gradleCommand": ":app:assembleDebug"
       },
       "ios": {
-        "buildConfiguration": "Release",
+        "buildConfiguration": "Debug",
         "image": "macos-monterey-12.1-xcode-13.2"
       }
     },
     "preview": {
-      "distribution": "store",
+      "distribution": "internal",
       "ios": {
-        "buildConfiguration": "Release",
+        "buildConfiguration": "Debug",
         "image": "macos-monterey-12.1-xcode-13.2"
       }
     },

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 678 - 221
package-lock.json


+ 1 - 0
package.json

@@ -80,6 +80,7 @@
     "react-native-device-info": "^8.6.0",
     "react-native-dialog": "^9.2.0",
     "react-native-dotenv": "^3.3.0",
+    "react-native-draggable": "^3.3.0",
     "react-native-fast-image": "^8.5.11",
     "react-native-fs": "^2.19.0",
     "react-native-gesture-handler": "~1.10.2",

+ 324 - 213
src/components/Map/MainMapComponent/IndoorMap.tsx

@@ -1,235 +1,346 @@
 import { FontAwesome } from "@expo/vector-icons";
 import ReactNativeZoomableView from '@openspacelabs/react-native-zoomable-view/src/ReactNativeZoomableView';
 import React, { useEffect, useState } from 'react';
-import { ActivityIndicator, Alert, Dimensions, GestureResponderEvent, ImageSourcePropType, StatusBar, StyleSheet, View,  } from 'react-native';
+import { ActivityIndicator, Alert, Dimensions, GestureResponderEvent, ImageSourcePropType, StatusBar, StyleSheet, TouchableOpacity, View, Text } from 'react-native';
 import Picker from 'react-native-picker-select';
 import Toast from 'react-native-root-toast';
-import { Image, Rect, Svg, Text } from 'react-native-svg';
+import { Image, Svg, Text as SvgText } from 'react-native-svg';
+import { UseMutationResult } from "react-query";
 import { Landmark } from '../../../data/landmarks';
 import { MapStackNavigationProp } from "../../../navigation/MapNavigator";
 import { colors, lmTypesIndoor } from "../../../utils/GlobalUtils";
 import IndoorFloor from '../IndoorFloor';
 import ArrowButton from './ArrowButton';
-
+import mapStyles from "./Map.styles";
 
 
 interface IndoorMapProps {
-  navigation: MapStackNavigationProp
-  landmarks: Landmark[]
-  promptAddLandmark: (longitude?: number, latitude?: number, floor?: number, lmCount?: string, parent?: string) => void
-  focusLandmark: (landmark: Landmark) => void
-  applyFilter: (landmarks: Landmark[]) => Landmark[]
+	navigation: MapStackNavigationProp
+	landmarks: Landmark[]
+	promptAddLandmark: (longitude?: number, latitude?: number, floor?: number, lmCount?: string, parent?: string) => void
+	focusLandmark: (landmark: Landmark) => void
+	applyFilter: (landmarks: Landmark[]) => Landmark[]
+	togglePreviewPin: (state: boolean) => void
+	showPreviewPin: boolean
+	selectedLandmarkId: string
+	editLmLoc: boolean
+	toggleEditLmLoc: (state: boolean) => void
+	toggleLmDetails: (state: boolean) => void
+	editLandmarkMutation: UseMutationResult<any, unknown, Landmark, unknown>
+	updatedLandmark: Landmark,
+	setUpdatedLandmark: (landmark: Landmark) => void
 }
 
 
-
-
-const IndoorMap: React.FC<IndoorMapProps> = ({ landmarks, promptAddLandmark, focusLandmark, applyFilter }) => {
-  const [floor, setFloor] = useState(1);
-  const [showME, setShowME] = useState(false);
-  const [SVGdim, setSVGdim] = useState([1, 1])
-
-  const [localLandmarks, setLocalLandmarks] = useState<Landmark[]>([])
-
-  const imageDim = 0.05 * Dimensions.get("window").width;
-
-  const loadLandmarks = applyFilter(landmarks)?.map((item) => {
-    if (!lmTypesIndoor[item.landmark_type]) {
-      return null
-    }
-    if (item.floor == floor && item.groupCount != -1 && SVGdim[0] != 1 && SVGdim[1] != 1) {
-      return (
-        <View style={{position: "absolute"}}>
-          <Svg>
-            <Image
-              onPress={() => {
-                //console.log("item = " + item.longitude * SVGdim[0]);
-                focusLandmark(item);
-              }}
-              key={item.id}
-              x={item.longitude * SVGdim[0]}
-              y={item.latitude * SVGdim[1]}
-              width={imageDim}
-              height={imageDim}
-              href={lmTypesIndoor[item.landmark_type].image as ImageSourcePropType} />
-            {item.landmark_type == 30 && <Text
-              x={item.longitude * SVGdim[0] + imageDim * .4}
-              y={item.latitude * SVGdim[1] + imageDim * .5}
-              fontFamily="sans-serif-medium"
-              fontWeight="bold"
-              fontSize="8"
-              fill="black"
-              >{item.groupCount}</Text>}
-          </Svg>
-        </View>
-      )
-    }
-  })
-
-
-  function addLandmark(evt: GestureResponderEvent) {
-    if (evt != null) {
-      evt.persist()
-      Alert.alert("Are you sure you want to add a landmark here?", undefined,
-        [{ text: "Cancel" }
-          ,
-        {
-          text: "Confirm", onPress: async () => {
-            try {
-              await promptAddLandmark((evt.nativeEvent.locationX - imageDim / 2) / SVGdim[0], (evt.nativeEvent.locationY - imageDim / 2) / SVGdim[1], floor)
-            }
-            catch (err) {
-              console.error(err)
-              Toast.show("Please ensure finger is not moving when holding down on screen.", { duration: Toast.durations.LONG, })
-
-              // Alert.alert("An error has occured." , "Please ensure thumb is not moving when holding down on screen.")
-              // consider toast
-            }
-          }
-        }]
-      )
-    }
-  }
-
-  useEffect(() => {
-    // Alert.alert("useEffect has been triggered")
-    setTimeout(() => setShowME(true), 50);
-  })
-
-  const childToWeb = (child: any) => {
-    const { type, props } = child;
-    const name = type && type.displayName;
-    const webName = name && name[0].toLowerCase() + name.slice(1);
-    const Tag = webName ? webName : type;
-    return <Tag {...props}>{toWeb(props.children)}</Tag>;
-  };
-
-  const toWeb = (children: any) => React.Children.map(children, childToWeb);
-  function changer(num) {
-    setFloor(prevState => prevState + num)
-  }
-
-  // TODO: wire up promptaddlandmark, applyfilters, and focuslandmark methods passed from MapNavigator
-  return (
-    <View style={{ height: '100%', width: Dimensions.get("screen").width, backgroundColor: colors.red }}>
-
-      <StatusBar backgroundColor={colors.red} />
-      {/* <CustomModal /> */}
-      {/* <Text style={{ fontSize: 16, marginBottom: 5 }}>Please select a floor you would like to go to.</Text> */}
-
-      <View style={{ borderColor: "blue", borderWidth: 0, maxHeight: 50, flex: 1, flexDirection: "row", justifyContent: "center", }}>
-
-        {floor == 0 ? <ArrowButton num={0} fontAweIcon={""} /> : <ArrowButton num={-1} fontAweIcon={"chevron-left"} propEvent={() => changer(-1)} />}
-
-        <View style={{ flex: 5, height: 53.5, width: 200 }}>
-          <Picker
-            placeholder={{}}
-            value={floor}
-            style={{
-              inputIOSContainer: { width: '70%', justifyContent: 'center', alignSelf: 'center' },
-              inputAndroid: { textAlign: 'center', color: "white", },
-              inputIOS: { color: 'white', textAlign: 'center', height: '100%', alignSelf: 'center' },
-              iconContainer: { height: '100%', justifyContent: 'center', }
-            }}
-            Icon={() => <FontAwesome name="chevron-down" color='white' size={15} />}
-            onValueChange={(value) => {
-              setFloor(value)
-              setShowME(false)
-            }
-            }
-            items={[
-              { label: 'Basement', value: 0 },
-              { label: 'First Floor', value: 1 },
-              { label: 'Second Floor', value: 2 },
-              { label: 'Third Floor', value: 3 },
-              { label: 'Fourth Floor', value: 4 },
-              // { label: 'Fifth Floor', value: 5 },
-            ]}
-          />
-        </View>
-
-
-        {/* {floor == 5 ? arrowBut(0, "") : arrowBut(1, "chevron-right")} */}
-        {floor == 4 ? <ArrowButton num={0} fontAweIcon={""} /> : <ArrowButton num={1} fontAweIcon={"chevron-right"} propEvent={() => changer(1)} />}
-
-
-      </View>
-
-      <View style={{ flex: 1, alignItems: "center", height: '100%', width: '100%', borderColor: 'purple', borderWidth: 0 }}>
-        <View style={styles.container}>
-          {showME === false ?
-            <View style={{ display: 'flex', flexDirection: 'row', justifyContent: "center", }}>
-              <ActivityIndicator color={colors.red} size="large" />
-            </View> :
-
-            <ReactNativeZoomableView
-              panBoundaryPadding={100}
-              // bindToBorders={false}
-              bindToBorders={true}
-              zoomStep={2.8}
-              // initialZoom={2.2}
-              maxZoom={5}
-              minZoom={1}
-              initialOffsetY={5}
-              onLongPress={(event) => {
-                // serialize()
-                addLandmark(event)
-              }}
-              movementSensibility={3}
-              longPressDuration={200}
-            >
-
-              <Svg onLayout={event => {
-                setSVGdim([event.nativeEvent.layout.width, event.nativeEvent.layout.height])
-                const transformedLandmarks = localLandmarks.map(item => {
-                  return { ...item, coordx: item.longitude * event.nativeEvent.layout.width, coordy: item.latitude * event.nativeEvent.layout.height }
-                })
-                //console.log("*this is within onLayout* SVGdim values are " + SVGdim[0] + " AND " + SVGdim[1])
-                setLocalLandmarks(transformedLandmarks)
-              }}
-              >
-                {/* {firstTime == true ? undefined : loadCircles} */}
-                <IndoorFloor floorNum={floor} />
-                {loadLandmarks}
-              </Svg>
-            </ReactNativeZoomableView>
-          }
-
-        </View>
-      </View>
-    </View >
-  );
+const IndoorMap: React.FC<IndoorMapProps> = ({ landmarks, promptAddLandmark, focusLandmark, applyFilter, togglePreviewPin, showPreviewPin, selectedLandmarkId, editLmLoc, toggleEditLmLoc, toggleLmDetails, editLandmarkMutation, updatedLandmark, setUpdatedLandmark }) => {
+	const [floor, setFloor] = useState(1);
+	const [showME, setShowME] = useState(false);
+	const [SVGdim, setSVGdim] = useState([1, 1])
+	const [localLandmarks, setLocalLandmarks] = useState<Landmark[]>([])
+	const imageDim = 0.05 * Dimensions.get("window").width;
+
+	// Local state that tracks active editing coordinates every frame instead of continually updating the actual landmark, then when the edited landmark or preview pin is released, update the actual landmark with this state.
+	const [newCoordinates, setNewCoordinates] = useState({ latitude: 0, longitude: 0 })
+	const [activeLandmark, setActiveLandmark] = useState<String>("")  // Local state to track active/selected landmark, used to highlight the selected landmark because the timing doesn't work with selectedLandmarkId
+	const [lmOpacity, setLmOpacity] = useState<string>("1")
+
+	const editLandmark = async () => {
+		if (updatedLandmark) {
+			updatedLandmark['editingLocation'] = true  // Tells the back-end we are only editing the landmark's location, nothing else
+			toggleEditLmLoc(false)
+			toggleLmDetails(true)
+			await editLandmarkMutation.mutateAsync(updatedLandmark)
+		}
+	}
+
+	/**
+	 * When landmark location editing is started, find the selected landmark and set its coordinates as the active editing coordinates
+	 */
+	useEffect(() => {
+		if (editLmLoc) {
+			applyFilter(landmarks)?.map((item) => {
+				if (item.id == selectedLandmarkId) {
+					setNewCoordinates({ latitude: item.latitude, longitude: item.longitude })
+				}
+			})
+		}
+	}, [editLmLoc])
+
+
+	useEffect(() => {
+		if (editLmLoc) {
+			setLmOpacity("0.3")
+		}
+		else {
+			setLmOpacity("1")
+		}
+	}, [editLmLoc])
+    
+    /**
+     * When there is no longer a landmark selected, revert the opacity of all landmarks to 1.
+     */
+	useEffect(() => {
+		if (selectedLandmarkId == "") {
+			setLmOpacity("1")
+		}
+	}, [selectedLandmarkId])
+
+	// Method to allow users to pick the precise location of their new landmark.
+	const chooseLocation = async (e: GestureResponderEvent) => {
+		if (!showPreviewPin) {
+			console.log('opening preview pin')
+			setNewCoordinates({ latitude: (e.nativeEvent.locationY - imageDim / 2) / SVGdim[1], longitude: (e.nativeEvent.locationX - imageDim / 2) / SVGdim[0] })
+			togglePreviewPin(true)
+		}
+	}
+
+	const loadLandmarks = applyFilter(landmarks)?.map((item) => {
+		if (!lmTypesIndoor[item.landmark_type]) {
+			return null
+		}
+		if (editLmLoc && item.id == selectedLandmarkId) {  // Landmark's location is currently being edited
+			return (
+				<View style={{ position: "absolute" }}>
+					<Svg>
+						<Image
+							key={item.id}
+							onStartShouldSetResponder={() => true}
+							onResponderMove={(e) => { setNewCoordinates({ latitude: (e.nativeEvent.locationY - imageDim / 2) / SVGdim[1], longitude: (e.nativeEvent.locationX - imageDim / 2) / SVGdim[0] }) }}
+							onResponderRelease={() => setUpdatedLandmark({ ...item, longitude: newCoordinates.longitude, latitude: newCoordinates.latitude })}
+							x={newCoordinates.longitude * SVGdim[0]}
+							y={newCoordinates.latitude * SVGdim[1]}
+							width={imageDim}
+							height={imageDim}
+							href={lmTypesIndoor[item.landmark_type].image as ImageSourcePropType} />
+						{item.landmark_type == 30 && <SvgText
+							x={item.longitude * SVGdim[0] + imageDim * .4}
+							y={item.latitude * SVGdim[1] + imageDim * .5}
+							fontFamily="sans-serif-medium"
+							fontWeight="bold"
+							fontSize="8"
+							fill="black"
+						>{item.groupCount}</SvgText>}
+					</Svg>
+				</View>
+			)
+		}
+		if (item.floor == floor && item.groupCount != -1 && SVGdim[0] != 1 && SVGdim[1] != 1) {  // Regular indoor landmark
+			return (
+				<View style={{ position: "absolute" }}>
+					<Svg>
+						<Image
+							opacity={item.id == activeLandmark ? "1" : lmOpacity}
+							onPress={() => {
+								if (!editLmLoc) {
+									setActiveLandmark(item.id)
+									setLmOpacity("0.3")
+									focusLandmark(item)
+								}
+							}}
+							key={item.id}
+							x={item.longitude * SVGdim[0]}
+							y={item.latitude * SVGdim[1]}
+							width={imageDim}
+							height={imageDim}
+							href={lmTypesIndoor[item.landmark_type].image as ImageSourcePropType} />
+						{item.landmark_type == 30 && <SvgText
+							opacity={item.id == activeLandmark ? "1" : lmOpacity}
+							x={item.longitude * SVGdim[0] + imageDim * .4}
+							y={item.latitude * SVGdim[1] + imageDim * .5}
+							fontFamily="sans-serif-medium"
+							fontWeight="bold"
+							fontSize="8"
+							fill="black"
+						>{item.groupCount}</SvgText>}
+					</Svg>
+				</View>
+			)
+		}
+	})
+
+
+	async function addLandmark(evt: GestureResponderEvent) {
+		if (evt != null) {
+			if (!showPreviewPin && !editLmLoc) {
+				Alert.alert("Are you sure you want to add a landmark here?", undefined,
+					[{ text: "Cancel" },
+					{
+						text: "Confirm", onPress: async () => {
+							try {
+								chooseLocation(evt)
+								// promptAddLandmark((evt.nativeEvent.locationX - imageDim / 2) / SVGdim[0], (evt.nativeEvent.locationY - imageDim / 2) / SVGdim[1], floor)
+							}
+							catch (err) {
+								console.error(err)
+								Toast.show("Please ensure finger is not moving when holding down on screen.", { duration: Toast.durations.LONG, })
+							}
+						}
+					}]
+				)
+			}
+		}
+	}
+
+
+	useEffect(() => {
+		// Alert.alert("useEffect has been triggered")
+		setTimeout(() => setShowME(true), 50);
+	})
+
+
+	const childToWeb = (child: any) => {
+		const { type, props } = child;
+		const name = type && type.displayName;
+		const webName = name && name[0].toLowerCase() + name.slice(1);
+		const Tag = webName ? webName : type;
+		return <Tag {...props}>{toWeb(props.children)}</Tag>;
+	};
+
+
+	const toWeb = (children: any) => React.Children.map(children, childToWeb);
+	function changer(num) {
+		setFloor(prevState => prevState + num)
+	}
+
+
+	return (
+		<View style={{ height: '100%', width: Dimensions.get("screen").width, backgroundColor: colors.red }}>
+			<StatusBar backgroundColor={colors.red} />
+			{/* <CustomModal /> */}
+			{/* <Text style={{ fontSize: 16, marginBottom: 5 }}>Please select a floor you would like to go to.</Text> */}
+			<View style={{ borderColor: "blue", borderWidth: 0, maxHeight: 50, flex: 1, flexDirection: "row", justifyContent: "center", }}>
+				{floor == 0 ? <ArrowButton num={0} fontAweIcon={""} /> : <ArrowButton num={-1} fontAweIcon={"chevron-left"} propEvent={() => changer(-1)} />}
+				<View style={{ flex: 5, height: 53.5, width: 200 }}>
+					<Picker
+						placeholder={{}}
+						value={floor}
+						style={{
+							inputIOSContainer: { width: '70%', justifyContent: 'center', alignSelf: 'center' },
+							inputAndroid: { textAlign: 'center', color: "white", },
+							inputIOS: { color: 'white', textAlign: 'center', height: '100%', alignSelf: 'center' },
+							iconContainer: { height: '100%', justifyContent: 'center', }
+						}}
+						Icon={() => <FontAwesome name="chevron-down" color='white' size={15} />}
+						onValueChange={(value) => {
+							togglePreviewPin(false)
+							setFloor(value)
+							setShowME(false)
+						}
+						}
+						items={[
+							{ label: 'Basement', value: 0 },
+							{ label: 'First Floor', value: 1 },
+							{ label: 'Second Floor', value: 2 },
+							{ label: 'Third Floor', value: 3 },
+							{ label: 'Fourth Floor', value: 4 },
+							// { label: 'Fifth Floor', value: 5 }, // Uncomment this to enable the fifth floor
+						]}
+					/>
+				</View>
+				{/* {floor == 5 ? arrowBut(0, "") : arrowBut(1, "chevron-right")} */}
+				{floor == 4 ? <ArrowButton num={0} fontAweIcon={""} /> : <ArrowButton num={1} fontAweIcon={"chevron-right"} propEvent={() => changer(1)} />}
+			</View>
+			<View style={{ flex: 1, alignItems: "center", height: '100%', width: '100%', borderColor: 'purple', borderWidth: 0 }}>
+				<View style={styles.container}>
+					{showME === false ?
+						<View style={{ display: 'flex', flexDirection: 'row', justifyContent: "center", }}>
+							<ActivityIndicator color={colors.red} size="large" />
+						</View> :
+						<ReactNativeZoomableView
+							panBoundaryPadding={100}
+							// bindToBorders={false}
+							bindToBorders={true}
+							zoomStep={2.8}
+							// initialZoom={2.2}
+							maxZoom={5}
+							minZoom={1}
+							initialOffsetY={5}
+							movementSensibility={3}
+							longPressDuration={200}
+							onLongPress={async (e) => { e.persist(); addLandmark(e) }}
+						>
+							<Svg onLayout={event => {
+								setSVGdim([event.nativeEvent.layout.width, event.nativeEvent.layout.height])
+								const transformedLandmarks = localLandmarks.map(item => {
+									return { ...item, coordx: item.longitude * event.nativeEvent.layout.width, coordy: item.latitude * event.nativeEvent.layout.height }
+								})
+								//console.log("*this is within onLayout* SVGdim values are " + SVGdim[0] + " AND " + SVGdim[1])
+								setLocalLandmarks(transformedLandmarks)
+							}}>
+								<IndoorFloor floorNum={floor} />
+								{loadLandmarks}
+								{showPreviewPin &&
+									<View style={{ position: "absolute" }}>
+										<Svg>
+											<Image
+												onStartShouldSetResponder={() => true}
+												onResponderMove={(e) => { setNewCoordinates({ latitude: (e.nativeEvent.locationY - imageDim / 2) / SVGdim[1], longitude: (e.nativeEvent.locationX - imageDim / 2) / SVGdim[0] }) }}
+												x={newCoordinates.longitude * SVGdim[0]}
+												y={newCoordinates.latitude * SVGdim[1]}
+												width={imageDim}
+												height={imageDim}
+												href={require("../../../../assets/preview.png")} />
+										</Svg>
+									</View>
+								}
+							</Svg>
+						</ReactNativeZoomableView>
+					}
+					{showPreviewPin &&
+						<View style={[mapStyles.previewButtonContainer]}>
+							<TouchableOpacity style={[mapStyles.previewButtons, mapStyles.cancelPreview]} onPress={() => togglePreviewPin(false)}>
+								<Text style={{ color: "white" }}>Cancel</Text>
+							</TouchableOpacity>
+							<TouchableOpacity style={[mapStyles.previewButtons, mapStyles.confirmPreview]} onPress={() => promptAddLandmark(newCoordinates.longitude, newCoordinates.latitude, floor)}>
+								<Text>Add landmark here</Text>
+							</TouchableOpacity>
+						</View>
+					}
+					{editLmLoc &&
+						<View style={[mapStyles.previewButtonContainer]}>
+							<TouchableOpacity style={[mapStyles.previewButtons, mapStyles.cancelPreview]} onPress={() => { toggleEditLmLoc(false); toggleLmDetails(true) }}>
+								<Text style={{ color: "white" }}>Cancel</Text>
+							</TouchableOpacity>
+							<TouchableOpacity style={[mapStyles.previewButtons, mapStyles.confirmPreview]} onPress={() => editLandmark()}>
+								<Text>Confirm Location</Text>
+							</TouchableOpacity>
+						</View>
+					}
+				</View>
+			</View>
+		</View >
+	);
 }
 
 const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-    // backgroundColor: "#fff",
-    justifyContent: "center",
-    borderColor: "black",
-    borderWidth: 0,
-    marginVertical: 0,
-    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",
-
-  },
-  image: {
-    alignItems: 'center',
-    justifyContent: 'center',
-  },
-  png: {
-    borderColor: "red",
-    borderWidth: 3
-  },
-  arrowButton: {
-    flex: 1,
-    backgroundColor: colors.red,
-    height: 53.5,
-  },
+	container: {
+		flex: 1,
+		// backgroundColor: "#fff",
+		justifyContent: "center",
+		borderColor: "black",
+		borderWidth: 0,
+		marginVertical: 0,
+		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",
+
+	},
+	image: {
+		alignItems: 'center',
+		justifyContent: 'center',
+	},
+	png: {
+		borderColor: "red",
+		borderWidth: 3
+	},
+	arrowButton: {
+		flex: 1,
+		backgroundColor: colors.red,
+		height: 53.5,
+	},
 });
 
 

+ 32 - 0
src/components/Map/MainMapComponent/Map.styles.tsx

@@ -62,6 +62,38 @@ const mapStyles = StyleSheet.create({
     },
     alertButton: {
         bottom: 70,
+    },
+    previewButtonContainer: {
+        bottom: 40,
+        justifyContent: 'space-around',
+        flexDirection: 'row',
+        marginTop: -35
+    },
+    previewButtons: {
+        borderRadius: 10,
+        borderWidth: 1,
+        padding: 0,
+        width: 150,
+        height: 35,
+        justifyContent: "center",
+        alignItems: "center",
+    },
+    previewTitle: {
+        top: 60,
+        flexDirection: 'row'
+    },
+    cancelPreview: {
+        backgroundColor: colors.red,
+        borderColor: colors.red,
+    },
+    confirmPreview: {
+        backgroundColor: 'deepskyblue',
+        borderColor: 'deepskyblue'
+    },
+    editIndoorLocation: {
+        borderColor: 'white',
+        marginLeft: 15,
+        marginTop: 10,
     }
 })
 

+ 60 - 8
src/components/Map/MainMapComponent/OutdoorMap.tsx

@@ -5,16 +5,16 @@
  * <dev@clicknpush.ca>, January 2022
  */
 
-import { createIconSetFromIcoMoon, FontAwesome } from "@expo/vector-icons";
+import { FontAwesome } from "@expo/vector-icons";
 import { RouteProp, useNavigationState } from "@react-navigation/native";
 import { booleanPointInPolygon, circle } from '@turf/turf';
 import * as Notifications from 'expo-notifications';
 import { observer } from "mobx-react";
 import React, { useEffect, useState } from "react";
-import { Button, ActivityIndicator, Alert, Image, ImageBackground, Keyboard, Modal, Text, TouchableOpacity, TouchableWithoutFeedback, View, StyleProp, TextStyle, GestureResponderEvent } from "react-native";
+import { ActivityIndicator, Alert, Image, ImageBackground, Keyboard, Modal, Text, TouchableOpacity, TouchableWithoutFeedback, View } from "react-native";
 import { LatLng, LongPressEvent, MapEvent, Marker, Polygon, Polyline } from "react-native-maps";
 import { openSettings } from "react-native-permissions";
-import Spokestack, { activate } from 'react-native-spokestack';
+import Spokestack from 'react-native-spokestack';
 import { useAuth } from "../../../data/Auth/AuthContext";
 import { Landmark } from '../../../data/landmarks';
 import { NotifType } from "../../../data/notifications";
@@ -67,6 +67,8 @@ interface OutdoorMapProps {
     promptAddLandmark: (longitude: number, latitude: number, floor?: number, lmCount?: string, parent?: string) => void
     setMarkerWindowPosition: (point: {x: number, y: number}) => void
     getLmUri: (uri: String) => void
+    showPreviewPin: boolean
+    togglePreviewPin: (state: boolean) => void
 }
 
 const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
@@ -84,6 +86,22 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
     ]);
     const [size, setSize] = useState(0.052344);
 
+	// Local state that tracks active editing coordinates every frame instead of continually updating the actual landmark, then when the edited landmark or preview pin is released, update the actual landmark with this state.
+    const [newCoordinates, setNewCoordinates] = useState({latitude: 0, longitude: 0})
+
+	const [activeLandmark, setActiveLandmark] = useState<String>("")  // Local state to track active/selected landmark, used to highlight the selected landmark because the timing doesn't work with selectedLandmarkId
+    const [lmOpacity, setLmOpacity] = useState<number>(1)
+    
+    /**
+     * When there is no longer a landmark selected, revert the opacity of all landmarks to 1.
+     */
+	useEffect(() => {
+		if (props.selectedLandmarkId == "") {
+			setLmOpacity(1)
+			return
+		}
+	}, [props.selectedLandmarkId])
+
     /**
      * If the ReactNavigation route prop changes, check if it contains incoming selected landmarks, display them if there are. This will be triggered by incoming notifcations 
      * (See the AuthorizedNavigator page for the useEffect that will trigger this)
@@ -242,6 +260,17 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
     });
     }
 
+    /**
+     * Method to allow users to pick the precise location of their new landmark.
+     */
+    const chooseLocation = async (e: LongPressEvent) => {
+        if (!props.showPreviewPin) {
+            setNewCoordinates(e.nativeEvent.coordinate)
+            props.togglePreviewPin(true)
+            
+        }
+    }
+
     return (
         <TouchableWithoutFeedback>
             <>
@@ -255,6 +284,7 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
                     </View>
                 </Modal>
                 <MapView
+                    radius={10}
                     key={mapStateOutdoor.refreshKey}
                     toolbarEnabled={false}
                     onPress={() => Keyboard.dismiss()}
@@ -262,7 +292,7 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
                     ref={mapStateOutdoor.mapRef}
                     style={{ width: '100%', height: '100%' }}
                     initialRegion={getInitialRegion()}
-                    onLongPress={async (e) => await props.promptAddLandmark(e.nativeEvent.coordinate.longitude, e.nativeEvent.coordinate.latitude)}
+                    onLongPress={async (e) => chooseLocation(e)}
                     showsUserLocation={locationPermissionsGranted}
                     onUserLocationChange={e => updateLocation(e.nativeEvent.coordinate)}
                     followsUserLocation={mapStateOutdoor.followUser}
@@ -293,7 +323,14 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
                             props.mapNavigation.navigate("Indoor")}
                         }
                     />
-
+                    {props.showPreviewPin && 
+                        <Marker
+                            draggable
+                            //@ts-ignore ignores an error thrown on the cluster property even though cluster works as intended
+                            cluster={false}
+                            coordinate={newCoordinates}
+                            onDragEnd={(e) => setNewCoordinates(e.nativeEvent.coordinate)} >
+                        </Marker>}
                     {props.applyFilters(props.landmarks)?.map((landmark) => {
                         if (landmark.groupCount !== -1) {  // -1 means it is part of a group so don't render a marker for it
                             if (landmark.floor == null) {
@@ -303,9 +340,12 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
                                 }
                                 return (
                                     <Marker
+                                        opacity={landmark?.id == activeLandmark ? 1 : lmOpacity}
                                         tracksViewChanges={trackChanges}
                                         coordinate={{ latitude: landmark.latitude as number, longitude: landmark.longitude as number }}
                                         onPress={async e => {
+                                            setActiveLandmark(landmark.id)
+                                            setLmOpacity(0.3)
                                             e.persist()
                                             props.focusLandmark(landmark, e)
                                             await takeSnapshot();
@@ -317,7 +357,7 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
                                             landmark.landmark_type == 30 ?  // Landmark group
                                                 <ImageBackground style={{ height: 35, width: 25, zIndex: 10 }} source={lmTypes[landmark.landmark_type]?.image}> 
                                                     <View style={{height: 35, position: 'absolute', top: 0, left: 0, right: 0, bottom: "40%", alignItems: 'center'}}>
-                                                        <Text style={{fontFamily: "sans-serif-medium", fontWeight: "bold"}}>{landmark.groupCount}</Text>
+                                                        <Text style={{fontFamily: "sans-serif-medium", fontWeight: "bold", opacity: landmark?.id == activeLandmark ? 1 : lmOpacity}}>{landmark.groupCount}</Text>
                                                     </View>
                                                 </ImageBackground>
                                                 : <Image style={{ height: 35, width: 25, zIndex: 10 }} source={lmTypes[landmark.landmark_type]?.image} />
@@ -381,9 +421,11 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
                         strokeColor="#111111"
                         mode="WALKING"
                     /> */}
-                    <Marker coordinate={coordinates[1]} pinColor={colors.red}/>
+                    {/* @ts-ignore ignores an error thrown on the cluster property even though cluster works as intended */}
+                    <Marker cluster={false} coordinate={coordinates[1]} pinColor={colors.red}/>
 
-                    <Marker coordinate={{  latitude: 53.527189,longitude: -113.5233285, }} pinColor={colors.red}>
+                    {/* @ts-ignore ignores an error thrown on the cluster property even though cluster works as intended */}
+                    <Marker cluster={false} coordinate={{  latitude: 53.527189,longitude: -113.5233285, }} pinColor={colors.red}>
                         {/* <Image source={require('../../../../assets/accessibleEntrance.png')} /> */}
                         <Text style={{ fontSize: size>0.00327 ? 0 : 0.05/size , maxWidth:200, }}>Route from University Station to Cameron Library</Text>
                     </Marker>
@@ -416,6 +458,16 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
                         newLandmark={props.newLandmark}
                         setNewLandmark={props.setNewLandmark}
                     /> : null}
+                {props.showPreviewPin &&
+                <View style={[mapStyles.previewButtonContainer]}>
+                    <TouchableOpacity style={[mapStyles.previewButtons, mapStyles.cancelPreview]} onPress={() => props.togglePreviewPin(false)}>
+                        <Text style={{color: 'white'}}>Cancel</Text>
+                    </TouchableOpacity>
+                    <TouchableOpacity style={[mapStyles.previewButtons, mapStyles.confirmPreview]} onPress={() => props.promptAddLandmark(newCoordinates.longitude, newCoordinates.latitude)}>
+                        <Text>Add landmark here</Text>
+                    </TouchableOpacity>
+                </View>
+                }
             </>
         </TouchableWithoutFeedback>)
 }

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

@@ -59,6 +59,11 @@ export const useMapState = () => {
 
     const [uri, setUri] = useState<String>('')
 
+    /**
+     * Flag that toggles whether or not reporting is enabled in the {@link LandmarkDetails} modal. 
+     */
+    const [reportingEnabled, toggleReportingEnabled] = useState<boolean>(false);
+
     return { 
         onlyOwned, toggleOnlyOwned,
         lmFilteredTypes, setLmTypeFilter,
@@ -70,7 +75,8 @@ export const useMapState = () => {
         newLandmark, setNewLandmark,
         lmAddVisible, toggleLmAdd,
         place, setPlace,
-        uri, setUri
+        uri, setUri,
+        reportingEnabled, toggleReportingEnabled,
      }
 }
 

+ 10 - 12
src/components/Map/Panels/AddLandmarkPanel.tsx

@@ -5,34 +5,28 @@
  * <dev@clicknpush.ca>, January 2022
  */
 
-import { FontAwesome } from "@expo/vector-icons";
 import { ImageInfo } from "expo-image-picker/build/ImagePicker.types";
 import React, { memo, useEffect, useState, useRef } from "react";
-import { ActivityIndicator, Dimensions, Image, Platform, SafeAreaView, Text, TextInput, TouchableOpacity, View, ImageSourcePropType, Share, KeyboardEventName, Keyboard, StyleSheet, KeyboardAvoidingView, Alert, Button } from 'react-native';
+import { ActivityIndicator, Dimensions, Image, Platform, SafeAreaView, Text, TextInput, TouchableOpacity, View, ImageSourcePropType, KeyboardEventName, Keyboard, StyleSheet, KeyboardAvoidingView } from 'react-native';
 import { ScrollView } from "react-native-gesture-handler";
 import Modal from 'react-native-modal';
-import Picker from 'react-native-picker-select';
 import { Landmark, LMPhoto, useAddLandmark } from "../../../data/landmarks";
 import { colors, lmTypes as allLmTypes, lmTypesIndoor, catTypes, catTypesInGroup } from "../../../utils/GlobalUtils";
 import { IconButton, SecondaryButton } from "../../Buttons";
 import { PhotoPicker } from "../../PhotoPicker";
 import TouchOpaq from "./LandmarkDetailsPanel/TouchOpaq";
-import { Svg, Rect, Image as ImageSVG, Circle } from 'react-native-svg'
-import ReactDOMServer from 'react-dom/server'; //npm i --save-dev @types/react-dom
+import { Svg, Image as ImageSVG } from 'react-native-svg'
 
 
 import IndoorFloor from "../IndoorFloor";
-import ViewShot, { captureRef, captureScreen } from "react-native-view-shot";
+import ViewShot, { captureRef } from "react-native-view-shot";
 
 import { useNavigationState } from "@react-navigation/native"
 import LandmarkTypePicker from "../../LandmarkTypePicker";
-import { values } from "mobx";
-import { Icon } from "react-native-paper/lib/typescript/components/Avatar/Avatar";
 
 import DatePicker from 'react-native-date-picker'
 import CheckBox from "@react-native-community/checkbox";
-import { propertiesContainsFilter } from "@turf/turf";
-import { useOwnedProfile, useToggleGroupLMTip } from "../../../data/profiles";
+import { useOwnedProfile } from "../../../data/profiles";
 
 
 /**
@@ -60,6 +54,10 @@ export interface AddLandmarkProps {
      * The currently selected {@link Landmark} object, used only when a landmark is added to an existing individual landmark. 
      */
     siblingID?: string | undefined
+    /**
+     * State to show or hide the preview pin upon submitting a new landmark
+     */
+    togglePreviewPin: (state: boolean) => void
 }
 
 /**
@@ -67,7 +65,7 @@ export interface AddLandmarkProps {
  * @component
  * @category Map
  */
-const AddLandmarkPanel: React.FC<AddLandmarkProps> = ({ newLandmark, setNewLandmark, setVisible, visible, siblingID }) => {
+const AddLandmarkPanel: React.FC<AddLandmarkProps> = ({ newLandmark, setNewLandmark, setVisible, visible, siblingID, togglePreviewPin }) => {
     const [photos, setPhotos] = useState<LMPhoto[]>([])
     const [photoSourceMenuOpened, togglePhotoSourceMenu] = useState<boolean>(false)
     const [keyboardOpened, setKeyboardOpened] = useState<boolean>(false);
@@ -75,7 +73,6 @@ const AddLandmarkPanel: React.FC<AddLandmarkProps> = ({ newLandmark, setNewLandm
     const [showExpire, toggleShowExpire] = useState<boolean>(false)
     var minDate = new Date();
     const {profile} = useOwnedProfile()
-    const toggleGroupLMTip = useToggleGroupLMTip()
 
     const addLandmarkMutation = useAddLandmark()
 
@@ -172,6 +169,7 @@ const AddLandmarkPanel: React.FC<AddLandmarkProps> = ({ newLandmark, setNewLandm
      * Calls {@link addLandmarkAsync} from {@link useLandmarks} to initate the process of adding a landmark, then closes the modal.
      */
     const submit = async () => {
+        togglePreviewPin(false)
         let newGroupBool: boolean = false
         if (newLandmark?.groupCount == -2) {
             newGroupBool = true;

+ 9 - 30
src/components/Map/Panels/LandmarkDetailsPanel/CommentView.tsx

@@ -10,9 +10,8 @@ import { FontAwesome } from "@expo/vector-icons"
 import { format, parseISO } from "date-fns"
 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 { LMComment, useGetLandmark } from "../../../../data/comments"
+import ViewShot from "react-native-view-shot"
 import { Svg, Image as ImageSVG } from "react-native-svg"
 import IndoorFloor from "../../IndoorFloor"
 import { lmTypes } from "../../../../utils/GlobalUtils"
@@ -33,46 +32,22 @@ export interface CommentProps {
     startEditingComment: (comment: LMComment) => void
     deleteComment: (id: string) => void
     uri: String
+    toggleReporting: (state: boolean) => void,
 }
 
 /**
  * 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, uri }) => {
+export const CommentView: React.FC<CommentProps> = ({ comment, selected, focusComment: selectComment, startEditingComment: startEditingComment, deleteComment, uri, toggleReporting }) => {
     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)}>
@@ -92,7 +67,11 @@ export const CommentView: React.FC<CommentProps> = ({ comment, selected, focusCo
                             </View> :
                             <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} />
+                                    <TouchableOpacity onPress={() => {
+                                        toggleReporting(true)
+                                    }}>
+                                        <FontAwesome style={{ paddingTop: 1, marginLeft: 20 }} color="red" size={25} name="flag" />
+                                    </TouchableOpacity>
                                     : null}
                             </View>
                         : null}

+ 11 - 11
src/components/Map/Panels/LandmarkDetailsPanel/CommentsContainer.tsx

@@ -11,21 +11,20 @@ import { FlatList, Keyboard, ListRenderItem, StyleSheet, Text, TextInput, View,
 import { useAuth } from "../../../../data/Auth/AuthContext";
 import { LMComment } from "../../../../data/comments";
 import { MainTabsNavigationProp } from "../../../../navigation/MainTabsNavigator";
-import { navigate } from "../../../../navigation/RootNavigator";
-import { PrimaryButton, SecondaryButton } from "../../../Buttons";
+import { SecondaryButton } from "../../../Buttons";
 import { CommentView } from "./CommentView";
 
 interface CommentsContainerProps {
-    focusedCommentId?: string,
-    comments?: LMComment[],
-    focusComment: (id: string) => void,
-    startEditingComment: (comment: LMComment) => void,
+    focusedCommentId?: string
+    comments?: LMComment[]
+    focusComment: (id: string) => void
+    startEditingComment: (comment: LMComment) => void
     deleteComment: (id: string) => void
-    commentBeingEdited?: LMComment, 
-    setCommentBeingEdited: (comment: LMComment) => void,
-    setNewComment: (commentId: string) => void,
+    commentBeingEdited?: LMComment,
+    setCommentBeingEdited: (comment: LMComment) => void
+    setNewComment: (commentId: string) => void
     newCommentId?: string,
-    editComment: (comment: LMComment) => void,
+    editComment: (comment: LMComment) => void
     addComment: () => void
     commentListRef: MutableRefObject<FlatList>
     commentTextInputRef: MutableRefObject<TextInput>
@@ -35,6 +34,7 @@ interface CommentsContainerProps {
     authNavigation: MainTabsNavigationProp
     toggleLmDetails: (state: boolean) => void
     uri: String
+    toggleReporting: (state: boolean) => void
 }
 
 /**
@@ -57,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} uri={props.uri} />
+                <CommentView comment={item} selected={selected} focusComment={props.focusComment} startEditingComment={props.startEditingComment} deleteComment={props.deleteComment} uri={props.uri} toggleReporting={props.toggleReporting} />
             </View>
         );
     };

+ 252 - 187
src/components/Map/Panels/LandmarkDetailsPanel/DetailsBody.tsx

@@ -9,11 +9,10 @@ import { FontAwesome } from "@expo/vector-icons";
 import { useNavigationState } from "@react-navigation/native";
 import React, { MutableRefObject, useEffect, useRef, useState } from "react";
 import { FlatList, Dimensions, Image, ScrollView, StyleSheet, Text, TextInput, View, TouchableOpacity } from "react-native";
-import Picker from "react-native-picker-select";
 import { QueryStatus } from "react-query";
-import { LMComment } from "../../../../data/comments";
-import { Landmark, LMPhoto } from "../../../../data/landmarks";
-import MapView, {Marker} from "react-native-maps";
+import { LMComment, useReportComment } from "../../../../data/comments";
+import { Landmark, LMPhoto, useReportLandmark } from "../../../../data/landmarks";
+import MapView, { Marker } from "react-native-maps";
 import { usePermissions } from "../../../../data/PermissionsContext";
 import { MainTabsNavigationProp } from "../../../../navigation/MainTabsNavigator";
 import { lmTypes as allLmTypes, lmTypesIndoor, catTypes, catTypesInGroup } from "../../../../utils/GlobalUtils";
@@ -21,11 +20,13 @@ import LandmarkTypePicker from "../../../LandmarkTypePicker";
 import { Separator } from "../../../Separator";
 import { CommentsContainer } from "./CommentsContainer";
 import { LandmarkPhotos } from "./LandmarkPhotos";
-import { useOutdoorMapState } from "../../MainMapComponent/useMapState";
 import { TagContainer } from "./TagContainer";
 import CheckBox from "@react-native-community/checkbox";
 import DatePicker from "react-native-date-picker";
 import { useAuth } from "../../../../data/Auth/AuthContext";
+import { captureRef } from "react-native-view-shot";
+import { Report } from "./Report";
+import mapStyles from "../../MainMapComponent/Map.styles";
 
 
 
@@ -41,17 +42,17 @@ interface DetailsBodyProps {
     focusComment: (id: string) => void,
     startEditingComment: (comment: LMComment) => void,
     deleteComment: (id: string) => void
-    commentBeingEdited?: LMComment, 
+    commentBeingEdited?: LMComment,
     setCommentBeingEdited: (comment: LMComment) => void,
     setNewComment: (commentId: string) => void,
     newCommentId?: string,
     editComment: (comment: LMComment) => void,
     addComment: () => void
-    setSelectedImage: (index: number) => void 
+    setSelectedImage: (index: number) => void
     tryDeletePhoto: (photoId: string) => void
     addPhoto: (photo: LMPhoto) => void
     addPhotoStatus: QueryStatus
-    deletePhotoStatus: QueryStatus 
+    deletePhotoStatus: QueryStatus
     toggleLmDetails: (state: boolean) => void
     setKeyboardOpened: (state: boolean) => void
     keyboardOpened: boolean
@@ -65,17 +66,21 @@ interface DetailsBodyProps {
     applyFilters: (landmarks?: Landmark[], getGroupList?: boolean, parent?: string) => Landmark[]
     landmarks: Landmark[]
     toggleDetailsPanel: (state: boolean) => void,
-    setLandmark: (landmark: string) => void; 
+    setLandmark: (landmark: string) => void;
     promptAddLandmark: (longitude: number, latitude: number, floor?: number, lmCount?: string, parent?: string) => void
+    reportingEnabled: boolean
+    toggleReporting: (state: boolean) => void,
+    commentReported: string
+	editLmLoc: boolean
+	toggleEditLmLoc: (state: boolean) => void
 }
 
 /**
  * Component that renders the body of the Landmark details panel
 */
 export const DetailsBody: React.FC<DetailsBodyProps> = (props) => {
-    const {locationPermissionsGranted, checkLocationPermissions, voicePermissionsGranted, checkVoicePermissions} = usePermissions();
-    const mapState = useOutdoorMapState()
-    const {landmarkOwnedByUser} = useAuth()
+    const { locationPermissionsGranted } = usePermissions();
+    const { landmarkOwnedByUser } = useAuth()
 
     const navigationState = useNavigationState(state => state)
     const [currentRoute, setCurrentRoute] = useState<string>()
@@ -88,7 +93,7 @@ export const DetailsBody: React.FC<DetailsBodyProps> = (props) => {
 
 
     let lmTypes = allLmTypes
-    if(currentRoute=="Indoor") {
+    if (currentRoute == "Indoor") {
         lmTypes = lmTypesIndoor
     }
 
@@ -100,7 +105,7 @@ export const DetailsBody: React.FC<DetailsBodyProps> = (props) => {
             console.error(props.landmark?.expiry_date)
             if (props.landmark?.expiry_date !== null) {
                 toggleShowExpire(true)
-                var parts = (props.landmark?.expiry_date?.toString().substring(0,10)).split('-')
+                var parts = (props.landmark?.expiry_date?.toString().substring(0, 10)).split('-')
                 setDate(new Date(+parts[0], +parts[1] - 1, +parts[2]))
             }
             else {
@@ -111,78 +116,112 @@ export const DetailsBody: React.FC<DetailsBodyProps> = (props) => {
             props.setUpdatedLandmark(undefined)
         }
     }, [props.editingEnabled])
-    
+
     const [date, setDate] = useState(new Date())
     const [showExpire, toggleShowExpire] = useState<boolean>(false)
     const [groupList, toggleGroupList] = useState<boolean>(props.landmark?.landmark_type == 30 ? true : false)
 
     useEffect(() => {
         if (props.editingEnabled && !showExpire)
-            props.setUpdatedLandmark({...props.updatedLandmark, expiry_date: null})
+            props.setUpdatedLandmark({ ...props.updatedLandmark, expiry_date: null })
         else if (props.editingEnabled && showExpire)
-            props.setUpdatedLandmark({...props.updatedLandmark, expiry_date: date})
+            props.setUpdatedLandmark({ ...props.updatedLandmark, expiry_date: date })
     }, [showExpire])
 
     useEffect(() => {
         if (showExpire)
-            props.setUpdatedLandmark({...props.updatedLandmark, expiry_date: date})
+            props.setUpdatedLandmark({ ...props.updatedLandmark, expiry_date: date })
     }, [date])
 
-    const [editLocation, toggleEditLocation] = useState<boolean>(false)
-    
+    const reportLandmarkMutation = useReportLandmark()
+    const reportCommentMutation = useReportComment()
+    const capture: any = React.createRef();
+
+    /**
+     * 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);
+    }
+
+    /**
+     * Calls the {@linkcode reportComment} mutation from the {@link useReportComment} hook.
+     */
+    const reportComment = async (commentID: string, reasons: any[], extraComments: string) => {
+        let content = {}
+        content['id'] = commentID
+        if (props.landmark?.floor !== null) {  // Indoor landmark screenshot
+            const uri = await captureRef(capture, {
+                format: "jpg",
+                quality: 1,
+                result: 'base64'
+            })
+            content['indoorLmLocImg'] = uri
+        }
+        else {  // Outdoor landmark screenshot
+            content['indoorLmLocImg'] = props.uri
+        }
+        content['reasons'] = reasons
+        content['isLandmark'] = false
+        content['extraComments'] = extraComments
+        content['landmarkInfo'] = props.landmark
+
+        await reportCommentMutation.mutateAsync(content);
+    }
+
     /**
      * Sub-component that renders picker for landmark types
      * @param 
      */
     const LandmarkTypePickerContainer: React.FC = () => {
-        if (props.updatedLandmark?.landmark_type){
-            return(
-            <>
-                <LandmarkTypePicker 
-                    placeholder={{ label: "Select a landmark type...", value: 0 }}
-                    value={props.updatedLandmark?.landmark_type}
-                    onValueChange={(value) => {
-                        if (value) {
-                            props.setUpdatedLandmark({ ...props.updatedLandmark, landmark_type: value, title: lmTypes[value].label })
-                        }
-                        else {
-                            props.setUpdatedLandmark({ ...props.updatedLandmark, landmark_type: undefined, title: 'no title' })
-                        }
-                    }}
-                    cats={Object.keys(props.landmark?.groupCount == -1 ? catTypesInGroup : catTypes)?.map(icon => {
-                        return (
-                            { label: catTypes[parseInt(icon)]?.cat.toUpperCase(), value: icon, key: icon }
-                        )
-                    })}
-                    items={Object.keys(lmTypes)?.map(icon => {
-                        return (
-                            { label: lmTypes[parseInt(icon)]?.label.toUpperCase(), 
-                            value: icon, key: lmTypes[parseInt(icon)]?.cat.toUpperCase() }
-                        )
-                    })}/>
-            </>
+        if (props.updatedLandmark?.landmark_type) {
+            return (
+                <>
+                    <LandmarkTypePicker
+                        placeholder={{ label: "Select a landmark type...", value: 0 }}
+                        value={props.updatedLandmark?.landmark_type}
+                        onValueChange={(value) => {
+                            if (value) {
+                                props.setUpdatedLandmark({ ...props.updatedLandmark, landmark_type: value, title: lmTypes[value].label })
+                            }
+                            else {
+                                props.setUpdatedLandmark({ ...props.updatedLandmark, landmark_type: undefined, title: 'no title' })
+                            }
+                        }}
+                        cats={Object.keys(props.landmark?.groupCount == -1 ? catTypesInGroup : catTypes)?.map(icon => {
+                            return (
+                                { label: catTypes[parseInt(icon)]?.cat.toUpperCase(), value: icon, key: icon }
+                            )
+                        })}
+                        items={Object.keys(lmTypes)?.map(icon => {
+                            return (
+                                {
+                                    label: lmTypes[parseInt(icon)]?.label.toUpperCase(),
+                                    value: icon, key: lmTypes[parseInt(icon)]?.cat.toUpperCase()
+                                }
+                            )
+                        })} />
+                </>
             )
         }
-        else return(null)
-        // return (
-        // <View style={{flexDirection: 'row', marginBottom: 20, justifyContent: "space-between"}}>
-        //     {props.updatedLandmark?.landmark_type ? 
-        //     <>
-        //         <LandmarkTypePicker 
-        //             placeholder={{}}
-        //             value={props.updatedLandmark?.landmark_type} 
-        //             onValueChange={(value) => {
-        //                 props.setUpdatedLandmark({...props.updatedLandmark, landmark_type: value, title: lmTypes[value].label})
-        //             }}  
-        //             items={Object.keys(lmTypes)?.filter(icon => parseInt(icon) != props.landmark?.landmark_type).map(icon => {
-        //                 return (
-        //                     {label: lmTypes[parseInt(icon)].label.toUpperCase(), value: icon, key: icon}
-        //                 )})}/>
-        //         {props.updatedLandmark ? <Image style={{marginLeft: 20}} source={lmTypes[props.updatedLandmark?.landmark_type].image}/> : null}
-        //     </>
-        //     : null}
-        // </View>
-        // )
+        else return (null)
     }
 
     /**
@@ -190,21 +229,21 @@ export const DetailsBody: React.FC<DetailsBodyProps> = (props) => {
      * @param 
      */
     const EditingDisabledUpperView: React.FC = () => {
-        if (!groupList) {
+        if (!groupList && !props.reportingEnabled) {
             return (
-                <View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
-                    <View style={{flex: 8, flexDirection: 'column', marginBottom: 20}}>
+                <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
+                    <View style={{ flex: 8, flexDirection: 'column', marginBottom: 20 }}>
                         {props.landmark?.groupCount == -1 ?
-                            <TouchableOpacity style={styles.groupReturnButton} onPress={() => {props.setLandmark(props.landmark?.parent); toggleGroupList(true)}}>
-                                <FontAwesome size={15} color="white" name="chevron-left"/>
-                                <Text style={{color: "white", marginLeft: 5}}>Return to group</Text>
+                            <TouchableOpacity style={styles.groupReturnButton} onPress={() => { props.setLandmark(props.landmark?.parent); toggleGroupList(true) }}>
+                                <FontAwesome size={15} color="white" name="chevron-left" />
+                                <Text style={{ color: "white", marginLeft: 5 }}>Return to group</Text>
                             </TouchableOpacity>
                             : null}
-                        <Text style={{color: 'white', marginBottom: 10, fontSize: 15}}>{lmTypes[props.landmark?.landmark_type]?.label.toUpperCase()}</Text>
+                        <Text style={{ color: 'white', marginBottom: 10, fontSize: 15 }}>{lmTypes[props.landmark?.landmark_type]?.label.toUpperCase()}</Text>
                         <ScrollView nestedScrollEnabled={true}>
-                            <Text style={{color: 'white', fontSize: 13}}>{props.landmark?.description}</Text>
+                            <Text style={{ color: 'white', fontSize: 13 }}>{props.landmark?.description}</Text>
                             {props.landmark?.expiry_date ?
-                            <Text style={{color: 'white', fontSize: 13}}>Landmark expires on {props.landmark.expiry_date.toString().substring(0,10)}</Text> : null}
+                                <Text style={{ color: 'white', fontSize: 13 }}>Landmark expires on {props.landmark.expiry_date.toString().substring(0, 10)}</Text> : null}
                         </ScrollView>
                     </View>
                     {props.landmark?.landmark_type ? <Image source={lmTypes[props.landmark?.landmark_type]?.image} /> : null}
@@ -219,10 +258,10 @@ export const DetailsBody: React.FC<DetailsBodyProps> = (props) => {
     const LandmarkGroupListView: React.FC = () => {
         return (
             <View>
-                <Text style={{color: 'white', fontSize: 13}}>{props.landmark?.description}</Text>
+                <Text style={{ color: 'white', fontSize: 13 }}>{props.landmark?.description}</Text>
                 {props.landmark?.expiry_date ?
-                <Text style={{color: 'white', fontSize: 13}}>Landmark group expires on {props.landmark.expiry_date.toString().substring(0,10)}</Text> : null}
-                <Text style={{fontSize: 16, color: "white", marginBottom: 10, marginTop: 5}}>Landmarks in this group:</Text>
+                    <Text style={{ color: 'white', fontSize: 13 }}>Landmark group expires on {props.landmark.expiry_date.toString().substring(0, 10)}</Text> : null}
+                <Text style={{ fontSize: 16, color: "white", marginBottom: 10, marginTop: 5 }}>Landmarks in this group:</Text>
                 <ScrollView>
                     {props.applyFilters(props.landmarks, true, props.landmark?.id)?.map((landmark) => {
                         return (
@@ -234,130 +273,156 @@ export const DetailsBody: React.FC<DetailsBodyProps> = (props) => {
                                     props.setLandmark(landmark.id)
                                 }}>
                                 <Image style={{ height: 52.5, width: 37.5, zIndex: 10, margin: 5 }} source={lmTypes[landmark.landmark_type]?.image} />
-                                <Text style={{marginLeft: 10}}>{lmTypes[landmark?.landmark_type]?.label.toUpperCase()}</Text>
+                                <Text style={{ marginLeft: 10 }}>{lmTypes[landmark?.landmark_type]?.label.toUpperCase()}</Text>
                             </TouchableOpacity>
                         )
                     }
                     )}
-                    {landmarkOwnedByUser(props.landmark) ? 
-                    <TouchableOpacity style={{marginTop: 30, justifyContent: 'center', alignItems: 'center', opacity: .7}}
-                        onPress={() => {props.promptAddLandmark(props.landmark?.longitude, props.landmark?.latitude, props.landmark?.floor, "groupItem", props.landmark?.id)}}>
-                        <Text style={{fontSize: 20, marginBottom: 10, color: 'white'}}>Add landmark to group</Text>
-                        <FontAwesome name="plus" size={30} color='white' />
-                    </TouchableOpacity> : null }
+                    {landmarkOwnedByUser(props.landmark) ?
+                        <TouchableOpacity style={{ marginTop: 30, marginBottom: 30, justifyContent: 'center', alignItems: 'center', opacity: .7 }}
+                            onPress={() => { props.promptAddLandmark(props.landmark?.longitude, props.landmark?.latitude, props.landmark?.floor, "groupItem", props.landmark?.id) }}>
+                            <Text style={{ fontSize: 20, color: 'white' }}>Add another landmark here</Text>
+                            <FontAwesome name="plus" size={30} color='white' />
+                        </TouchableOpacity> : null}
                 </ScrollView>
             </View>
         )
     }
 
+
     return (
-        <ScrollView ref={mainScrollRef} nestedScrollEnabled={true} contentContainerStyle={{justifyContent: 'space-between'}} style={{flex: 1, marginHorizontal: 20}}>
+        <ScrollView ref={mainScrollRef} nestedScrollEnabled={true} contentContainerStyle={{ justifyContent: 'space-between' }} style={{ flex: 1, marginHorizontal: 20 }}>
             {props.editingEnabled ?
-            <>
-                {props.landmark?.groupCount !== -1 &&  // Prevent users from editing the location of landmarks in a group (must edit group itself)
-                <MapView
-                    toolbarEnabled={false}
-                    onPress={(e) => 
-                        props.setUpdatedLandmark({...props.updatedLandmark, longitude: e.nativeEvent.coordinate.longitude, latitude: e.nativeEvent.coordinate.latitude})
-                    }
-                    testID="mapViewLocationEdit"
-                    style={{ width: '100%', height: Dimensions.get("window").height * .4, marginBottom: 20, marginTop: 10}}
-                    initialRegion={{latitude: props.landmark?.latitude, longitude: props.landmark?.longitude, latitudeDelta: 0.01, longitudeDelta: 0.01}}
-                    showsUserLocation={locationPermissionsGranted}
-                    showsMyLocationButton={false}
-                    >
-                    <Marker
-                        coordinate={{ latitude: props.updatedLandmark?.latitude as number, longitude: props.updatedLandmark?.longitude as number }} >
-                        {props.updatedLandmark?.landmark_type ? <Image style={{ height: 35, width: 25 }} source={lmTypes[props.updatedLandmark?.landmark_type]?.image} /> : null}
-                    </Marker>
-                </MapView>}
-                {props.landmark?.landmark_type !== 30 && <LandmarkTypePickerContainer /> /* Prevent editing of landmark type if it is a group */}
-                <Separator style={{marginVertical: 10, opacity: .5}} color="lightgray" />
-                <Text style={{color: 'white', marginBottom: 10}}>Description</Text>
-                <ScrollView nestedScrollEnabled={true} style={{backgroundColor: 'white', marginBottom: 20}}>
-                    <TextInput 
-                        multiline={true} 
-                        style={[styles.input, {fontSize: 13, marginBottom: 10}]}
-                        onChangeText={text => props.setUpdatedLandmark({...props.updatedLandmark, description: text})} 
-                        value={props.updatedLandmark?.description}/>
-                </ScrollView>
-                <View style={{ paddingHorizontal: 15, paddingVertical: 10, flex: 1, flexDirection: "row"}}>
-                    <Text style={{color: "white", margin: 0, fontSize: 16}}>Add expiry date?</Text>
-                    <CheckBox
-                        value={showExpire}
-                        boxType="square"
-                        onValueChange={() => toggleShowExpire(!showExpire)}
-                        tintColors={{true: "white", false: "white"}}
-                        tintColor="white"
-                        onCheckColor="#00000000"
-                        onFillColor="white"
-                        onTintColor="white"
-                        style={{borderColor: "white", marginLeft: 10, width: 20, height: 20}}
-                    />
-                </View>
-                <View style={{flex: 1, alignSelf: "stretch"}}>
-                    {showExpire && <DatePicker
-                        mode="date"
-                        date={date}
-                        onDateChange={setDate}
-                        fadeToColor="none" />}
-                </View>
-            </>: <EditingDisabledUpperView />}
+                <>
+                    {props.landmark?.groupCount !== -1 ?  // Prevent users from editing the location of landmarks in a group (must edit group itself)
+                        props.landmark?.floor == null ?  // Outdoor landmark, render MapView to edit location
+                            <MapView
+                                toolbarEnabled={false}
+                                onPress={(e) =>
+                                    props.setUpdatedLandmark({ ...props.updatedLandmark, longitude: e.nativeEvent.coordinate.longitude, latitude: e.nativeEvent.coordinate.latitude })
+                                }
+                                testID="mapViewLocationEdit"
+                                style={{ width: '100%', height: Dimensions.get("window").height * .4, marginBottom: 20, marginTop: 10 }}
+                                initialRegion={{ latitude: props.landmark?.latitude, longitude: props.landmark?.longitude, latitudeDelta: 0.01, longitudeDelta: 0.01 }}
+                                showsUserLocation={locationPermissionsGranted}
+                                showsMyLocationButton={false}
+                            >
+                                <Marker
+                                    draggable
+                                    onDragEnd={(e) => props.setUpdatedLandmark({ ...props.updatedLandmark, longitude: e.nativeEvent.coordinate.longitude, latitude: e.nativeEvent.coordinate.latitude })}
+                                    coordinate={{ latitude: props.updatedLandmark?.latitude as number, longitude: props.updatedLandmark?.longitude as number }} >
+                                    {props.updatedLandmark?.landmark_type ? <Image style={{ height: 35, width: 25 }} source={lmTypes[props.updatedLandmark?.landmark_type]?.image} /> : null}
+                                </Marker>
+                            </MapView>
+                            :
+                            // Indoor landmark, button to edit landmark location
+                            <View>
+                                <TouchableOpacity style={[mapStyles.previewButtons, mapStyles.editIndoorLocation]}
+                                    onPress={() => {
+                                        // props.toggleDetailsPanel(false)
+                                        props.toggleLmDetails(false)
+                                        props.toggleEditLmLoc(true)
+                                    }}>
+                                    <Text style={{ color: 'white' }}>Edit landmark location</Text>
+                                </TouchableOpacity>
+                            </View>
+                        : null}
+                    {props.landmark?.landmark_type !== 30 && <LandmarkTypePickerContainer /> /* Prevent editing of landmark type if it is a group */}
+                    <Separator style={{ marginVertical: 10, opacity: .5 }} color="lightgray" />
+                    <Text style={{ color: 'white', marginBottom: 10 }}>Description</Text>
+                    <ScrollView nestedScrollEnabled={true} style={{ backgroundColor: 'white', marginBottom: 20 }}>
+                        <TextInput
+                            multiline={true}
+                            style={[styles.input, { fontSize: 13, marginBottom: 10 }]}
+                            onChangeText={text => props.setUpdatedLandmark({ ...props.updatedLandmark, description: text })}
+                            value={props.updatedLandmark?.description} />
+                    </ScrollView>
+                    <View style={{ paddingHorizontal: 15, paddingVertical: 10, flex: 1, flexDirection: "row" }}>
+                        <Text style={{ color: "white", margin: 0, fontSize: 16 }}>Add expiry date?</Text>
+                        <CheckBox
+                            value={showExpire}
+                            boxType="square"
+                            onValueChange={() => toggleShowExpire(!showExpire)}
+                            tintColors={{ true: "white", false: "white" }}
+                            tintColor="white"
+                            onCheckColor="#00000000"
+                            onFillColor="white"
+                            onTintColor="white"
+                            style={{ borderColor: "white", marginLeft: 10, width: 20, height: 20 }}
+                        />
+                    </View>
+                    <View style={{ flex: 1, alignSelf: "stretch" }}>
+                        {showExpire && <DatePicker
+                            mode="date"
+                            date={date}
+                            onDateChange={setDate}
+                            fadeToColor="none" />}
+                    </View>
+                </> : <EditingDisabledUpperView />}
+            {props.reportingEnabled ?
+                <Report
+                    landmark={props.landmark}
+                    commentID={props.commentReported}
+                    isLandmark={props.commentReported == "" ? true : false}
+                    reportLandmark={reportLandmark}
+                    reportComment={reportComment}
+                    toggleReporting={props.toggleReporting} /> : null}
             {!props.editingEnabled && groupList ?
-            <LandmarkGroupListView /> : null}
-            {!props.editingEnabled && !groupList ? 
-            <TagContainer
-                landmark={props.landmark}
-                tagLandmark={props.tagLandmark}
-                toggleLmDetails={props.toggleLmDetails}
-                authNavigation={props.authNavigation}
-                taggedByUser={props.taggedByUser} /> : null}
-            {!props.editingEnabled && !groupList ?
-            <CommentsContainer
-                toggleLmDetails={props.toggleLmDetails}
-                authNavigation={props.authNavigation}
-                setKeyboardOpened={props.setKeyboardOpened}
-                keyboardOpened={props.keyboardOpened}
-                comments={props.comments}
-                mainScrollRef={mainScrollRef}
-                focusComment={props.focusComment}
-                focusedCommentId={props.focusedCommentId}
-                commentListRef={props.commentListRef}
-                commentTextInputRef={props.commentTextInputRef}
-                commentBeingEdited={props.commentBeingEdited}
-                addComment={props.addComment}
-                setCommentBeingEdited={props.setCommentBeingEdited}
-                setNewComment={props.setNewComment}
-                newCommentId={props.newCommentId}
-                editComment={props.editComment}
-                startEditingComment={props.startEditingComment}
-                deleteComment={props.deleteComment}
-                uri={props.uri} /> : null}
+                <LandmarkGroupListView /> : null}
+            {!props.editingEnabled && !groupList && !props.reportingEnabled ?
+                <TagContainer
+                    landmark={props.landmark}
+                    tagLandmark={props.tagLandmark}
+                    toggleLmDetails={props.toggleLmDetails}
+                    authNavigation={props.authNavigation}
+                    taggedByUser={props.taggedByUser} /> : null}
+            {!props.editingEnabled && !groupList && !props.reportingEnabled ?
+                <CommentsContainer
+                    toggleLmDetails={props.toggleLmDetails}
+                    authNavigation={props.authNavigation}
+                    setKeyboardOpened={props.setKeyboardOpened}
+                    keyboardOpened={props.keyboardOpened}
+                    comments={props.comments}
+                    mainScrollRef={mainScrollRef}
+                    focusComment={props.focusComment}
+                    focusedCommentId={props.focusedCommentId}
+                    commentListRef={props.commentListRef}
+                    commentTextInputRef={props.commentTextInputRef}
+                    commentBeingEdited={props.commentBeingEdited}
+                    addComment={props.addComment}
+                    setCommentBeingEdited={props.setCommentBeingEdited}
+                    setNewComment={props.setNewComment}
+                    newCommentId={props.newCommentId}
+                    editComment={props.editComment}
+                    startEditingComment={props.startEditingComment}
+                    deleteComment={props.deleteComment}
+                    uri={props.uri}
+                    toggleReporting={props.toggleReporting} /> : null}
             {!props.editingEnabled && !props.keyboardOpened && !groupList ?
-            <LandmarkPhotos 
-                profileId={props.profileId}
-                deletePhotoStatus={props.deletePhotoStatus}
-                addPhotoStatus={props.addPhotoStatus}
-                tryDeletePhoto={props.tryDeletePhoto}
-                addPhoto={props.addPhoto}
-                toggleLmDetails={props.toggleLmDetails}
-                editingEnabled={props.editingEnabled}
-                setUpdatedLandmark={props.setUpdatedLandmark}
-                updatedLandmark={props.updatedLandmark}
-                landmark={props.landmark}
-                setSelectedImage={props.setSelectedImage}
-                processingPhoto={props.processingPhoto}
-                setProcessingPhoto={props.setProcessingPhoto}/>
-             : null}
-            {landmarkOwnedByUser(props.landmark) && !groupList && !props.landmark?.parent ? 
-                <View style={{borderTopWidth: 1, borderColor: 'lightgray'}}>
-                    <TouchableOpacity style={{paddingTop: 30, justifyContent: 'center', alignItems: 'center', opacity: .7}}
-                        onPress={() => {props.promptAddLandmark(props.landmark?.longitude, props.landmark?.latitude, props.landmark?.floor, "newGroup", props.landmark?.id)}}>
-                        <Text style={{fontSize: 20, marginBottom: 10, color: 'white'}}>Add another landmark here</Text>
+                <LandmarkPhotos
+                    profileId={props.profileId}
+                    deletePhotoStatus={props.deletePhotoStatus}
+                    addPhotoStatus={props.addPhotoStatus}
+                    tryDeletePhoto={props.tryDeletePhoto}
+                    addPhoto={props.addPhoto}
+                    toggleLmDetails={props.toggleLmDetails}
+                    editingEnabled={props.editingEnabled}
+                    setUpdatedLandmark={props.setUpdatedLandmark}
+                    updatedLandmark={props.updatedLandmark}
+                    landmark={props.landmark}
+                    setSelectedImage={props.setSelectedImage}
+                    processingPhoto={props.processingPhoto}
+                    setProcessingPhoto={props.setProcessingPhoto} />
+                : null}
+            {landmarkOwnedByUser(props.landmark) && !groupList && !props.landmark?.parent ?
+                <View style={{ borderTopWidth: 1, borderColor: 'lightgray' }}>
+                    <TouchableOpacity style={{ paddingTop: 30, marginBottom: 20, justifyContent: 'center', alignItems: 'center', opacity: .7 }}
+                        onPress={() => { props.promptAddLandmark(props.landmark?.longitude, props.landmark?.latitude, props.landmark?.floor, "newGroup", props.landmark?.id) }}>
+                        <Text style={{ fontSize: 20, marginBottom: 10, color: 'white' }}>Add another landmark here</Text>
                         <FontAwesome name="plus" size={30} color='white' />
                     </TouchableOpacity>
-                </View> : null }
-            
+                </View> : null}
+
         </ScrollView>
     )
 }

+ 27 - 32
src/components/Map/Panels/LandmarkDetailsPanel/DetailsHeader.tsx

@@ -7,9 +7,9 @@
 
 import React from "react";
 import { FontAwesome } from "@expo/vector-icons";
-import { StyleSheet, Text, TouchableOpacity, View, ImageSourcePropType, SafeAreaView, FlatList } from "react-native";
+import { StyleSheet, Text, TouchableOpacity, View, ImageSourcePropType } from "react-native";
 import { Svg, Image as ImageSVG } from "react-native-svg";
-import ViewShot, { captureRef } from "react-native-view-shot";
+import ViewShot from "react-native-view-shot";
 import { QueryStatus } from "react-query";
 import { useAuth } from "../../../../data/Auth/AuthContext";
 import { Landmark, useReportLandmark } from "../../../../data/landmarks";
@@ -17,7 +17,6 @@ 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";
 
@@ -39,6 +38,10 @@ interface DetailsHeaderProps {
     toggleLmDetails: (state: boolean) => void
     place: string
     uri: String
+    reportingEnabled: boolean,
+    toggleReporting: (state: boolean) => void,
+    setFocusedComment: (state: string) => void
+    setLandmark: (landmark: string) => void
 }
 
 /**
@@ -53,36 +56,11 @@ export const DetailsHeader: React.FC<DetailsHeaderProps> = (props) => {
             props.deletePhotoStatus == "loading"
     }
 
-    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
         if (landmarkOwnedByUser(props.landmark) || (props.profile?.places == props.place)) {
@@ -91,7 +69,7 @@ export const DetailsHeader: React.FC<DetailsHeaderProps> = (props) => {
                 return (
                     <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: "center", width: '100%' }}>
                         <TouchOpaq
-                            func={() => props.toggleEditing(false)}
+                            func={() => { props.toggleEditing(false) }}
                             size={25}
                             col={"white"}
                             name={"close"}
@@ -129,7 +107,7 @@ export const DetailsHeader: React.FC<DetailsHeaderProps> = (props) => {
                             <FontAwesome style={{ marginLeft: 5, marginBottom: 2, marginRight: 10 }} color="white" size={25} name="thumbs-up" />
                         </View>
                         <TouchOpaq
-                            func={() => props.toggleDetailsPanel(false)}
+                            func={() => { props.setLandmark(""); props.toggleDetailsPanel(false) }}
                             size={25}
                             col={"white"}
                             name={"close"}
@@ -173,9 +151,26 @@ export const DetailsHeader: React.FC<DetailsHeaderProps> = (props) => {
                             </View>
                         </TouchableOpacity>}
                     <View style={{ flexDirection: 'row' }}>
-                        {anonUserId ? null : <Report isLandmark={true} landmark={props.landmark} reportLandmark={reportLandmark} />}
+                        {anonUserId ? null : // Show report button if user is logged in
+                            <TouchableOpacity onPress={() => {
+                                props.setFocusedComment("")
+                                props.toggleReporting(true)
+                            }}>
+                                <View>
+                                    {!props.reportingEnabled ?
+                                        props.landmark?.floor == null ?  // 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" />
+                                        : null}
+                                </View>
+                            </TouchableOpacity>}
                         <TouchOpaq
-                            func={() => props.toggleDetailsPanel(false)}
+                            func={() => {
+                                props.setLandmark("")
+                                props.toggleDetailsPanel(false)
+                                props.toggleReporting(false)
+                            }}
                             col={"white"}
                             size={25}
                             name={"close"}

+ 29 - 14
src/components/Map/Panels/LandmarkDetailsPanel/LandmarkDetails.tsx

@@ -7,17 +7,18 @@
 
 import { FontAwesome } from "@expo/vector-icons";
 import React, { memo, useEffect, useRef, useState } from "react";
-import { ScrollView, ActivityIndicator, Alert, Dimensions, FlatList, Image, Keyboard, Platform, SafeAreaView, StyleSheet, Text, TextInput, TouchableOpacity, View } from "react-native";
+import { ScrollView, ActivityIndicator, Alert, Dimensions, FlatList, Image, Keyboard, SafeAreaView, StyleSheet, Text, TextInput, TouchableOpacity, View } from "react-native";
 import Modal from 'react-native-modal';
 import { LMComment, useAddComment, useDeleteComment, useEditComment, useLandmarkComments } from "../../../../data/comments";
-import { Landmark, useAddLandmarkPhoto, useDeleteLandmark, useDeleteLandmarkPhoto, useEditLandmark, useLandmark, useRateLandmark, useTagLandmark } from "../../../../data/landmarks";
+import { Landmark, useAddLandmarkPhoto, useDeleteLandmark, useDeleteLandmarkPhoto, useLandmark, useRateLandmark, useTagLandmark } from "../../../../data/landmarks";
 import { useAuth } from "../../../../data/Auth/AuthContext";
-import { colors, lmTypes } from "../../../../utils/GlobalUtils";
+import { colors } from "../../../../utils/GlobalUtils";
 import { IconButton, PrimaryButton } from "../../../Buttons";
 import { DetailsBody } from "./DetailsBody";
 import { DetailsHeader } from "./DetailsHeader";
 import { useOwnedProfile } from "../../../../data/profiles";
 import { MainTabsNavigationProp } from "../../../../navigation/MainTabsNavigator";
+import { UseMutationResult } from "react-query";
 
 /**
  * Props for the {@link LandmarkDetails} component.
@@ -52,6 +53,13 @@ export interface LandmarkDetailsProps {
     applyFilters: (landmarks?: Landmark[], getGroupList?: boolean, parent?: string) => Landmark[]
     landmarks: Landmark[]
     promptAddLandmark: (longitude: number, latitude: number, floor?: number, lmCount?: string, parent?: string) => void
+    reportingEnabled: boolean
+    toggleReporting: (state: boolean) => void
+	editLmLoc: boolean
+	toggleEditLmLoc: (state: boolean) => void
+    editLandmarkMutation: UseMutationResult<any, unknown, Landmark, unknown>
+	updatedLandmark: Landmark,
+	setUpdatedLandmark: (landmark: Landmark) => void
 }
 
 /**
@@ -59,7 +67,7 @@ export interface LandmarkDetailsProps {
  * @component
  * @category Map
  */
-const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition, authNavigation, landmarkId, setLandmark, toggleDetailsPanel, setEditing, editingEnabled, visible, toggleLmDetails, place, uri, applyFilters, landmarks, promptAddLandmark}) => {
+const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition, authNavigation, landmarkId, setLandmark, toggleDetailsPanel, setEditing, editingEnabled, visible, toggleLmDetails, place, uri, applyFilters, landmarks, promptAddLandmark, reportingEnabled, toggleReporting, editLmLoc, toggleEditLmLoc, editLandmarkMutation, updatedLandmark, setUpdatedLandmark}) => {
     const {userId, landmarkOwnedByUser} = useAuth()
 
     // /**
@@ -67,10 +75,6 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition,
     //  */
     // const selectedLandmarkState = undefined;
     // const [selectedLandmark, setLandmark] = useState<Landmark | undefined>(selectedLandmarkState);
-    /**
-     * Holds state of a {@link Landmark} object parallel to {@linkcode selectedLandmarkState} that is manipulated when {@linkcode editingEnabled} is true.
-     */
-    const [updatedLandmark, setUpdatedLandmark] = useState<Landmark | undefined>(undefined);
     /**
      * Holds text of the current {@link LMComment} being added to this {@link Landmark}.
      */
@@ -100,7 +104,6 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition,
     const [processingPhoto, setProcessingPhoto] = useState<boolean>(false)
 
     const landmarkQuery = useLandmark(landmarkId)
-    const editLandmarkMutation = useEditLandmark()
     const rateLandmarkMutation = useRateLandmark()
     const tagLandmarkMutation = useTagLandmark()
     const deleteLandmarkMutation = useDeleteLandmark()
@@ -257,6 +260,7 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition,
       {
         text: "Confirm", onPress: async () => {
             await deleteLandmarkMutation.mutateAsync(landmarkId);   
+            setLandmark("")
             toggleDetailsPanel(false);
             Alert.alert("Landmark Deleted", "This landmark has been deleted.");
         }
@@ -285,10 +289,10 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition,
      */
     const focusComment = (id: string) => {
         if (focusedCommentId == id) {
-            setFocusedComment("");
+            setFocusedComment("")
         }
         else {
-            setFocusedComment(id);
+            setFocusedComment(id)
             console.log(uri.substring(uri.length-10, uri.length))
         }
     }
@@ -362,7 +366,9 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition,
                 if (editingEnabled) {
                     Keyboard.dismiss();
                 } else {
-                    toggleLmDetails(false)   
+                    setLandmark("")
+                    toggleLmDetails(false)
+                    toggleReporting(false)
                 }
             }}
             style={{justifyContent: "flex-end", height: '100%', margin: 0}}
@@ -406,7 +412,11 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition,
                     updatedLandmark={updatedLandmark}
                     profile={profile}
                     place={place}
-                    uri = {uri} />
+                    uri = {uri}
+                    reportingEnabled={reportingEnabled}
+                    toggleReporting={toggleReporting}
+                    setFocusedComment={setFocusedComment}
+                    setLandmark={setLandmark} />
                 <DetailsBody
                     authNavigation={authNavigation} 
                     setProcessingPhoto={setProcessingPhoto}
@@ -444,7 +454,12 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition,
                     landmarks={landmarks}
                     toggleDetailsPanel={toggleDetailsPanel}
                     setLandmark={setLandmark}
-                    promptAddLandmark={promptAddLandmark}/>
+                    promptAddLandmark={promptAddLandmark}
+                    reportingEnabled={reportingEnabled}
+                    toggleReporting={toggleReporting}
+                    commentReported={focusedCommentId}
+                    editLmLoc={editLmLoc}
+                    toggleEditLmLoc={toggleEditLmLoc} />
                 </> :
                 <View style={{height: '100%', justifyContent: "space-evenly", alignItems: "center", marginHorizontal: 20}}>
                     <Text style={{color: 'white', fontSize: 20}}>{

+ 84 - 139
src/components/Map/Panels/LandmarkDetailsPanel/Report.tsx

@@ -8,19 +8,18 @@
 import { FontAwesome } from "@expo/vector-icons"
 import React, { useEffect } from "react"
 import { useState } from "react"
-import { Alert, Text, StyleSheet, Pressable, TextInput, TouchableOpacity, View, Keyboard } from "react-native"
-import Modal from "react-native-modal"
+import { Alert, Text, StyleSheet, TextInput, TouchableOpacity, View, Keyboard } from "react-native"
 import Select from "react-native-multiple-select"
-import { LMComment } from "../../../../data/comments"
 import { Landmark } from "../../../../data/landmarks"
 
 
 interface ReportProps {
-    landmark?: Landmark,
-    comment?: LMComment
-    isLandmark: boolean,
+    landmark?: Landmark
+    commentID?: string
+    isLandmark: boolean
     reportLandmark?: (landmark, reasons, extraComments) => void
     reportComment?: (landmark, reasons, extraComments) => void
+    toggleReporting: (state: boolean) => void
 }
 
 export const Report: React.FC<ReportProps> = (props) => {
@@ -56,165 +55,111 @@ export const Report: React.FC<ReportProps> = (props) => {
         }
         props.isLandmark ?
             props.reportLandmark(props.landmark, reasonsToString, text)
-            : props.reportComment(props.comment, reasonsToString, text)
+            : props.reportComment(props.commentID, reasonsToString, text)
         Alert.alert('Report submitted', 'Thank you for making The Atlas a safer place!')
-        setModalVisible(!modalVisible)
+        props.toggleReporting(false)
     }
 
-    const [modalVisible, setModalVisible] = useState(false)
     const [text, onChangeText] = React.useState('')
     const [selectedReasons, setSelectedReasons] = useState([])
 
     const reasons = props.isLandmark ?
         [
-            { value: '92iijs7yta', label: 'Landmark does not exist' },
-            { value: 'hsyasajs', label: 'Inaccurate tag' },
-            { value: 'a0s0a8ssbsd', label: 'Spam' },
-            { value: '16hbajsabsd', label: 'Sexually explicit material' },
-            { value: 'nahs75a5sg', label: 'Hate speech or symbols' },
-            { value: '667atsas', label: 'Promotion of violence' },
+            { value: '1', label: 'Landmark does not exist' },
+            { value: '2', label: 'Inaccurate tag' },
+            { value: '3', label: 'Spam' },
+            { value: '4', label: 'Sexually explicit material' },
+            { value: '5', label: 'Hate speech or symbols' },
+            { value: '6', label: 'Promotion of violence' },
         ]
         :
         [
-            { value: '92iijs7ytb', label: 'Misinformation' },
-            { value: 'a0s0a8ssbse', label: 'Spam' },
-            { value: '16hbajsabse', label: 'Sexually explicit material' },
-            { value: 'nahs75a5sh', label: 'Hate speech or symbols' },
-            { value: '667atsat', label: 'Promotion of violence' },
-            { value: 'hsyasajt', label: 'Harassment or bullying' },
-            { value: 'djsjudksje', label: 'Impersonation' }
+            { value: '1', label: 'Misinformation' },
+            { value: '2', label: 'Spam' },
+            { value: '3', label: 'Sexually explicit material' },
+            { value: '4', label: 'Hate speech or symbols' },
+            { value: '5', label: 'Promotion of violence' },
+            { value: '6', label: 'Harassment or bullying' },
+            { value: '7', label: 'Impersonation' }
         ]
 
     return (
         <View>
-            <TouchableOpacity onPress={() => { setModalVisible(!modalVisible) }}>
-                <View>
-                    {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  // Pop-up modal that contains the entire report interface
-                backdropTransitionOutTiming={0}
-                hideModalContentWhileAnimating={true}
-                isVisible={modalVisible}
-                animationIn={'slideInUp'}
-                animationOut={'slideOutDown'}
-                useNativeDriver={true}
-                useNativeDriverForBackdrop={true}
-                onBackdropPress={() => setModalVisible(false)}>
-                <View style={styles.modalView}>
-                    <View style={{ width: '100%' }}>
-                        {props.isLandmark ?
-                            <Text style={styles.modalText}>Report Landmark</Text>
-                            :
-                            <Text style={styles.modalText}>Report Comment</Text>
-                        }
-                        <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  // Dropdown list to select any number of reasons for the report
-                                    styleRowList={{ borderColor: "red", borderWidth: 0 }}
-                                    textColor='black'
-                                    itemTextColor='black'
-                                    items={reasons}
-                                    displayKey="label"
-                                    uniqueKey="value"
-                                    submitButtonText="Confirm"
-                                    submitButtonColor='black'
-                                    onSelectedItemsChange={onSelectedReasonsChange}
-                                    selectedItems={selectedReasons}>
-                                </Select>
-                            </View>
-                        </View>
-                        <TextInput  // Text box if the user wants to add extra comments to the report
-                            style={styles.input}
-                            onChangeText={onChangeText}
-                            value={text}
-                            placeholder="Optional extra feedback..."
-                            keyboardType="default"
-                        />
+            {props.isLandmark ?
+                <Text style={{ color: 'white', marginBottom: 20, fontSize: 15 }}>Report Landmark</Text>
+                :
+                <Text style={{ color: 'white', marginBottom: 20, fontSize: 15 }}>Report Comment</Text>
+            }
+            <View style={{ width: '100%' }}>
+                <View style={{ justifyContent: 'flex-start' }}>
+                    <Text style={{ marginRight: 10, marginBottom: 5, color: 'white' }}>Reason(s) for reporting:</Text>
+                    <View style={{ alignSelf: 'stretch', borderColor: "blue", borderWidth: 0, width: '100%', justifyContent: 'center' }}>
+                        <Select  // Dropdown list to select any number of reasons for the report
+                            styleRowList={{ borderColor: "red", borderWidth: 0 }}
+                            styleIndicator={{ backgroundColor: "white" }}
+                            textColor='black'
+                            itemTextColor='black'
+                            items={reasons}
+                            displayKey="label"
+                            uniqueKey="value"
+                            submitButtonText="Confirm"
+                            submitButtonColor='black'
+                            onSelectedItemsChange={onSelectedReasonsChange}
+                            selectedItems={selectedReasons}
+                            textInputProps={{ editable: false, autoFocus: false }}
+                            searchIcon={false}
+                            searchInputPlaceholderText=''
+                            tagBorderColor='white'
+                            tagTextColor='white'
+                            tagRemoveIconColor='white'
+                            selectedItemTextColor='red'
+                            selectedItemIconColor='red'>
+                        </Select>
                     </View>
-                    {!keyboardIsVisible && <View style={{ flexDirection: 'row' }}>
-                        <Pressable
-                            style={styles.cancel}
-                            onPress={() => { setModalVisible(!modalVisible) }}>
-                            <Text style={{ color: 'white', textAlign: 'center' }}>Cancel</Text>
-                        </Pressable>
-                        <Pressable
-                            style={styles.submit}
-                            onPress={() =>
-                                Alert.alert(
-                                    'Are you sure you want to submit this report?',
-                                    'This action cannot be undone.',
-                                    [
-                                        { text: 'Cancel', onPress: () => console.log('Report cancelled'), style: 'cancel' },
-                                        { text: 'OK', onPress: sendReport },
-                                    ],
-                                    { cancelable: false }
-                                )}>
-                            <Text style={{ color: 'white', textAlign: 'center' }}>Submit</Text>
-                        </Pressable>
-                    </View>}
                 </View>
-            </Modal>
+                <Text style={{ marginRight: 10, marginBottom: 5, marginTop: 15, color: 'white' }}>
+                    Optional extra comments: <FontAwesome size={16} name="question-circle-o" onPress={() => Alert.alert("", "This is space to provide any additional info about the report that you feel may not be captured by the reason(s) for reporting")} />      
+                </Text>
+                <TextInput  // Text box if the user wants to add extra comments to the report
+                    style={styles.input}
+                    onChangeText={onChangeText}
+                    value={text}
+                    placeholder="Optional extra comments..."
+                    placeholderTextColor='gray'
+                    keyboardType="default"
+                />
+            </View>
+            {!keyboardIsVisible &&
+                <View style={{ justifyContent: 'flex-end', flexDirection: 'row', paddingHorizontal: 20, paddingBottom: 10, marginTop: 5 }}>
+                    <TouchableOpacity onPress={() => props.toggleReporting(false)}><Text style={{ color: 'white', marginRight: 25 }}>Cancel</Text></TouchableOpacity>
+                    <TouchableOpacity
+                        onPress={() =>
+                            Alert.alert(
+                                'Are you sure you want to submit this report?',
+                                'This action cannot be undone.',
+                                [
+                                    { text: 'Cancel', onPress: () => console.log('Report cancelled'), style: 'cancel' },
+                                    { text: 'OK', onPress: sendReport },
+                                ],
+                                { cancelable: false }
+                            )}>
+                        <Text style={{ color: 'white' }}>Submit Report</Text>
+                    </TouchableOpacity>
+                </View>
+            }
         </View>
     )
 }
 
 const styles = StyleSheet.create({
-    modalView: {
-        flex: 1,
-        justifyContent: 'space-between',
-        alignSelf: 'stretch',
-        margin: 20,
-        backgroundColor: 'white',
-        borderRadius: 20,
-        padding: 35,
-        alignItems: 'center',
-        shadowColor: '#000',
-        shadowOffset: {
-            width: 0,
-            height: 2,
-        },
-        shadowOpacity: 0.25,
-        shadowRadius: 4,
-        elevation: 5,
-    },
-    modalText: {
-        marginBottom: 15,
-        textAlign: 'center',
-    },
     input: {
         height: 40,
-        margin: 12,
-        marginTop: 60,
+        marginBottom: 20,
         borderWidth: 1,
-        borderRadius: 10,
+        borderRadius: 0,
         padding: 10,
-    },
-    cancel: {
-        width: 100,
-        height: 40,
-        margin: 12,
-        borderWidth: 1,
-        borderRadius: 10,
         borderColor: 'white',
-        padding: 10,
-        backgroundColor: '#df3f3f',
-        color: 'white'
+        backgroundColor: 'white'
+
     },
-    submit: {
-        width: 100,
-        height: 40,
-        margin: 12,
-        borderWidth: 1,
-        borderRadius: 10,
-        borderColor: 'white',
-        padding: 10,
-        backgroundColor: '#3fafdf',
-        color: 'white'
-    }
 })

+ 1 - 1
src/components/Profile/ProfileHeader.tsx

@@ -21,7 +21,7 @@ export const ProfileHeader: React.FC<{ profile?: UserProfile }> = memo(({ profil
     const delay = ms => new Promise(res => setTimeout(res, ms));
 
     const getLandmarkCount = () => {
-        return landmarkQuery?.data?.filter(lm => landmarkOwnedByUser(lm)).length
+        return landmarkQuery?.data?.filter(lm => landmarkOwnedByUser(lm) && lm.landmark_type != 30).length
     }
 
     const getTotalLandmarkRating = () => {

+ 1 - 1
src/data/comments.ts

@@ -163,7 +163,7 @@ export const useReportComment = () => {
     const {sendApiRequestAsync} = useAuth()
     const queryClient = useQueryClient();
 
-    const reportComment =  async (content: string) => {
+    const reportComment =  async (content: {}) => {
         if (content) {
             const response = await sendApiRequestAsync({
                 axiosConfig:{

+ 0 - 26
src/data/profiles.ts

@@ -207,30 +207,4 @@ export const useToggleTips = () => {
         onSuccess: () => { queryClient.invalidateQueries(queryKeys.getOwnedProfile)},  
         onError: () => queryClient.invalidateQueries(queryKeys.getOwnedProfile),  
     })
-}
-
-
-export const useToggleGroupLMTip = () => {
-    const {sendApiRequestAsync, userId} = useAuth()
-    const queryClient = useQueryClient();
-
-    const toggleGroupTip = async () => {
-        if (userId) { 
-            const response = await sendApiRequestAsync({
-                axiosConfig: {
-                    method: 'POST',
-                    url: `/api/user-profile/toggle-group-lm-tip/${userId}/`,
-                },
-                authorized: true,
-                errorMessage: 'Something went wrong when toggling group landmark tip',
-                loggingCategory: 'PROFILE',
-            });   
-            return response.data;
-        }
-    }
-
-    return useMutation(toggleGroupTip, {
-        onSuccess: () => { queryClient.invalidateQueries(queryKeys.getOwnedProfile)},  
-        onError: () => queryClient.invalidateQueries(queryKeys.getOwnedProfile),  
-    })
 }

+ 39 - 18
src/navigation/MapNavigator.tsx

@@ -4,7 +4,7 @@ import { createNativeStackNavigator } from "@react-navigation/native-stack"
 import { StackNavigationProp } from "@react-navigation/stack"
 import { observer } from "mobx-react"
 import React, { useEffect, useState } from "react"
-import { Alert, Dimensions, Image, ScrollView, View, Linking, Text } from "react-native"
+import { Image, ScrollView, View, Linking, Text } from "react-native"
 import { Chip } from "react-native-paper"
 import { IconButton } from "../components/Buttons"
 import IndoorMap from "../components/Map/MainMapComponent/IndoorMap"
@@ -15,17 +15,14 @@ import AddLandmarkPanel from "../components/Map/Panels/AddLandmarkPanel"
 import { FilterPanel } from "../components/Map/Panels/FilterPanel/FilterPanel"
 import LandmarkDetails from "../components/Map/Panels/LandmarkDetailsPanel/LandmarkDetails"
 import { useAuth } from "../data/Auth/AuthContext"
-import { Landmark, useLandmarks } from '../data/landmarks'
+import { Landmark, useEditLandmark, useLandmarks } from '../data/landmarks'
 import { colors, lmTypes, } from "../utils/GlobalUtils"
 import { MainTabsNavigationProp } from "./MainTabsNavigator"
-
 import { Menu, MenuItem, MenuDivider } from 'react-native-material-menu';
 import { navigate } from "./RootNavigator"
-import { Separator } from "../components/Separator"
 import { MapEvent } from "react-native-maps"
 
 
-
 const MapStackNavigator = createNativeStackNavigator()
 
 export type MapStackParamList = {
@@ -51,6 +48,14 @@ const MapNavigator: React.FC = ({ }) => {
     const [currentRoute, setCurrentRoute] = useState<string>()
 
     const [visible, setVisible] = useState(false);
+    const [showPreviewPin, togglePreviewPin] = useState<boolean>(false)
+    const [editLmLoc, toggleEditLmLoc] = useState<boolean>(false)
+
+    /**
+     * Holds state of a {@link Landmark} object parallel to {@linkcode selectedLandmarkState} that is manipulated when {@linkcode editingEnabled} is true.
+     */
+    const [updatedLandmark, setUpdatedLandmark] = useState<Landmark | undefined>(undefined);
+    const editLandmarkMutation = useEditLandmark()
 
     useEffect(() => {
         const currentRouteIndex = navigationState?.routes[0]?.state?.index
@@ -96,14 +101,14 @@ const MapNavigator: React.FC = ({ }) => {
 
         return landmarks
     }
+    const delay = ms => new Promise(res => setTimeout(res, ms));
 
     /**
      * Triggered by on a {@link Landmark} displayed on the map. 
      * Sets {@linkcode selectedLandmark} to the pressed {@link Landmark} object's value and toggles the {@link LandmarkDetails} modal.
      */
-    const focusLandmark = (landmark: Landmark, e?: MapEvent<{action: "marker-press"; id: string;}>) => {
+    const focusLandmark = async (landmark: Landmark, e?: MapEvent<{action: "marker-press"; id: string;}>) => {
         console.log("e = " + e?.nativeEvent?.id);
-        // const {x, y} = e?.nativeEvent?.position; // this causes an error since e is undefined for indoors
         const x = landmark.latitude;
         const y = landmark.longitude;
         setMarkerWindowPosition({x: x, y: y});
@@ -112,6 +117,7 @@ const MapNavigator: React.FC = ({ }) => {
             mapState.toggleLmDetails(true)
         }
         else {
+            await delay(200)
             mapState.setSelectedLandmarkId(landmark.id);
         }
     }
@@ -135,11 +141,11 @@ const MapNavigator: React.FC = ({ }) => {
         if (lmCount == "groupItem") {  // Adding landmark to an already established group
             mapState.setNewLandmark({ latitude: latitude, longitude: longitude, floor: floor, voice: false, parent: parent, groupCount: -1 });
             mapState.toggleLmAdd(true)
-            mapState.toggleLmDetails(false)
+            // mapState.toggleLmDetails(false)
         } else if (lmCount == "newGroup") {  // Adding 2nd landmark to existing landmark, thus making a new group
             mapState.setNewLandmark({ latitude: latitude, longitude: longitude, floor: floor, voice: false, parent: parent, groupCount: -2 });
             mapState.toggleLmAdd(true)
-            mapState.toggleLmDetails(false)
+            // mapState.toggleLmDetails(false)
         } else {  // Adding a regular individial landmark
             mapState.setNewLandmark({ latitude: latitude, longitude: longitude, floor: floor, voice: false });
             mapState.toggleLmAdd(true)
@@ -169,9 +175,10 @@ const MapNavigator: React.FC = ({ }) => {
                             selectedLandmarkId={mapState.selectedLandmarkId}
                             setSelectedLandmarkId={mapState.setSelectedLandmarkId}
                             setMarkerWindowPosition={setMarkerWindowPosition}
-                            getLmUri={getLmUri} />}
+                            getLmUri={getLmUri}
+                            showPreviewPin={showPreviewPin}
+                            togglePreviewPin={togglePreviewPin} />}
                 </MapStackNavigator.Screen>
-
                 <MapStackNavigator.Screen name="Indoor" >
                     {({ navigation }) =>
                         <IndoorMap
@@ -180,9 +187,17 @@ const MapNavigator: React.FC = ({ }) => {
                             promptAddLandmark={promptAddLandmark}
                             focusLandmark={focusLandmark}
                             applyFilter={applyFilters}
+                            togglePreviewPin={togglePreviewPin}
+                            showPreviewPin={showPreviewPin}
+                            selectedLandmarkId={mapState.selectedLandmarkId}
+                            editLmLoc={editLmLoc}
+                            toggleEditLmLoc={toggleEditLmLoc}
+                            toggleLmDetails={mapState.toggleLmDetails}
+                            editLandmarkMutation={editLandmarkMutation}
+                            updatedLandmark={updatedLandmark}
+                            setUpdatedLandmark={setUpdatedLandmark}
                         />}
                 </MapStackNavigator.Screen>
-
             </MapStackNavigator.Navigator>
 
 
@@ -226,6 +241,8 @@ const MapNavigator: React.FC = ({ }) => {
                     >
                         <MenuItem onPress={() => {
                             setVisible(false)
+                            togglePreviewPin(false)
+                            toggleEditLmLoc(false)
                             navigate("Outdoor")
                             mapState.setPlace("Outdoor")
                             // Alert.alert("Cameron Library")
@@ -259,6 +276,7 @@ const MapNavigator: React.FC = ({ }) => {
                         
                         <MenuItem onPress={() => {
                             setVisible(false)
+                            togglePreviewPin(false)
                             navigate("Indoor")
                             mapState.setPlace("Cameron")
                             // Alert.alert("Cameron Library")
@@ -271,9 +289,6 @@ const MapNavigator: React.FC = ({ }) => {
 
                 </View>
             }
-
-
-
             {/* {!mapState.filterVisible ?
                 <View style={{ top: 10, marginLeft: 40, marginRight: 20, position: 'absolute', flexDirection: "row-reverse", justifyContent: 'flex-end' }}>
                     <IconButton size={20} color={colors.red} style={[mapStyles.filterButton]} icon="filter" onPress={() => mapState.toggleFilter(true)} />
@@ -288,14 +303,13 @@ const MapNavigator: React.FC = ({ }) => {
                     </ScrollView>
                 </View>
                  : null}                */}
-
-
             <AddLandmarkPanel
                 setNewLandmark={mapState.setNewLandmark}
                 setVisible={mapState.toggleLmAdd}
                 newLandmark={mapState.newLandmark}
                 visible={mapState.lmAddVisible}
-                siblingID={mapState.selectedLandmarkId} />
+                siblingID={mapState.selectedLandmarkId}
+                togglePreviewPin={togglePreviewPin} />
             <LandmarkDetails
                 markerWindowPosition={markerWindowPosition}
                 authNavigation={authNavigation}
@@ -311,6 +325,13 @@ const MapNavigator: React.FC = ({ }) => {
                 applyFilters = {applyFilters}
                 landmarks={landmarksQuery?.data}
                 promptAddLandmark={promptAddLandmark}
+                reportingEnabled={mapState.reportingEnabled}
+                toggleReporting={mapState.toggleReportingEnabled}
+                editLmLoc={editLmLoc}
+                toggleEditLmLoc={toggleEditLmLoc}
+                editLandmarkMutation={editLandmarkMutation}
+                updatedLandmark={updatedLandmark}
+                setUpdatedLandmark={setUpdatedLandmark}
                 />
             <FilterPanel
                 visible={mapState.filterVisible}

+ 2 - 2
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://192.168.1.87:8000'  // Aidan home
+export const API_URL = 'http://192.168.1.77:8000'  // Aidan home
 
 // export const API_URL = Config.API_URL

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 303 - 344
yarn.lock


Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels