8 次代码提交 b53045da6a ... 44b07caa93

作者 SHA1 备注 提交日期
  cdmoss 44b07caa93 resolve merge 2 年之前
  cdmoss 1725090a86 change url 2 年之前
  cdmoss 509572ee8c added landmarks 2 年之前
  cdmoss 4d2bc573e4 added new landmarks 2 年之前
  cdmoss ed7b2b12c2 add version details to account tab 2 年之前
  cdmoss 6b1aa61322 fixed inescapable login error 2 年之前
  cdmoss 0ed9ac1f35 fixed delete landmark loading 2 年之前
  cdmoss 7b5594b0ae cleaned up voice flow, removed wakework 2 年之前

+ 1 - 8
android/app/src/main/AndroidManifest.xml

@@ -8,13 +8,6 @@
   <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
   <uses-permission android:name="android.permission.VIBRATE"/>
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
-  <queries>
-    <intent>
-      <action android:name="android.intent.action.VIEW"/>
-      <category android:name="android.intent.category.BROWSABLE"/>
-      <data android:scheme="https"/>
-    </intent>
-  </queries>
   <application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:usesCleartextTraffic="true">
     <meta-data android:name="com.google.android.geo.API_KEY" android:value="AIzaSyBkpMWgPFbmVFUrma67bR8knwqA-U24H8c"/>
     <meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
@@ -32,7 +25,7 @@
         <category android:name="android.intent.category.DEFAULT"/>
         <category android:name="android.intent.category.BROWSABLE"/>
         <data android:scheme="cnp.mobile"/>
-        <data android:scheme="com.clicknpush.mobile"/>
+        <data android:scheme="https"/>
       </intent-filter>
     </activity>
     <activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>

+ 2 - 2
android/build.gradle

@@ -4,8 +4,8 @@ buildscript {
     ext {
         buildToolsVersion = "29.0.3"
         minSdkVersion = 21
-        compileSdkVersion = 31
-        targetSdkVersion = 31
+        compileSdkVersion = 32
+        targetSdkVersion = 32
         kotlinVersion = "1.6.0"
     }
     repositories {

二进制
assets/locator.png


+ 33 - 5
assets/mapicon.svg

@@ -28,14 +28,14 @@
      inkscape:document-units="mm"
      showgrid="false"
      inkscape:zoom="1.6017536"
-     inkscape:cx="263.14908"
-     inkscape:cy="333.69676"
+     inkscape:cx="361.16666"
+     inkscape:cy="314.9673"
      inkscape:window-width="1284"
      inkscape:window-height="793"
      inkscape:window-x="2029"
      inkscape:window-y="128"
      inkscape:window-maximized="0"
-     inkscape:current-layer="layer1-0" />
+     inkscape:current-layer="layer1" />
   <defs
      id="defs2" />
   <g
@@ -56,14 +56,14 @@
          y="-3.8746803"
          style="fill:#800080" />
       <circle
-         style="fill:#008000;fill-rule:evenodd;stroke-width:0.412708"
+         style="fill:#800033;fill-rule:evenodd;stroke-width:0.412708"
          id="path31"
          cx="134.12941"
          cy="10.2516"
          r="17.5" />
       <path
          sodipodi:type="star"
-         style="fill:#008000;fill-rule:evenodd"
+         style="fill:#800033;fill-rule:evenodd"
          id="path553"
          inkscape:flatsided="false"
          sodipodi:sides="3"
@@ -86,5 +86,33 @@
          cy="9.4013062"
          r="13" />
     </g>
+    <image
+       width="23.18363"
+       height="25.165833"
+       preserveAspectRatio="none"
+       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAAAXNSR0IArs4c6QAAAARnQU1BAACx
+jwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAPlSURBVHhe7d1dUtswFAbQUKA8UFbTDbLBbqbT
+djr0V25MJ8SOoiSWLCnnvNiZYRLp415ZGEg2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQgpvxmN3z5uOf8fSo582nYuNaU2om8pgqlUm2Fzll
+sjE9FYdMpmrPZPEnXWrCc1otjFyZyGNq6UwWe7Kck97XSmGUykQeU0tlssST3IWJ/xjPi6m5KEoW
+wq6KM7kJmfwez4sJedyHw8/to/NcFOhahbCrtqJYOxN5TF2SybvxeLIaJj6oZRzBQx3F8G8Mw8q5
+uh5q5KwGqago/6lgPE9hDN/H89WFsbyEw+P20Tp6qZGTLz21TXzXStuLoTk+j+dVCXl8CIev20fl
+9FQjJ31xzRN/VbhJhm1VNVeOOSGP9+FQ7CZKbzWS/IUtTPxVqSZpJRN5TKVmkvQzSEsTH92Ox2za
+KoYiY63ixkCq1EzOvotVszD5i+59H9PggpF9zOH5hxsD3TnaIC0Ww6DVcbeo5xqJNogim2o5E9/P
+WXfjcVaXW6xXCiK/1jMO44/e4Ys1SJE7Hy3poeEsGrMO1vrBBglBFv/jshwURD69ZBur9a63WEvq
+qdEsGulmG0SAXJtDNX8VVxANv7xrydQWCyI0SIIeV0tX1TQaBCImDWJl4VrN1b4rCERoEIjQIBCh
+QSBCg0DEpEFK/f8y1Gau9l1BIEKDJOjxqmqnkEaDQMRVNIjVcnnXkulsgygors2hmrfFStTTomEB
+THewQUKIXTSPYsinl2xjtR5rAn/Vu6eHgrBgzDpY69GrRAgz+qZatVMM+bWe8bEaP7aN+jUeGbVc
+EBaMWdEaP/pzRquhKoZyeq6Row3SojDxrB9/0GJB5B5zeP6mt+OHJDVIgwWR/V0hW8qk0Fib2o6n
+ZpJ8BWmlIEqOM7zWw3harTDGYh9s02ONnLTFqj2AFcb3El7zaTyvThjb8Em3WT9MaF9vNXLWZGp8
+55OVvzGPIZMv43kVxub4tn1UXi81cnZR1RRAJavWfcikio8hC3kM26qiV445PdTIRYW1dgBrr5Jz
+KsikhsXivxqa5JJMlgjzNoRQfLWqrRB2rVUUMnkr5DHcer7o7tpigZYMoOZC2FUqE3lMLZXJ4sHm
+DKGVQtiXKxN5TC2dSc6Ab0IQF//CrtUimLNUYcjkrZDH8OuKLE1XLOhTguipAGJSM5HH1LVkAgAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACTab
+vzgUM5kLFUqnAAAAAElFTkSuQmCC
+"
+       id="image1142"
+       x="92.197334"
+       y="61.165543" />
   </g>
 </svg>

二进制
assets/misc.png


+ 8 - 11
src/components/Map/IndoorFloor.tsx

@@ -1,26 +1,23 @@
-import { View, Text } from 'react-native'
 import React from 'react'
 import { Image } from 'react-native-svg'
-import BasementC from './MainMapComponent/images/BasementC.svg'
-import FirstFloorC from './MainMapComponent/images/FirstFloorC.svg'
-import SecondFloorC from './MainMapComponent/images/SecondFloorC.svg'
-import ThirdFloorC from './MainMapComponent/images/ThirdFloorC.svg'
-import FourthFloorC from './MainMapComponent/images/FourthFloorC.svg'
-import FifthFloorC from './MainMapComponent/images/FifthFloorC.svg'
 
-// export const IndoorFloor: React.FC<{floorNum: number}> = (props) => {
+interface Building {
+  id: number
+  name: string
+  floors: number
+}
+
 function IndoorFloor(props) {
-  const compArray = [
+  const floors = [
     <Image height={"100%"} width={"100%"} href={require('./MainMapComponent/images/basement.png')} />,
     <Image height={'100%'} width="100%" href={require('./MainMapComponent/images/firstfloor.png')} />,
     <Image height={"100%"} width={"100%"} href={require('./MainMapComponent/images/secondfloor.png')} />,
     <Image height={"100%"} width={"100%"} href={require('./MainMapComponent/images/thirdfloor.png')} />,
     <Image height={"100%"} width={"100%"} href={require('./MainMapComponent/images/fourthfloor.png')} />,
     <Image height={"100%"} width={"100%"} href={require('./MainMapComponent/images/fifthfloor.png')} />,
-    // <FifthFloorC viewBox='257 153 270 290' style={{ opacity: 0.7 }} height={"100%"} width={"100%"} />,
   ]
 
-  return compArray[props.floorNum]
+  return floors[props.floorNum]
 }
 
 export default IndoorFloor

+ 14 - 85
src/components/Map/MainMapComponent/IndoorMap.tsx

@@ -1,23 +1,15 @@
-import React, { useState, useEffect, Children } from 'react';
-import { View, Text, StatusBar, StyleSheet, Dimensions, Button, ActivityIndicator, Alert, Modal, PanResponderCallbacks, PanResponderGestureState, GestureResponderEvent, ImageSourcePropType, TouchableOpacity, Platform, Linking, Pressable, } from 'react-native';
-import { Svg, Defs, Rect, Mask, Circle, Marker, Path, Polyline, Image } from 'react-native-svg';
-import { RadioButton } from 'react-native-paper';
-import { Picker as EricPicker } from '@react-native-picker/picker';
-import ReactNativeZoomableView from '@openspacelabs/react-native-zoomable-view/src/ReactNativeZoomableView';
-import Spinner from 'react-native-spinkit'
-import { colors, lmTypesIndoor } from "../../../utils/GlobalUtils";
-import { MapStackNavigationProp } from "../../../navigation/MapNavigator"
-import CustomModal from './modal';
 import { FontAwesome } from "@expo/vector-icons";
-import ReactDOMServer from 'react-dom/server'; //npm i --save-dev @types/react-dom
-import { ZoomableViewEvent } from '@openspacelabs/react-native-zoomable-view/src/typings';
-import IndoorFloor from '../IndoorFloor'
-// import Toast from 'react-native-toast-message';
-import Toast from 'react-native-root-toast';
-import ArrowButton from './ArrowButton'
-import BottomButtons from './BottomButtons'
+import ReactNativeZoomableView from '@openspacelabs/react-native-zoomable-view/src/ReactNativeZoomableView';
+import React, { useEffect, useState } from 'react';
+import { ActivityIndicator, Alert, Dimensions, GestureResponderEvent, ImageSourcePropType, StatusBar, StyleSheet, View } from 'react-native';
 import Picker from 'react-native-picker-select';
+import Toast from 'react-native-root-toast';
+import { Image, Rect, Svg } from 'react-native-svg';
 import { Landmark } from '../../../data/landmarks';
+import { MapStackNavigationProp } from "../../../navigation/MapNavigator";
+import { colors, lmTypesIndoor } from "../../../utils/GlobalUtils";
+import IndoorFloor from '../IndoorFloor';
+import ArrowButton from './ArrowButton';
 
 
 
@@ -32,11 +24,9 @@ interface IndoorMapProps {
 
 
 
-const IndoorMap: React.FC<IndoorMapProps> = ({ navigation, landmarks, promptAddLandmark, focusLandmark, applyFilter }) => {
+const IndoorMap: React.FC<IndoorMapProps> = ({ landmarks, promptAddLandmark, focusLandmark, applyFilter }) => {
   const [floor, setFloor] = useState(1);
   const [showME, setShowME] = useState(false);
-  const [showDots, setShowDots] = useState(false);
-  const [showAddedDot, setShowAddedDot] = useState(false)
   const [SVGdim, setSVGdim] = useState([1, 1])
 
   const [localLandmarks, setLocalLandmarks] = useState<Landmark[]>([])
@@ -59,8 +49,7 @@ const IndoorMap: React.FC<IndoorMapProps> = ({ navigation, landmarks, promptAddL
           href={lmTypesIndoor[item.landmark_type].image as ImageSourcePropType} />
       )
     }
-  }
-  )
+  })
 
 
   function addLandmark(evt: GestureResponderEvent) {
@@ -87,9 +76,7 @@ const IndoorMap: React.FC<IndoorMapProps> = ({ navigation, landmarks, promptAddL
 
   useEffect(() => {
     // Alert.alert("useEffect has been triggered")
-    setShowAddedDot(false)
-    setShowDots(false)
-    setTimeout(() => setShowME(true), 100);
+    setTimeout(() => setShowME(true), 50);
   })
 
   const childToWeb = (child: any) => {
@@ -101,23 +88,6 @@ const IndoorMap: React.FC<IndoorMapProps> = ({ navigation, landmarks, promptAddL
   };
 
   const toWeb = (children: any) => React.Children.map(children, childToWeb);
-
-  function renderSvg() {
-    return (
-      <Svg height="100%" width="100%" style={{ backgroundColor: '#33AAFF' }}>
-        <Rect
-          x="50"
-          y="50"
-          width="50"
-          height="50"
-          fill="#3399ff"
-          strokeWidth="3"
-          stroke="rgb(0,0,0)"
-        />
-      </Svg>
-    )
-  }
-
   function changer(num) {
     setFloor(prevState => prevState + num)
   }
@@ -127,7 +97,7 @@ const IndoorMap: React.FC<IndoorMapProps> = ({ navigation, landmarks, promptAddL
     <View style={{ height: '100%', width: Dimensions.get("screen").width, backgroundColor:colors.red }}>
 
       <StatusBar backgroundColor={colors.red} />
-      <CustomModal />
+      {/* <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", }}>
@@ -162,23 +132,6 @@ const IndoorMap: React.FC<IndoorMapProps> = ({ navigation, landmarks, promptAddL
         </View>
 
 
-        {/* <EricPicker
-          style={{ backgroundColor: colors.red, width: 200, height: 50, flex: 5, color: 'white' }}
-          selectedValue={floor} // the text of what gets displayed on the dropdown header
-          onValueChange={(itemValue, itemIndex: number) => {
-            setFloor(itemIndex)
-            setShowME(false)
-          }}>
-          The value in EricPicker.Item refers to selectedValue in EricPicker, which refers to the state "floor"
-          <EricPicker.Item label="Basement" value={0} />
-          <EricPicker.Item label="First Floor" value={1} />
-          <EricPicker.Item label="Second Floor" value={2} />
-          <EricPicker.Item label="Third Floor" value={3} />
-          <EricPicker.Item label="Fourth Floor" value={4} />
-          <EricPicker.Item label="Fifth Floor" value={5} />
-        </EricPicker> */}
-
-
         {/* {floor == 5 ? arrowBut(0, "") : arrowBut(1, "chevron-right")} */}
         {floor == 5 ? <ArrowButton num={0} fontAweIcon={""} /> : <ArrowButton num={1} fontAweIcon={"chevron-right"} propEvent={() => changer(1)} />}
 
@@ -189,7 +142,7 @@ const IndoorMap: React.FC<IndoorMapProps> = ({ navigation, landmarks, promptAddL
         <View style={styles.container}>
           {showME === false ?
             <View style={{ display: 'flex', flexDirection: 'row', justifyContent: "center", }}>
-              <Spinner size={200} color={colors.red} type="9CubeGrid" />
+              <ActivityIndicator color={colors.red} size="large" />
             </View> :
 
             <ReactNativeZoomableView
@@ -227,22 +180,6 @@ const IndoorMap: React.FC<IndoorMapProps> = ({ navigation, landmarks, promptAddL
 
         </View>
       </View>
-      {/* <Button title="load coordinates" onPress={() => {
-        setfirstTime(false)
-        setLocalLandmarks(landmarks)
-      }} /> */}
-
-      {/* <Button title='Press me to svgString' color={colors.red} onPress={serialize}></Button> */}
-      {/* <View style={{ flex: 0.1, flexDirection: 'row', alignItems:'flex-end', justifyContent: 'space-around' , borderColor:'green' , borderWidth:0 ,}}>
-      <Button title="Go back to map" color={colors.red} onPress={() => navigation.goBack()} />
-      <Button title="Resources" color={colors.red} onPress={() => Linking.openURL('https://www.library.ualberta.ca/')} />
-      </View> */}
-      {/* <TouchableOpacity style={styles.arrowButton} onPress={() => setFloor(prevState => prevState+1)} ><Text>Increase floor by 1</Text></TouchableOpacity> */}
-
-
-      {/* <BottomButtons navigation={navigation}/> */}
-      {/* <Text>{floor}</Text> */}
-
     </View >
   );
 }
@@ -256,12 +193,6 @@ const styles = StyleSheet.create({
     borderWidth: 0,
     marginVertical: 0,
     aspectRatio: 8 / 10,  // (caters to portrait mode) (width is 80% the value of height dimension)
-    // flex: 1,
-    // // backgroundColor: "#fff",
-    // justifyContent: "center",
-    // borderColor: "black",
-    // borderWidth: 2,
-    // marginVertical: 15,
     width: '100%',
     height: '100%',
     maxWidth: "100%",
@@ -280,9 +211,7 @@ const styles = StyleSheet.create({
   arrowButton: {
     flex: 1,
     backgroundColor: colors.red,
-    // backgroundColor: "blue",
     height: 53.5,
-    // borderRadius: 10,
   },
 });
 

二进制
src/components/Map/MainMapComponent/images/firstfloor.png


+ 6 - 4
src/components/Map/Panels/LandmarkDetailsPanel/LandmarkDetails.tsx

@@ -180,6 +180,8 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({authNavigation, landma
             if (deleteLandmarkMutation.isSuccess) {
                 deleteLandmarkMutation.reset()
             }
+
+            console.log(deleteLandmarkMutation.status)
         }
         resetDeleteLMOnSuccess();
     }, [deleteLandmarkMutation.status]);
@@ -236,14 +238,14 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({authNavigation, landma
      * Calls the {@linkcode deleteLandmark} mutation from the {@link useLandmarks} hook and closes the modal once finished.
      */
     const removeLandmark = async () => {
-        Alert.alert("Are you sure you want to delete landmark here?", undefined,
+        Alert.alert("Are you sure you want to delete this landmark?", undefined,
       [{ text: "Cancel", }
         ,
       {
         text: "Confirm", onPress: async () => {
             await deleteLandmarkMutation.mutateAsync(landmarkId);   
             toggleDetailsPanel(false);
-            Alert.alert("LANDMARK HAS BEEN DELETED")
+            Alert.alert("Landmark Deleted", "This landmark has been deleted.");
         }
       }])
     }
@@ -370,8 +372,8 @@ const LandmarkDetails: React.FC<LandmarkDetailsProps> = ({authNavigation, landma
                 </View>
                 :
                 (landmarkQuery.isSuccess || landmarkQuery.isIdle) && 
-                editLandmarkMutation.isIdle || editLandmarkMutation.isSuccess && 
-                deleteLandmarkMutation.isIdle || deleteLandmarkMutation.isSuccess ?
+                (editLandmarkMutation.isIdle || editLandmarkMutation.isSuccess) && 
+                (deleteLandmarkMutation.isIdle || deleteLandmarkMutation.isSuccess) ?
                 <>
                 <DetailsHeader
                     authNavigation={authNavigation}

+ 25 - 16
src/components/Map/Panels/VoicePanel.tsx

@@ -58,6 +58,12 @@ export interface VoiceAction {
  * @param 
  * @returns 
  */
+
+const activateSpokestack = async (sourceCall: string) => {
+    await Spokestack.activate();
+    console.log("Spokestack activated from: " + sourceCall);
+}
+
 export const VoicePanel: React.FC<VoicePanelProps> = ({
         navigation,
         toggleVoiceVisible, 
@@ -181,13 +187,13 @@ export const VoicePanel: React.FC<VoicePanelProps> = ({
             '1A8196594C401EB93035CC6D7D6328CF1855C2B359744E720953AC34B6F658CA', // api token
             {
                 pipeline: {
-                    profile: PipelineProfile.TFLITE_WAKEWORD_NATIVE_ASR
-                },
-                wakeword: {
-                    detect: 'https://s.spokestack.io/u/kHNUa/detect.tflite',
-                    filter: 'https://s.spokestack.io/u/kHNUa/filter.tflite',
-                    encode: 'https://s.spokestack.io/u/kHNUa/encode.tflite',
+                    profile: PipelineProfile.PTT_NATIVE_ASR
                 },
+                // wakeword: {
+                //     detect: 'https://s.spokestack.io/u/kHNUa/detect.tflite',
+                //     filter: 'https://s.spokestack.io/u/kHNUa/filter.tflite',
+                //     encode: 'https://s.spokestack.io/u/kHNUa/encode.tflite',
+                // },
             }
         ).then(Spokestack.start).catch(error => console.log("[Voice]: Something went wrong when starting spokestack: " + error))
 
@@ -211,7 +217,7 @@ export const VoicePanel: React.FC<VoicePanelProps> = ({
                 else if (!speechResult && !listening && action) {
                     console.log('[Voice]: No speech result detected, but an action is in progress')
                     setListening(true)
-                    await Spokestack.activate()
+                    
                 }
                 else if (!speechResult && !listening) {
                     await closeVoice()
@@ -242,7 +248,7 @@ export const VoicePanel: React.FC<VoicePanelProps> = ({
         // handle unrecognized result
         else {
             setResponse("Did not recognize command, please try again")
-            await Spokestack.activate();
+            await activateSpokestack('processSpeechTranscript');
         }
     }
 
@@ -269,7 +275,7 @@ export const VoicePanel: React.FC<VoicePanelProps> = ({
         else {
             setResponse("Did not recognize command, please try again")
         }
-        await Spokestack.activate()
+        await activateSpokestack("discernActionRequest");
     }
 
     // action initiators
@@ -277,7 +283,7 @@ export const VoicePanel: React.FC<VoicePanelProps> = ({
     const initiateAddLandmarkAction = async () => {
         setNewLandmark({longitude: userCoords.longitude, latitude: userCoords.latitude})
         setAction({actionType: "Add landmark", actionStep: 1})
-        await Spokestack.activate();
+        await activateSpokestack("initiateAddLandmarkAction");
     }   
 
     const initiateViewNearLandmarksAction = async () => {
@@ -289,7 +295,7 @@ export const VoicePanel: React.FC<VoicePanelProps> = ({
         // tell user there is no landmarks
         else {
             setResponse("There are no landmarks nearby")
-            await Spokestack.activate();
+            await activateSpokestack("initiateViewNearLandmarksAction");
         }
     }
 
@@ -308,7 +314,7 @@ export const VoicePanel: React.FC<VoicePanelProps> = ({
         else {
         }
 
-        await Spokestack.activate();
+        await activateSpokestack("performActionFlow");
     }
 
 // action flows
@@ -382,11 +388,14 @@ export const VoicePanel: React.FC<VoicePanelProps> = ({
         const finishAddAttempt = async () => {
             if (addLandmarkMutation.isSuccess) {
                 addLandmarkMutation.reset();
-                setSelectedLandmarkId(newLandmark.id)
-                await Spokestack.activate();
+                setAlert({
+                    title: "Added landmark", 
+                    message: "Landmark added successfully", 
+                    type: "success",
+                })
             }
             else if (addLandmarkMutation.isError) {
-                await Spokestack.activate();
+                await activateSpokestack("finishAddAttempt");
             }
         }
         finishAddAttempt();
@@ -397,13 +406,13 @@ export const VoicePanel: React.FC<VoicePanelProps> = ({
 // end action flows
     
     const closeVoice = async () => {
-        await Spokestack.deactivate();
         setSpeechResult('')
         setPreviousResult('')
         setResponse('')
         setListening(false)
         setAction(undefined);
         toggleVoiceVisible(false)
+        await Spokestack.deactivate();
     }
 
     const LmTypeDisplay: React.FC<{lmType: {image: ImageRequireSource, label:string}, style?: ViewStyle}> = ({lmType, style}) => {

+ 25 - 6
src/components/Profile/LoginView.tsx

@@ -6,20 +6,20 @@
  */
 
 import { makeRedirectUri } from "expo-auth-session";
+import Constants from "expo-constants";
+import * as Linking from 'expo-linking';
 import * as WebBrowser from 'expo-web-browser';
 import { maybeCompleteAuthSession } from "expo-web-browser";
 import React, { useState } from "react";
-import { ActivityIndicator, Image, Pressable, StyleSheet, Text, TouchableHighlight, TouchableOpacity, View } from "react-native";
+import { ActivityIndicator, Image, Platform, StyleSheet, Text, TouchableOpacity, View } from "react-native";
+import Collapsible from "react-native-collapsible";
 import { useAuth } from "../../data/Auth/AuthContext";
 import { BaseStackNavigationProp } from "../../navigation/BaseStackNavigator";
 import { API_URL } from "../../utils/RequestUtils";
 import { GenericButton, PrimaryButton } from "../Buttons";
-import { ProfileSectionHeader } from "./ProfileSections/ProfileSectionHeader";
-import * as Linking from 'expo-linking';
-import { BrowserLink, ProfileLegal } from "./ProfileSections/ProfileLegal";
 import { Separator } from "../Separator";
-import Collapsible from "react-native-collapsible";
 import UnauthorizedLayout from "./AuthLayout";
+import { BrowserLink } from "./ProfileSections/ProfileLegal";
 
 /**
  * Props used by the {@link Intro} screen.
@@ -49,6 +49,7 @@ const LoginView : React.FC<{navigation: BaseStackNavigationProp}> = ({navigation
     const loginMessageState = "";
     const [loginMessage, setLoginMessage] = useState<string>(loginMessageState);
     const [legalCollapsed, toggleLegal] = useState<boolean>(true)
+    const [aboutCollapsed, toggleAbout] = useState<boolean>(true)
     /**
      * @type {boolean} 
      * React state holding the error state of the component.
@@ -84,6 +85,10 @@ const LoginView : React.FC<{navigation: BaseStackNavigationProp}> = ({navigation
         toggleLegal(!legalCollapsed)
     }
 
+    const openAbout = () => {
+        toggleAbout(!aboutCollapsed)
+    }
+
     return (
         <UnauthorizedLayout>
             {!loginMessage ?
@@ -97,13 +102,27 @@ const LoginView : React.FC<{navigation: BaseStackNavigationProp}> = ({navigation
                     <View style={{width: '100%'}}>
                         <Separator color="#E0E0E0" />
                         <GenericButton text="Legal" style={{justifyContent: 'center', alignItems: 'center', padding: 20, width: '100%', }} onPress={openLegal}/>
-                        <Separator color="#E0E0E0" />
                         <Collapsible collapsed={legalCollapsed} style={{borderColor: '#E0E0E0', borderBottomWidth: 1, borderRightWidth: 1, borderLeftWidth: 1, justifyContent: 'center', alignItems: 'center', padding: 20}} >
                             <BrowserLink style={{marginBottom: 20}} text="Mobile app third-party licenses" route="mobile-tpl"/>
                             <BrowserLink text="API third-party licenses" route="api-tpl"/>
                         </Collapsible>
                     </View>
                     <Separator color="#D3D3D3" />
+                    <View style={{width: '100%'}}>
+                        <Separator color="#E0E0E0" />
+                        <GenericButton text="About" style={{justifyContent: 'center', alignItems: 'center', padding: 20, width: '100%', }} onPress={openAbout}/>
+                        <Separator color="#E0E0E0" />
+                        <Collapsible collapsed={aboutCollapsed} style={{borderColor: '#E0E0E0', borderBottomWidth: 1, borderRightWidth: 1, borderLeftWidth: 1, justifyContent: 'center', alignItems: 'center', padding: 20}} >                            
+                            <View>
+                                <Text>{Platform.OS == "android" ? "Google Play" : Platform.OS == 'ios' ? "App Store" : null} version: {Constants.nativeAppVersion}</Text>
+                                <Text></Text>
+                            </View>
+                            <View>
+                                <Text>OTA version: {Constants.manifest.releaseId}</Text>
+                            </View>
+                        </Collapsible>
+                    </View>
+                    <Separator color="#D3D3D3" />
                     <GenericButton text="Provide feedback" style={{justifyContent: 'center', alignItems: 'center', padding: 20, width: '100%', }} onPress={openFeedback}/>
                 </View>
             </View> :

+ 27 - 43
src/components/Profile/ProfileSections.tsx

@@ -10,6 +10,7 @@ import React, { useState } from "react"
 import { View } from "react-native"
 import { UserProfile } from "../../data/profiles"
 import { RegisterCredsValues } from "../../utils/RegistrationUtils"
+import { ProfileAbout } from './ProfileSections/ProfileAbout'
 import { ProfileInformation } from "./ProfileSections/ProfileInformation"
 import { ProfileLegal } from "./ProfileSections/ProfileLegal"
 import { ProfilePrefs } from "./ProfileSections/ProfilePrefs"
@@ -29,6 +30,17 @@ interface ProfileSectionsProps {
     deleteAccountStatus: string
 }
 
+type SectionName = "information" | "legal" | "prefs" | "subscription" | "skills" | "about" | 'none'
+
+interface SectionsCollapseState {
+    info: boolean,
+    prefs: boolean,
+    legal: boolean,
+    subscription: boolean,
+    skills: boolean,
+    about: boolean
+}
+
 export const ProfileSections: React.FC<ProfileSectionsProps> = ({
     profile, 
     toggleTipsAsync, 
@@ -41,56 +53,28 @@ export const ProfileSections: React.FC<ProfileSectionsProps> = ({
     deleteAccount,
     deleteAccountStatus
 }) => {
+    const [collapsed, setCollapsed] = useState<SectionsCollapseState>({info: true, prefs: true, legal: true, subscription: true, skills: true, about: true})
     const [infoCollapsed, toggleInfo] = useState<boolean>(true)
     const [skillsCollapsed, toggleSkills] = useState<boolean>(true)
     const [prefsCollapsed, togglePrefs] = useState<boolean>(true)
     const [subscriptionCollapsed, toggleSubscription] = useState<boolean>(true)
     const [legalCollapsed, toggleLegal] = useState<boolean>(true)
 
-    const openInfo = () => {
-        toggleInfo(!infoCollapsed)
-        if (!skillsCollapsed) toggleSkills(true)
-        if (!prefsCollapsed) togglePrefs(true)
-        if (!subscriptionCollapsed) toggleSubscription(!subscriptionCollapsed)
-        if (!legalCollapsed) toggleLegal(true)
-    }
-
-    const openSkills = () => {
-        toggleSkills(!skillsCollapsed)
-        if (!infoCollapsed) toggleInfo(true)
-        if (!prefsCollapsed) togglePrefs(true)
-        if (!subscriptionCollapsed) toggleSubscription(!subscriptionCollapsed)
-        if (!legalCollapsed) toggleLegal(true)
-    }
-
-    const openPrefs = () => {
-        togglePrefs(!prefsCollapsed)
-        if (!infoCollapsed) toggleInfo(true)
-        if (!skillsCollapsed) toggleSkills(true)
-        if (!subscriptionCollapsed) toggleSubscription(!subscriptionCollapsed)
-        if (!legalCollapsed) toggleLegal(true)
-    }
-
-    const openSubscription = () => {
-        toggleSubscription(!subscriptionCollapsed)
-        if (!infoCollapsed) toggleInfo(true)
-        if (!skillsCollapsed) toggleSkills(true)
-        if (!prefsCollapsed) togglePrefs(true)
-        if (!legalCollapsed) toggleLegal(true)
+    const toggleSection = (section: SectionName) => {
+        Object.keys(collapsed).forEach(key => {
+            if (key === section) {
+                setCollapsed({...collapsed, [key]: !collapsed[key]})
+            }
+            else {
+                setCollapsed({...collapsed, [key]: true})
+            }
+        })
     }
-
     const openFeedback = () => {
+        toggleSection('none')
         Linking.openURL('mailto:dev@clicknpush.ca')
     }
 
-    const openLegal = () => {
-        toggleLegal(!legalCollapsed)
-        if (!infoCollapsed) toggleInfo(true)
-        if (!subscriptionCollapsed) toggleSubscription(true)
-        if (!skillsCollapsed) toggleSkills(true)
-        if (!prefsCollapsed) togglePrefs(true)
-    }
-
     return (
         <View>
             <ProfileInformation 
@@ -102,14 +86,14 @@ export const ProfileSections: React.FC<ProfileSectionsProps> = ({
                 resetChangePassword={resetChangePassword} 
                 changePasswordStatus={changePasswordStatus} 
                 changePassword={changePassword} 
-                openInfo={openInfo} 
+                openInfo={() => toggleSection('information')} 
                 infoCollapsed={infoCollapsed} 
                 email={profile?.email} 
                 username={profile?.username} />
             {/* <ProfileSkills openSkills={openSkills} skillsCollapsed={skillsCollapsed} /> */}
-            <ProfilePrefs openPrefs={openPrefs} showTips={profile?.show_tips} prefsCollapsed={prefsCollapsed} toggleTipsAsync={toggleTipsAsync} />
-            <ProfileSubscription openSubscription={openSubscription} subscriptionCollapsed={subscriptionCollapsed} />
-            <ProfileLegal openLegal={openLegal} legalCollapsed={legalCollapsed} />
+            <ProfilePrefs openPrefs={() => toggleSection('prefs')} showTips={profile?.show_tips} prefsCollapsed={collapsed.prefs} toggleTipsAsync={toggleTipsAsync} />
+            <ProfileSubscription openSubscription={() => toggleSection('subscription')} subscriptionCollapsed={collapsed.subscription} />
+            <ProfileLegal openLegal={() => toggleSection('legal')} legalCollapsed={collapsed.legal} />
             <ProfileSectionHeader isCollapsed={true} collapseToggleMethod={openFeedback} title="Provide feedback"/>
         </View>
     )

+ 30 - 0
src/components/Profile/ProfileSections/ProfileAbout.tsx

@@ -0,0 +1,30 @@
+/* Copyright (C) Click & Push Accessibility, Inc - All Rights Reserved
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ * Proprietary and confidential
+ * Written and maintained by the Click & Push Development team 
+ * <dev@clicknpush.ca>, January 2022
+ */
+
+import React from "react"
+import { Button, Platform, Text, View } from "react-native"
+import { GlobalStyles } from "../../../utils/GlobalUtils"
+import { ProfileSection } from "./ProfileSection"
+import Constants from "expo-constants"
+
+interface ProfileAboutProps {
+    openAbout: () => void
+    aboutCollapsed: boolean
+}
+
+export const ProfileAbout: React.FC<ProfileAboutProps> = ({aboutCollapsed, openAbout}) => {
+    return (
+        <ProfileSection isCollapsed={aboutCollapsed} collapseToggleMethod={openAbout} title="About">
+            <View>
+                <Text>{Platform.OS == "android" ? "Google Play" : Platform.OS == 'ios' ? "App Store" : null} version: {Constants.nativeAppVersion}</Text>
+            </View>
+            <View>
+                <Text>OTA version: {Constants.manifest.releaseId}</Text>
+            </View>
+        </ProfileSection>
+    )
+}

+ 3 - 3
src/data/Auth/AuthContext.tsx

@@ -288,7 +288,7 @@ export const AuthContextProvider: React.FC = ({children}) => {
         
             // handle authentication response from the server
             LOGGING.log('AUTH', 'info', "Prompting user with web browser...")
-            const response = await request.promptAsync(discovery);
+            const response = await request.promptAsync(discovery, {createTask: false});
         
             // if succesful, prepare a request for an access/id token
             if (response.type == "success" && request.codeVerifier) {
@@ -333,12 +333,12 @@ export const AuthContextProvider: React.FC = ({children}) => {
             }
             else {
                 LOGGING.log('AUTH', 'error', "An error occured when trying to authenticate user: " + response.type)
-                setAlert({title: 'Login error', message: "Something went wrong while logging in. Please try again.", callback: () => {}, type: 'error'})
+                setAlert({title: 'Login error', message: "Something went wrong while logging in. Please try again.", callback: () => setAuthStateLoading(false), type: 'error'})
             }
         }
         catch (error) {
             LOGGING.log('AUTH', 'error', "An error occured when trying to authenticate user: " + error)
-            setAlert({title: 'Login error', message: "Something went wrong while logging in. Please try again.", callback: () => {}, type: 'error'})
+            setAlert({title: 'Login error', message: "Something went wrong while logging in. Please try again.", callback: () => setAuthStateLoading(false), type: 'error'})
         }
 
         return {success: false}

+ 27 - 0
src/data/buildings.ts

@@ -0,0 +1,27 @@
+export interface Space {
+    
+}
+
+export interface Building {
+    id: number;
+    name: string;
+    floors: BuildingFloor[];
+    polygon: string
+}
+
+export interface BuildingFloor {
+    id: number;
+    number: number;
+    map: string;
+    building: string
+    landmarks
+}
+
+export interface BuildingResource {
+    id: number;
+    label: string;
+    content: string
+    building: string;
+
+}
+

+ 7 - 4
src/utils/GlobalUtils.ts

@@ -30,6 +30,8 @@ export const lmTypes: {[key: number]: {image: ImageRequireSource, label: string}
     9: {image: require('../../assets/ice.png'), label: "ice"},
     14: {image: require('../../assets/ramp.png'), label: "ramp"},
     16: {image: require('../../assets/childfriendly.png'), label: "child friendly area"},
+    21: {image: require('../../assets/accessibleEntrance.png'), label: 'accessible entrance'},
+    22: {image: require('../../assets/misc.png'), label: 'misc'}
 }
 
 export const lmTypesIndoor: {[key: number]: {image: ImageRequireSource, label: string}} = {
@@ -43,10 +45,11 @@ export const lmTypesIndoor: {[key: number]: {image: ImageRequireSource, label: s
     14: {image: require('../../assets/ramp.png'), label: "ramp"},
     15: {image: require('../../assets/water.png'), label: "water fountain"},
     16: {image: require('../../assets/childfriendly.png'), label: "child friendly area"},
-    17: {image: require('../../assets/garbage.png'), label: "Garbage cans"},
-    18: {image: require('../../assets/loudnoise.png'), label: "Loud area"},
-    19: {image: require('../../assets/tripping.png'), label: "Tripping hazard"},
-
+    17: {image: require('../../assets/garbage.png'), label: "garbage cans"},
+    18: {image: require('../../assets/loudnoise.png'), label: "loud area"},
+    19: {image: require('../../assets/tripping.png'), label: "tripping hazard"},
+    20: {image: require('../../assets/locator.png'), label: 'locator'},
+    22: {image: require('../../assets/misc.png'), label: 'misc'}
 }
 
 // 2: {image: require('../../assets/stairs.png'), label: "stairs"},

+ 2 - 1
src/utils/RequestUtils.ts

@@ -11,7 +11,8 @@
 //export const API_URL = 'http://192.168.3.81:8000'
 // export const API_URL = 'https://staging.clicknpush.ca'
 
+export const API_URL = 'https://app.clicknpush.ca'
+//export const API_URL = 'http://192.168.1.64:8000'   // Chase
 //export const API_URL = 'http://192.168.0.22:8000'       // Eric
-export const API_URL = 'http://192.168.1.69:8000'
 
 // export const API_URL = Config.API_URL

+ 1 - 0
src/utils/logging.ts

@@ -2,6 +2,7 @@ export interface Logging {
     log: (category: LogCategory, message: string, type?: Logtype) => void;
     CATEGORIES: { [key: string]: LogCategory };
 }
+
 export type LogCategory = "SYSTEM" | "AUTH" | 'NOTIFICATIONS' | 'LANDMARKS' | 'COMMENTS' | 'PROFILE';
 export type Logtype = "error" | "warning" | "info"