Просмотр исходного кода

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

aidan 1 год назад
Родитель
Сommit
2653dfa012

+ 1 - 1
app.json

@@ -46,7 +46,7 @@
     "runtimeVersion": "1.0.1",
     "runtimeVersion": "1.0.1",
     "extra": {
     "extra": {
       "eas": {
       "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"
         "gradleCommand": ":app:assembleDebug"
       },
       },
       "ios": {
       "ios": {
-        "buildConfiguration": "Release",
+        "buildConfiguration": "Debug",
         "image": "macos-monterey-12.1-xcode-13.2"
         "image": "macos-monterey-12.1-xcode-13.2"
       }
       }
     },
     },
     "preview": {
     "preview": {
-      "distribution": "store",
+      "distribution": "internal",
       "ios": {
       "ios": {
-        "buildConfiguration": "Release",
+        "buildConfiguration": "Debug",
         "image": "macos-monterey-12.1-xcode-13.2"
         "image": "macos-monterey-12.1-xcode-13.2"
       }
       }
     },
     },

Разница между файлами не показана из-за своего большого размера
+ 678 - 221
package-lock.json


+ 1 - 0
package.json

@@ -80,6 +80,7 @@
     "react-native-device-info": "^8.6.0",
     "react-native-device-info": "^8.6.0",
     "react-native-dialog": "^9.2.0",
     "react-native-dialog": "^9.2.0",
     "react-native-dotenv": "^3.3.0",
     "react-native-dotenv": "^3.3.0",
+    "react-native-draggable": "^3.3.0",
     "react-native-fast-image": "^8.5.11",
     "react-native-fast-image": "^8.5.11",
     "react-native-fs": "^2.19.0",
     "react-native-fs": "^2.19.0",
     "react-native-gesture-handler": "~1.10.2",
     "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 { FontAwesome } from "@expo/vector-icons";
 import ReactNativeZoomableView from '@openspacelabs/react-native-zoomable-view/src/ReactNativeZoomableView';
 import ReactNativeZoomableView from '@openspacelabs/react-native-zoomable-view/src/ReactNativeZoomableView';
 import React, { useEffect, useState } from 'react';
 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 Picker from 'react-native-picker-select';
 import Toast from 'react-native-root-toast';
 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 { Landmark } from '../../../data/landmarks';
 import { MapStackNavigationProp } from "../../../navigation/MapNavigator";
 import { MapStackNavigationProp } from "../../../navigation/MapNavigator";
 import { colors, lmTypesIndoor } from "../../../utils/GlobalUtils";
 import { colors, lmTypesIndoor } from "../../../utils/GlobalUtils";
 import IndoorFloor from '../IndoorFloor';
 import IndoorFloor from '../IndoorFloor';
 import ArrowButton from './ArrowButton';
 import ArrowButton from './ArrowButton';
-
+import mapStyles from "./Map.styles";
 
 
 
 
 interface IndoorMapProps {
 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({
 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: {
     alertButton: {
         bottom: 70,
         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
  * <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 { RouteProp, useNavigationState } from "@react-navigation/native";
 import { booleanPointInPolygon, circle } from '@turf/turf';
 import { booleanPointInPolygon, circle } from '@turf/turf';
 import * as Notifications from 'expo-notifications';
 import * as Notifications from 'expo-notifications';
 import { observer } from "mobx-react";
 import { observer } from "mobx-react";
 import React, { useEffect, useState } from "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 { LatLng, LongPressEvent, MapEvent, Marker, Polygon, Polyline } from "react-native-maps";
 import { openSettings } from "react-native-permissions";
 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 { useAuth } from "../../../data/Auth/AuthContext";
 import { Landmark } from '../../../data/landmarks';
 import { Landmark } from '../../../data/landmarks';
 import { NotifType } from "../../../data/notifications";
 import { NotifType } from "../../../data/notifications";
@@ -67,6 +67,8 @@ interface OutdoorMapProps {
     promptAddLandmark: (longitude: number, latitude: number, floor?: number, lmCount?: string, parent?: string) => void
     promptAddLandmark: (longitude: number, latitude: number, floor?: number, lmCount?: string, parent?: string) => void
     setMarkerWindowPosition: (point: {x: number, y: number}) => void
     setMarkerWindowPosition: (point: {x: number, y: number}) => void
     getLmUri: (uri: String) => void
     getLmUri: (uri: String) => void
+    showPreviewPin: boolean
+    togglePreviewPin: (state: boolean) => void
 }
 }
 
 
 const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
 const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
@@ -84,6 +86,22 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
     ]);
     ]);
     const [size, setSize] = useState(0.052344);
     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 
      * 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)
      * (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 (
     return (
         <TouchableWithoutFeedback>
         <TouchableWithoutFeedback>
             <>
             <>
@@ -255,6 +284,7 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
                     </View>
                     </View>
                 </Modal>
                 </Modal>
                 <MapView
                 <MapView
+                    radius={10}
                     key={mapStateOutdoor.refreshKey}
                     key={mapStateOutdoor.refreshKey}
                     toolbarEnabled={false}
                     toolbarEnabled={false}
                     onPress={() => Keyboard.dismiss()}
                     onPress={() => Keyboard.dismiss()}
@@ -262,7 +292,7 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
                     ref={mapStateOutdoor.mapRef}
                     ref={mapStateOutdoor.mapRef}
                     style={{ width: '100%', height: '100%' }}
                     style={{ width: '100%', height: '100%' }}
                     initialRegion={getInitialRegion()}
                     initialRegion={getInitialRegion()}
-                    onLongPress={async (e) => await props.promptAddLandmark(e.nativeEvent.coordinate.longitude, e.nativeEvent.coordinate.latitude)}
+                    onLongPress={async (e) => chooseLocation(e)}
                     showsUserLocation={locationPermissionsGranted}
                     showsUserLocation={locationPermissionsGranted}
                     onUserLocationChange={e => updateLocation(e.nativeEvent.coordinate)}
                     onUserLocationChange={e => updateLocation(e.nativeEvent.coordinate)}
                     followsUserLocation={mapStateOutdoor.followUser}
                     followsUserLocation={mapStateOutdoor.followUser}
@@ -293,7 +323,14 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
                             props.mapNavigation.navigate("Indoor")}
                             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) => {
                     {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.groupCount !== -1) {  // -1 means it is part of a group so don't render a marker for it
                             if (landmark.floor == null) {
                             if (landmark.floor == null) {
@@ -303,9 +340,12 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
                                 }
                                 }
                                 return (
                                 return (
                                     <Marker
                                     <Marker
+                                        opacity={landmark?.id == activeLandmark ? 1 : lmOpacity}
                                         tracksViewChanges={trackChanges}
                                         tracksViewChanges={trackChanges}
                                         coordinate={{ latitude: landmark.latitude as number, longitude: landmark.longitude as number }}
                                         coordinate={{ latitude: landmark.latitude as number, longitude: landmark.longitude as number }}
                                         onPress={async e => {
                                         onPress={async e => {
+                                            setActiveLandmark(landmark.id)
+                                            setLmOpacity(0.3)
                                             e.persist()
                                             e.persist()
                                             props.focusLandmark(landmark, e)
                                             props.focusLandmark(landmark, e)
                                             await takeSnapshot();
                                             await takeSnapshot();
@@ -317,7 +357,7 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
                                             landmark.landmark_type == 30 ?  // Landmark group
                                             landmark.landmark_type == 30 ?  // Landmark group
                                                 <ImageBackground style={{ height: 35, width: 25, zIndex: 10 }} source={lmTypes[landmark.landmark_type]?.image}> 
                                                 <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'}}>
                                                     <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>
                                                     </View>
                                                 </ImageBackground>
                                                 </ImageBackground>
                                                 : <Image style={{ height: 35, width: 25, zIndex: 10 }} source={lmTypes[landmark.landmark_type]?.image} />
                                                 : <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"
                         strokeColor="#111111"
                         mode="WALKING"
                         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')} /> */}
                         {/* <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>
                         <Text style={{ fontSize: size>0.00327 ? 0 : 0.05/size , maxWidth:200, }}>Route from University Station to Cameron Library</Text>
                     </Marker>
                     </Marker>
@@ -416,6 +458,16 @@ const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
                         newLandmark={props.newLandmark}
                         newLandmark={props.newLandmark}
                         setNewLandmark={props.setNewLandmark}
                         setNewLandmark={props.setNewLandmark}
                     /> : null}
                     /> : 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>)
         </TouchableWithoutFeedback>)
 }
 }

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

@@ -59,6 +59,11 @@ export const useMapState = () => {
 
 
     const [uri, setUri] = useState<String>('')
     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 { 
     return { 
         onlyOwned, toggleOnlyOwned,
         onlyOwned, toggleOnlyOwned,
         lmFilteredTypes, setLmTypeFilter,
         lmFilteredTypes, setLmTypeFilter,
@@ -70,7 +75,8 @@ export const useMapState = () => {
         newLandmark, setNewLandmark,
         newLandmark, setNewLandmark,
         lmAddVisible, toggleLmAdd,
         lmAddVisible, toggleLmAdd,
         place, setPlace,
         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
  * <dev@clicknpush.ca>, January 2022
  */
  */
 
 
-import { FontAwesome } from "@expo/vector-icons";
 import { ImageInfo } from "expo-image-picker/build/ImagePicker.types";
 import { ImageInfo } from "expo-image-picker/build/ImagePicker.types";
 import React, { memo, useEffect, useState, useRef } from "react";
 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 { ScrollView } from "react-native-gesture-handler";
 import Modal from 'react-native-modal';
 import Modal from 'react-native-modal';
-import Picker from 'react-native-picker-select';
 import { Landmark, LMPhoto, useAddLandmark } from "../../../data/landmarks";
 import { Landmark, LMPhoto, useAddLandmark } from "../../../data/landmarks";
 import { colors, lmTypes as allLmTypes, lmTypesIndoor, catTypes, catTypesInGroup } from "../../../utils/GlobalUtils";
 import { colors, lmTypes as allLmTypes, lmTypesIndoor, catTypes, catTypesInGroup } from "../../../utils/GlobalUtils";
 import { IconButton, SecondaryButton } from "../../Buttons";
 import { IconButton, SecondaryButton } from "../../Buttons";
 import { PhotoPicker } from "../../PhotoPicker";
 import { PhotoPicker } from "../../PhotoPicker";
 import TouchOpaq from "./LandmarkDetailsPanel/TouchOpaq";
 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 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 { useNavigationState } from "@react-navigation/native"
 import LandmarkTypePicker from "../../LandmarkTypePicker";
 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 DatePicker from 'react-native-date-picker'
 import CheckBox from "@react-native-community/checkbox";
 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. 
      * The currently selected {@link Landmark} object, used only when a landmark is added to an existing individual landmark. 
      */
      */
     siblingID?: string | undefined
     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
  * @component
  * @category Map
  * @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 [photos, setPhotos] = useState<LMPhoto[]>([])
     const [photoSourceMenuOpened, togglePhotoSourceMenu] = useState<boolean>(false)
     const [photoSourceMenuOpened, togglePhotoSourceMenu] = useState<boolean>(false)
     const [keyboardOpened, setKeyboardOpened] = 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)
     const [showExpire, toggleShowExpire] = useState<boolean>(false)
     var minDate = new Date();
     var minDate = new Date();
     const {profile} = useOwnedProfile()
     const {profile} = useOwnedProfile()
-    const toggleGroupLMTip = useToggleGroupLMTip()
 
 
     const addLandmarkMutation = useAddLandmark()
     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.
      * Calls {@link addLandmarkAsync} from {@link useLandmarks} to initate the process of adding a landmark, then closes the modal.
      */
      */
     const submit = async () => {
     const submit = async () => {
+        togglePreviewPin(false)
         let newGroupBool: boolean = false
         let newGroupBool: boolean = false
         if (newLandmark?.groupCount == -2) {
         if (newLandmark?.groupCount == -2) {
             newGroupBool = true;
             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 { format, parseISO } from "date-fns"
 import { TouchableOpacity, View, Text, StyleSheet, ImageSourcePropType } from "react-native"
 import { TouchableOpacity, View, Text, StyleSheet, ImageSourcePropType } from "react-native"
 import { useAuth } from "../../../../data/Auth/AuthContext"
 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 { Svg, Image as ImageSVG } from "react-native-svg"
 import IndoorFloor from "../../IndoorFloor"
 import IndoorFloor from "../../IndoorFloor"
 import { lmTypes } from "../../../../utils/GlobalUtils"
 import { lmTypes } from "../../../../utils/GlobalUtils"
@@ -33,46 +32,22 @@ export interface CommentProps {
     startEditingComment: (comment: LMComment) => void
     startEditingComment: (comment: LMComment) => void
     deleteComment: (id: string) => void
     deleteComment: (id: string) => void
     uri: String
     uri: String
+    toggleReporting: (state: boolean) => void,
 }
 }
 
 
 /**
 /**
  * Component that displays a {@link LMComment} object in a clean format.
  * Component that displays a {@link LMComment} object in a clean format.
  * @component
  * @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 { userId } = useAuth()
     const getLandmarkMutation = useGetLandmark(comment.landmark)
     const getLandmarkMutation = useGetLandmark(comment.landmark)
     const landmark = getLandmarkMutation.data.landmark
     const landmark = getLandmarkMutation.data.landmark
-    const reportCommentMutation = useReportComment()
 
 
     const capture: any = React.createRef();
     const capture: any = React.createRef();
     const imgWidth = 346
     const imgWidth = 346
     const imgHeight = 448
     const imgHeight = 448
     const imageDim = 25
     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 (
     return (
         <TouchableOpacity style={[{ paddingHorizontal: 10 }, selected ? { backgroundColor: '#E8E8E8' } : null]} onPress={() => selectComment(comment.id)}>
         <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> :
                             <View style={{ marginTop: 10, flexDirection: 'row', alignSelf: 'flex-end' }}>
                             <View style={{ marginTop: 10, flexDirection: 'row', alignSelf: 'flex-end' }}>
                                 {userId ?  // Only show the report button if the user is logged in
                                 {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}
                                     : null}
                             </View>
                             </View>
                         : null}
                         : 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 { useAuth } from "../../../../data/Auth/AuthContext";
 import { LMComment } from "../../../../data/comments";
 import { LMComment } from "../../../../data/comments";
 import { MainTabsNavigationProp } from "../../../../navigation/MainTabsNavigator";
 import { MainTabsNavigationProp } from "../../../../navigation/MainTabsNavigator";
-import { navigate } from "../../../../navigation/RootNavigator";
-import { PrimaryButton, SecondaryButton } from "../../../Buttons";
+import { SecondaryButton } from "../../../Buttons";
 import { CommentView } from "./CommentView";
 import { CommentView } from "./CommentView";
 
 
 interface CommentsContainerProps {
 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
     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,
     newCommentId?: string,
-    editComment: (comment: LMComment) => void,
+    editComment: (comment: LMComment) => void
     addComment: () => void
     addComment: () => void
     commentListRef: MutableRefObject<FlatList>
     commentListRef: MutableRefObject<FlatList>
     commentTextInputRef: MutableRefObject<TextInput>
     commentTextInputRef: MutableRefObject<TextInput>
@@ -35,6 +34,7 @@ interface CommentsContainerProps {
     authNavigation: MainTabsNavigationProp
     authNavigation: MainTabsNavigationProp
     toggleLmDetails: (state: boolean) => void
     toggleLmDetails: (state: boolean) => void
     uri: String
     uri: String
+    toggleReporting: (state: boolean) => void
 }
 }
 
 
 /**
 /**
@@ -57,7 +57,7 @@ export const CommentsContainer: React.FC<CommentsContainerProps> = (props) => {
             <View>
             <View>
                 {/* Some conditional rendering to make the borders look nice */}
                 {/* 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> }
                 {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>
             </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 { useNavigationState } from "@react-navigation/native";
 import React, { MutableRefObject, useEffect, useRef, useState } from "react";
 import React, { MutableRefObject, useEffect, useRef, useState } from "react";
 import { FlatList, Dimensions, Image, ScrollView, StyleSheet, Text, TextInput, View, TouchableOpacity } from "react-native";
 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 { 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 { usePermissions } from "../../../../data/PermissionsContext";
 import { MainTabsNavigationProp } from "../../../../navigation/MainTabsNavigator";
 import { MainTabsNavigationProp } from "../../../../navigation/MainTabsNavigator";
 import { lmTypes as allLmTypes, lmTypesIndoor, catTypes, catTypesInGroup } from "../../../../utils/GlobalUtils";
 import { lmTypes as allLmTypes, lmTypesIndoor, catTypes, catTypesInGroup } from "../../../../utils/GlobalUtils";
@@ -21,11 +20,13 @@ import LandmarkTypePicker from "../../../LandmarkTypePicker";
 import { Separator } from "../../../Separator";
 import { Separator } from "../../../Separator";
 import { CommentsContainer } from "./CommentsContainer";
 import { CommentsContainer } from "./CommentsContainer";
 import { LandmarkPhotos } from "./LandmarkPhotos";
 import { LandmarkPhotos } from "./LandmarkPhotos";
-import { useOutdoorMapState } from "../../MainMapComponent/useMapState";
 import { TagContainer } from "./TagContainer";
 import { TagContainer } from "./TagContainer";
 import CheckBox from "@react-native-community/checkbox";
 import CheckBox from "@react-native-community/checkbox";
 import DatePicker from "react-native-date-picker";
 import DatePicker from "react-native-date-picker";
 import { useAuth } from "../../../../data/Auth/AuthContext";
 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,
     focusComment: (id: string) => void,
     startEditingComment: (comment: LMComment) => void,
     startEditingComment: (comment: LMComment) => void,
     deleteComment: (id: string) => void
     deleteComment: (id: string) => void
-    commentBeingEdited?: LMComment, 
+    commentBeingEdited?: LMComment,
     setCommentBeingEdited: (comment: LMComment) => void,
     setCommentBeingEdited: (comment: LMComment) => void,
     setNewComment: (commentId: string) => void,
     setNewComment: (commentId: string) => void,
     newCommentId?: string,
     newCommentId?: string,
     editComment: (comment: LMComment) => void,
     editComment: (comment: LMComment) => void,
     addComment: () => void
     addComment: () => void
-    setSelectedImage: (index: number) => void 
+    setSelectedImage: (index: number) => void
     tryDeletePhoto: (photoId: string) => void
     tryDeletePhoto: (photoId: string) => void
     addPhoto: (photo: LMPhoto) => void
     addPhoto: (photo: LMPhoto) => void
     addPhotoStatus: QueryStatus
     addPhotoStatus: QueryStatus
-    deletePhotoStatus: QueryStatus 
+    deletePhotoStatus: QueryStatus
     toggleLmDetails: (state: boolean) => void
     toggleLmDetails: (state: boolean) => void
     setKeyboardOpened: (state: boolean) => void
     setKeyboardOpened: (state: boolean) => void
     keyboardOpened: boolean
     keyboardOpened: boolean
@@ -65,17 +66,21 @@ interface DetailsBodyProps {
     applyFilters: (landmarks?: Landmark[], getGroupList?: boolean, parent?: string) => Landmark[]
     applyFilters: (landmarks?: Landmark[], getGroupList?: boolean, parent?: string) => Landmark[]
     landmarks: Landmark[]
     landmarks: Landmark[]
     toggleDetailsPanel: (state: boolean) => void,
     toggleDetailsPanel: (state: boolean) => void,
-    setLandmark: (landmark: string) => void; 
+    setLandmark: (landmark: string) => void;
     promptAddLandmark: (longitude: number, latitude: number, floor?: number, lmCount?: string, parent?: 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
  * Component that renders the body of the Landmark details panel
 */
 */
 export const DetailsBody: React.FC<DetailsBodyProps> = (props) => {
 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 navigationState = useNavigationState(state => state)
     const [currentRoute, setCurrentRoute] = useState<string>()
     const [currentRoute, setCurrentRoute] = useState<string>()
@@ -88,7 +93,7 @@ export const DetailsBody: React.FC<DetailsBodyProps> = (props) => {
 
 
 
 
     let lmTypes = allLmTypes
     let lmTypes = allLmTypes
-    if(currentRoute=="Indoor") {
+    if (currentRoute == "Indoor") {
         lmTypes = lmTypesIndoor
         lmTypes = lmTypesIndoor
     }
     }
 
 
@@ -100,7 +105,7 @@ export const DetailsBody: React.FC<DetailsBodyProps> = (props) => {
             console.error(props.landmark?.expiry_date)
             console.error(props.landmark?.expiry_date)
             if (props.landmark?.expiry_date !== null) {
             if (props.landmark?.expiry_date !== null) {
                 toggleShowExpire(true)
                 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]))
                 setDate(new Date(+parts[0], +parts[1] - 1, +parts[2]))
             }
             }
             else {
             else {
@@ -111,78 +116,112 @@ export const DetailsBody: React.FC<DetailsBodyProps> = (props) => {
             props.setUpdatedLandmark(undefined)
             props.setUpdatedLandmark(undefined)
         }
         }
     }, [props.editingEnabled])
     }, [props.editingEnabled])
-    
+
     const [date, setDate] = useState(new Date())
     const [date, setDate] = useState(new Date())
     const [showExpire, toggleShowExpire] = useState<boolean>(false)
     const [showExpire, toggleShowExpire] = useState<boolean>(false)
     const [groupList, toggleGroupList] = useState<boolean>(props.landmark?.landmark_type == 30 ? true : false)
     const [groupList, toggleGroupList] = useState<boolean>(props.landmark?.landmark_type == 30 ? true : false)
 
 
     useEffect(() => {
     useEffect(() => {
         if (props.editingEnabled && !showExpire)
         if (props.editingEnabled && !showExpire)
-            props.setUpdatedLandmark({...props.updatedLandmark, expiry_date: null})
+            props.setUpdatedLandmark({ ...props.updatedLandmark, expiry_date: null })
         else if (props.editingEnabled && showExpire)
         else if (props.editingEnabled && showExpire)
-            props.setUpdatedLandmark({...props.updatedLandmark, expiry_date: date})
+            props.setUpdatedLandmark({ ...props.updatedLandmark, expiry_date: date })
     }, [showExpire])
     }, [showExpire])
 
 
     useEffect(() => {
     useEffect(() => {
         if (showExpire)
         if (showExpire)
-            props.setUpdatedLandmark({...props.updatedLandmark, expiry_date: date})
+            props.setUpdatedLandmark({ ...props.updatedLandmark, expiry_date: 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
      * Sub-component that renders picker for landmark types
      * @param 
      * @param 
      */
      */
     const LandmarkTypePickerContainer: React.FC = () => {
     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 
      * @param 
      */
      */
     const EditingDisabledUpperView: React.FC = () => {
     const EditingDisabledUpperView: React.FC = () => {
-        if (!groupList) {
+        if (!groupList && !props.reportingEnabled) {
             return (
             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 ?
                         {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>
                             </TouchableOpacity>
                             : null}
                             : 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}>
                         <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 ?
                             {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>
                         </ScrollView>
                     </View>
                     </View>
                     {props.landmark?.landmark_type ? <Image source={lmTypes[props.landmark?.landmark_type]?.image} /> : null}
                     {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 = () => {
     const LandmarkGroupListView: React.FC = () => {
         return (
         return (
             <View>
             <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 ?
                 {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>
                 <ScrollView>
                     {props.applyFilters(props.landmarks, true, props.landmark?.id)?.map((landmark) => {
                     {props.applyFilters(props.landmarks, true, props.landmark?.id)?.map((landmark) => {
                         return (
                         return (
@@ -234,130 +273,156 @@ export const DetailsBody: React.FC<DetailsBodyProps> = (props) => {
                                     props.setLandmark(landmark.id)
                                     props.setLandmark(landmark.id)
                                 }}>
                                 }}>
                                 <Image style={{ height: 52.5, width: 37.5, zIndex: 10, margin: 5 }} source={lmTypes[landmark.landmark_type]?.image} />
                                 <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>
                             </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>
                 </ScrollView>
             </View>
             </View>
         )
         )
     }
     }
 
 
+
     return (
     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.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 ?
             {!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 ?
             {!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' />
                         <FontAwesome name="plus" size={30} color='white' />
                     </TouchableOpacity>
                     </TouchableOpacity>
-                </View> : null }
-            
+                </View> : null}
+
         </ScrollView>
         </ScrollView>
     )
     )
 }
 }

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

@@ -7,9 +7,9 @@
 
 
 import React from "react";
 import React from "react";
 import { FontAwesome } from "@expo/vector-icons";
 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 { 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 { QueryStatus } from "react-query";
 import { useAuth } from "../../../../data/Auth/AuthContext";
 import { useAuth } from "../../../../data/Auth/AuthContext";
 import { Landmark, useReportLandmark } from "../../../../data/landmarks";
 import { Landmark, useReportLandmark } from "../../../../data/landmarks";
@@ -17,7 +17,6 @@ import { UserProfile } from "../../../../data/profiles";
 import { MainTabsNavigationProp } from "../../../../navigation/MainTabsNavigator";
 import { MainTabsNavigationProp } from "../../../../navigation/MainTabsNavigator";
 import { lmTypes } from "../../../../utils/GlobalUtils";
 import { lmTypes } from "../../../../utils/GlobalUtils";
 import IndoorFloor from "../../IndoorFloor";
 import IndoorFloor from "../../IndoorFloor";
-import { Report } from "./Report";
 import TouchOpaq from './TouchOpaq';
 import TouchOpaq from './TouchOpaq';
 // import { TextInput } from "react-native-paper";
 // import { TextInput } from "react-native-paper";
 
 
@@ -39,6 +38,10 @@ interface DetailsHeaderProps {
     toggleLmDetails: (state: boolean) => void
     toggleLmDetails: (state: boolean) => void
     place: string
     place: string
     uri: 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"
             props.deletePhotoStatus == "loading"
     }
     }
 
 
-    const reportLandmarkMutation = useReportLandmark()
     const capture: any = React.createRef();
     const capture: any = React.createRef();
     const imgWidth = 346
     const imgWidth = 346
     const imgHeight = 448
     const imgHeight = 448
     const imageDim = 25
     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 = () => {
     const HeaderContent: React.FC = () => {
         // landmark is owned by user
         // landmark is owned by user
         if (landmarkOwnedByUser(props.landmark) || (props.profile?.places == props.place)) {
         if (landmarkOwnedByUser(props.landmark) || (props.profile?.places == props.place)) {
@@ -91,7 +69,7 @@ export const DetailsHeader: React.FC<DetailsHeaderProps> = (props) => {
                 return (
                 return (
                     <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: "center", width: '100%' }}>
                     <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: "center", width: '100%' }}>
                         <TouchOpaq
                         <TouchOpaq
-                            func={() => props.toggleEditing(false)}
+                            func={() => { props.toggleEditing(false) }}
                             size={25}
                             size={25}
                             col={"white"}
                             col={"white"}
                             name={"close"}
                             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" />
                             <FontAwesome style={{ marginLeft: 5, marginBottom: 2, marginRight: 10 }} color="white" size={25} name="thumbs-up" />
                         </View>
                         </View>
                         <TouchOpaq
                         <TouchOpaq
-                            func={() => props.toggleDetailsPanel(false)}
+                            func={() => { props.setLandmark(""); props.toggleDetailsPanel(false) }}
                             size={25}
                             size={25}
                             col={"white"}
                             col={"white"}
                             name={"close"}
                             name={"close"}
@@ -173,9 +151,26 @@ export const DetailsHeader: React.FC<DetailsHeaderProps> = (props) => {
                             </View>
                             </View>
                         </TouchableOpacity>}
                         </TouchableOpacity>}
                     <View style={{ flexDirection: 'row' }}>
                     <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
                         <TouchOpaq
-                            func={() => props.toggleDetailsPanel(false)}
+                            func={() => {
+                                props.setLandmark("")
+                                props.toggleDetailsPanel(false)
+                                props.toggleReporting(false)
+                            }}
                             col={"white"}
                             col={"white"}
                             size={25}
                             size={25}
                             name={"close"}
                             name={"close"}

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

@@ -7,17 +7,18 @@
 
 
 import { FontAwesome } from "@expo/vector-icons";
 import { FontAwesome } from "@expo/vector-icons";
 import React, { memo, useEffect, useRef, useState } from "react";
 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 Modal from 'react-native-modal';
 import { LMComment, useAddComment, useDeleteComment, useEditComment, useLandmarkComments } from "../../../../data/comments";
 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 { useAuth } from "../../../../data/Auth/AuthContext";
-import { colors, lmTypes } from "../../../../utils/GlobalUtils";
+import { colors } from "../../../../utils/GlobalUtils";
 import { IconButton, PrimaryButton } from "../../../Buttons";
 import { IconButton, PrimaryButton } from "../../../Buttons";
 import { DetailsBody } from "./DetailsBody";
 import { DetailsBody } from "./DetailsBody";
 import { DetailsHeader } from "./DetailsHeader";
 import { DetailsHeader } from "./DetailsHeader";
 import { useOwnedProfile } from "../../../../data/profiles";
 import { useOwnedProfile } from "../../../../data/profiles";
 import { MainTabsNavigationProp } from "../../../../navigation/MainTabsNavigator";
 import { MainTabsNavigationProp } from "../../../../navigation/MainTabsNavigator";
+import { UseMutationResult } from "react-query";
 
 
 /**
 /**
  * Props for the {@link LandmarkDetails} component.
  * Props for the {@link LandmarkDetails} component.
@@ -52,6 +53,13 @@ export interface LandmarkDetailsProps {
     applyFilters: (landmarks?: Landmark[], getGroupList?: boolean, parent?: string) => Landmark[]
     applyFilters: (landmarks?: Landmark[], getGroupList?: boolean, parent?: string) => Landmark[]
     landmarks: Landmark[]
     landmarks: Landmark[]
     promptAddLandmark: (longitude: number, latitude: number, floor?: number, lmCount?: string, parent?: string) => void
     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
  * @component
  * @category Map
  * @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()
     const {userId, landmarkOwnedByUser} = useAuth()
 
 
     // /**
     // /**
@@ -67,10 +75,6 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition,
     //  */
     //  */
     // const selectedLandmarkState = undefined;
     // const selectedLandmarkState = undefined;
     // const [selectedLandmark, setLandmark] = useState<Landmark | undefined>(selectedLandmarkState);
     // 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}.
      * 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 [processingPhoto, setProcessingPhoto] = useState<boolean>(false)
 
 
     const landmarkQuery = useLandmark(landmarkId)
     const landmarkQuery = useLandmark(landmarkId)
-    const editLandmarkMutation = useEditLandmark()
     const rateLandmarkMutation = useRateLandmark()
     const rateLandmarkMutation = useRateLandmark()
     const tagLandmarkMutation = useTagLandmark()
     const tagLandmarkMutation = useTagLandmark()
     const deleteLandmarkMutation = useDeleteLandmark()
     const deleteLandmarkMutation = useDeleteLandmark()
@@ -257,6 +260,7 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition,
       {
       {
         text: "Confirm", onPress: async () => {
         text: "Confirm", onPress: async () => {
             await deleteLandmarkMutation.mutateAsync(landmarkId);   
             await deleteLandmarkMutation.mutateAsync(landmarkId);   
+            setLandmark("")
             toggleDetailsPanel(false);
             toggleDetailsPanel(false);
             Alert.alert("Landmark Deleted", "This landmark has been deleted.");
             Alert.alert("Landmark Deleted", "This landmark has been deleted.");
         }
         }
@@ -285,10 +289,10 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition,
      */
      */
     const focusComment = (id: string) => {
     const focusComment = (id: string) => {
         if (focusedCommentId == id) {
         if (focusedCommentId == id) {
-            setFocusedComment("");
+            setFocusedComment("")
         }
         }
         else {
         else {
-            setFocusedComment(id);
+            setFocusedComment(id)
             console.log(uri.substring(uri.length-10, uri.length))
             console.log(uri.substring(uri.length-10, uri.length))
         }
         }
     }
     }
@@ -362,7 +366,9 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition,
                 if (editingEnabled) {
                 if (editingEnabled) {
                     Keyboard.dismiss();
                     Keyboard.dismiss();
                 } else {
                 } else {
-                    toggleLmDetails(false)   
+                    setLandmark("")
+                    toggleLmDetails(false)
+                    toggleReporting(false)
                 }
                 }
             }}
             }}
             style={{justifyContent: "flex-end", height: '100%', margin: 0}}
             style={{justifyContent: "flex-end", height: '100%', margin: 0}}
@@ -406,7 +412,11 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition,
                     updatedLandmark={updatedLandmark}
                     updatedLandmark={updatedLandmark}
                     profile={profile}
                     profile={profile}
                     place={place}
                     place={place}
-                    uri = {uri} />
+                    uri = {uri}
+                    reportingEnabled={reportingEnabled}
+                    toggleReporting={toggleReporting}
+                    setFocusedComment={setFocusedComment}
+                    setLandmark={setLandmark} />
                 <DetailsBody
                 <DetailsBody
                     authNavigation={authNavigation} 
                     authNavigation={authNavigation} 
                     setProcessingPhoto={setProcessingPhoto}
                     setProcessingPhoto={setProcessingPhoto}
@@ -444,7 +454,12 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({markerWindowPosition,
                     landmarks={landmarks}
                     landmarks={landmarks}
                     toggleDetailsPanel={toggleDetailsPanel}
                     toggleDetailsPanel={toggleDetailsPanel}
                     setLandmark={setLandmark}
                     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}}>
                 <View style={{height: '100%', justifyContent: "space-evenly", alignItems: "center", marginHorizontal: 20}}>
                     <Text style={{color: 'white', fontSize: 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 { FontAwesome } from "@expo/vector-icons"
 import React, { useEffect } from "react"
 import React, { useEffect } from "react"
 import { useState } 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 Select from "react-native-multiple-select"
-import { LMComment } from "../../../../data/comments"
 import { Landmark } from "../../../../data/landmarks"
 import { Landmark } from "../../../../data/landmarks"
 
 
 
 
 interface ReportProps {
 interface ReportProps {
-    landmark?: Landmark,
-    comment?: LMComment
-    isLandmark: boolean,
+    landmark?: Landmark
+    commentID?: string
+    isLandmark: boolean
     reportLandmark?: (landmark, reasons, extraComments) => void
     reportLandmark?: (landmark, reasons, extraComments) => void
     reportComment?: (landmark, reasons, extraComments) => void
     reportComment?: (landmark, reasons, extraComments) => void
+    toggleReporting: (state: boolean) => void
 }
 }
 
 
 export const Report: React.FC<ReportProps> = (props) => {
 export const Report: React.FC<ReportProps> = (props) => {
@@ -56,165 +55,111 @@ export const Report: React.FC<ReportProps> = (props) => {
         }
         }
         props.isLandmark ?
         props.isLandmark ?
             props.reportLandmark(props.landmark, reasonsToString, text)
             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!')
         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 [text, onChangeText] = React.useState('')
     const [selectedReasons, setSelectedReasons] = useState([])
     const [selectedReasons, setSelectedReasons] = useState([])
 
 
     const reasons = props.isLandmark ?
     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 (
     return (
         <View>
         <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>
                     </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>
                 </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>
         </View>
     )
     )
 }
 }
 
 
 const styles = StyleSheet.create({
 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: {
     input: {
         height: 40,
         height: 40,
-        margin: 12,
-        marginTop: 60,
+        marginBottom: 20,
         borderWidth: 1,
         borderWidth: 1,
-        borderRadius: 10,
+        borderRadius: 0,
         padding: 10,
         padding: 10,
-    },
-    cancel: {
-        width: 100,
-        height: 40,
-        margin: 12,
-        borderWidth: 1,
-        borderRadius: 10,
         borderColor: 'white',
         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 delay = ms => new Promise(res => setTimeout(res, ms));
 
 
     const getLandmarkCount = () => {
     const getLandmarkCount = () => {
-        return landmarkQuery?.data?.filter(lm => landmarkOwnedByUser(lm)).length
+        return landmarkQuery?.data?.filter(lm => landmarkOwnedByUser(lm) && lm.landmark_type != 30).length
     }
     }
 
 
     const getTotalLandmarkRating = () => {
     const getTotalLandmarkRating = () => {

+ 1 - 1
src/data/comments.ts

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

+ 0 - 26
src/data/profiles.ts

@@ -207,30 +207,4 @@ export const useToggleTips = () => {
         onSuccess: () => { queryClient.invalidateQueries(queryKeys.getOwnedProfile)},  
         onSuccess: () => { queryClient.invalidateQueries(queryKeys.getOwnedProfile)},  
         onError: () => 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 { StackNavigationProp } from "@react-navigation/stack"
 import { observer } from "mobx-react"
 import { observer } from "mobx-react"
 import React, { useEffect, useState } from "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 { Chip } from "react-native-paper"
 import { IconButton } from "../components/Buttons"
 import { IconButton } from "../components/Buttons"
 import IndoorMap from "../components/Map/MainMapComponent/IndoorMap"
 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 { FilterPanel } from "../components/Map/Panels/FilterPanel/FilterPanel"
 import LandmarkDetails from "../components/Map/Panels/LandmarkDetailsPanel/LandmarkDetails"
 import LandmarkDetails from "../components/Map/Panels/LandmarkDetailsPanel/LandmarkDetails"
 import { useAuth } from "../data/Auth/AuthContext"
 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 { colors, lmTypes, } from "../utils/GlobalUtils"
 import { MainTabsNavigationProp } from "./MainTabsNavigator"
 import { MainTabsNavigationProp } from "./MainTabsNavigator"
-
 import { Menu, MenuItem, MenuDivider } from 'react-native-material-menu';
 import { Menu, MenuItem, MenuDivider } from 'react-native-material-menu';
 import { navigate } from "./RootNavigator"
 import { navigate } from "./RootNavigator"
-import { Separator } from "../components/Separator"
 import { MapEvent } from "react-native-maps"
 import { MapEvent } from "react-native-maps"
 
 
 
 
-
 const MapStackNavigator = createNativeStackNavigator()
 const MapStackNavigator = createNativeStackNavigator()
 
 
 export type MapStackParamList = {
 export type MapStackParamList = {
@@ -51,6 +48,14 @@ const MapNavigator: React.FC = ({ }) => {
     const [currentRoute, setCurrentRoute] = useState<string>()
     const [currentRoute, setCurrentRoute] = useState<string>()
 
 
     const [visible, setVisible] = useState(false);
     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(() => {
     useEffect(() => {
         const currentRouteIndex = navigationState?.routes[0]?.state?.index
         const currentRouteIndex = navigationState?.routes[0]?.state?.index
@@ -96,14 +101,14 @@ const MapNavigator: React.FC = ({ }) => {
 
 
         return landmarks
         return landmarks
     }
     }
+    const delay = ms => new Promise(res => setTimeout(res, ms));
 
 
     /**
     /**
      * Triggered by on a {@link Landmark} displayed on the map. 
      * 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.
      * 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);
         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 x = landmark.latitude;
         const y = landmark.longitude;
         const y = landmark.longitude;
         setMarkerWindowPosition({x: x, y: y});
         setMarkerWindowPosition({x: x, y: y});
@@ -112,6 +117,7 @@ const MapNavigator: React.FC = ({ }) => {
             mapState.toggleLmDetails(true)
             mapState.toggleLmDetails(true)
         }
         }
         else {
         else {
+            await delay(200)
             mapState.setSelectedLandmarkId(landmark.id);
             mapState.setSelectedLandmarkId(landmark.id);
         }
         }
     }
     }
@@ -135,11 +141,11 @@ const MapNavigator: React.FC = ({ }) => {
         if (lmCount == "groupItem") {  // Adding landmark to an already established group
         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.setNewLandmark({ latitude: latitude, longitude: longitude, floor: floor, voice: false, parent: parent, groupCount: -1 });
             mapState.toggleLmAdd(true)
             mapState.toggleLmAdd(true)
-            mapState.toggleLmDetails(false)
+            // mapState.toggleLmDetails(false)
         } else if (lmCount == "newGroup") {  // Adding 2nd landmark to existing landmark, thus making a new group
         } 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.setNewLandmark({ latitude: latitude, longitude: longitude, floor: floor, voice: false, parent: parent, groupCount: -2 });
             mapState.toggleLmAdd(true)
             mapState.toggleLmAdd(true)
-            mapState.toggleLmDetails(false)
+            // mapState.toggleLmDetails(false)
         } else {  // Adding a regular individial landmark
         } else {  // Adding a regular individial landmark
             mapState.setNewLandmark({ latitude: latitude, longitude: longitude, floor: floor, voice: false });
             mapState.setNewLandmark({ latitude: latitude, longitude: longitude, floor: floor, voice: false });
             mapState.toggleLmAdd(true)
             mapState.toggleLmAdd(true)
@@ -169,9 +175,10 @@ const MapNavigator: React.FC = ({ }) => {
                             selectedLandmarkId={mapState.selectedLandmarkId}
                             selectedLandmarkId={mapState.selectedLandmarkId}
                             setSelectedLandmarkId={mapState.setSelectedLandmarkId}
                             setSelectedLandmarkId={mapState.setSelectedLandmarkId}
                             setMarkerWindowPosition={setMarkerWindowPosition}
                             setMarkerWindowPosition={setMarkerWindowPosition}
-                            getLmUri={getLmUri} />}
+                            getLmUri={getLmUri}
+                            showPreviewPin={showPreviewPin}
+                            togglePreviewPin={togglePreviewPin} />}
                 </MapStackNavigator.Screen>
                 </MapStackNavigator.Screen>
-
                 <MapStackNavigator.Screen name="Indoor" >
                 <MapStackNavigator.Screen name="Indoor" >
                     {({ navigation }) =>
                     {({ navigation }) =>
                         <IndoorMap
                         <IndoorMap
@@ -180,9 +187,17 @@ const MapNavigator: React.FC = ({ }) => {
                             promptAddLandmark={promptAddLandmark}
                             promptAddLandmark={promptAddLandmark}
                             focusLandmark={focusLandmark}
                             focusLandmark={focusLandmark}
                             applyFilter={applyFilters}
                             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.Screen>
-
             </MapStackNavigator.Navigator>
             </MapStackNavigator.Navigator>
 
 
 
 
@@ -226,6 +241,8 @@ const MapNavigator: React.FC = ({ }) => {
                     >
                     >
                         <MenuItem onPress={() => {
                         <MenuItem onPress={() => {
                             setVisible(false)
                             setVisible(false)
+                            togglePreviewPin(false)
+                            toggleEditLmLoc(false)
                             navigate("Outdoor")
                             navigate("Outdoor")
                             mapState.setPlace("Outdoor")
                             mapState.setPlace("Outdoor")
                             // Alert.alert("Cameron Library")
                             // Alert.alert("Cameron Library")
@@ -259,6 +276,7 @@ const MapNavigator: React.FC = ({ }) => {
                         
                         
                         <MenuItem onPress={() => {
                         <MenuItem onPress={() => {
                             setVisible(false)
                             setVisible(false)
+                            togglePreviewPin(false)
                             navigate("Indoor")
                             navigate("Indoor")
                             mapState.setPlace("Cameron")
                             mapState.setPlace("Cameron")
                             // Alert.alert("Cameron Library")
                             // Alert.alert("Cameron Library")
@@ -271,9 +289,6 @@ const MapNavigator: React.FC = ({ }) => {
 
 
                 </View>
                 </View>
             }
             }
-
-
-
             {/* {!mapState.filterVisible ?
             {/* {!mapState.filterVisible ?
                 <View style={{ top: 10, marginLeft: 40, marginRight: 20, position: 'absolute', flexDirection: "row-reverse", justifyContent: 'flex-end' }}>
                 <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)} />
                     <IconButton size={20} color={colors.red} style={[mapStyles.filterButton]} icon="filter" onPress={() => mapState.toggleFilter(true)} />
@@ -288,14 +303,13 @@ const MapNavigator: React.FC = ({ }) => {
                     </ScrollView>
                     </ScrollView>
                 </View>
                 </View>
                  : null}                */}
                  : null}                */}
-
-
             <AddLandmarkPanel
             <AddLandmarkPanel
                 setNewLandmark={mapState.setNewLandmark}
                 setNewLandmark={mapState.setNewLandmark}
                 setVisible={mapState.toggleLmAdd}
                 setVisible={mapState.toggleLmAdd}
                 newLandmark={mapState.newLandmark}
                 newLandmark={mapState.newLandmark}
                 visible={mapState.lmAddVisible}
                 visible={mapState.lmAddVisible}
-                siblingID={mapState.selectedLandmarkId} />
+                siblingID={mapState.selectedLandmarkId}
+                togglePreviewPin={togglePreviewPin} />
             <LandmarkDetails
             <LandmarkDetails
                 markerWindowPosition={markerWindowPosition}
                 markerWindowPosition={markerWindowPosition}
                 authNavigation={authNavigation}
                 authNavigation={authNavigation}
@@ -311,6 +325,13 @@ const MapNavigator: React.FC = ({ }) => {
                 applyFilters = {applyFilters}
                 applyFilters = {applyFilters}
                 landmarks={landmarksQuery?.data}
                 landmarks={landmarksQuery?.data}
                 promptAddLandmark={promptAddLandmark}
                 promptAddLandmark={promptAddLandmark}
+                reportingEnabled={mapState.reportingEnabled}
+                toggleReporting={mapState.toggleReportingEnabled}
+                editLmLoc={editLmLoc}
+                toggleEditLmLoc={toggleEditLmLoc}
+                editLandmarkMutation={editLandmarkMutation}
+                updatedLandmark={updatedLandmark}
+                setUpdatedLandmark={setUpdatedLandmark}
                 />
                 />
             <FilterPanel
             <FilterPanel
                 visible={mapState.filterVisible}
                 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 = 'http://192.168.3.81:8000'
 // export const API_URL = 'https://staging.clicknpush.ca'
 // 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.106:8000' // Nathan
 //export const API_URL = 'http://192.168.1.64:8000'   // Chase
 //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.0.22:8000'       // Eric
 // export const API_URL = 'http://192.168.1.131:8000'  // Aidan surface
 // 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
 // export const API_URL = Config.API_URL

Разница между файлами не показана из-за своего большого размера
+ 303 - 344
yarn.lock


Некоторые файлы не были показаны из-за большого количества измененных файлов