diff --git a/Untitled b/Untitled deleted file mode 100644 index e69de29..0000000 diff --git a/design/Deighton AR Design.sketch b/design/Deighton AR Design.sketch index d174cc0..c5afdb8 100644 Binary files a/design/Deighton AR Design.sketch and b/design/Deighton AR Design.sketch differ diff --git a/design/Deighton AR System.svg b/design/Deighton AR High Level.svg similarity index 100% rename from design/Deighton AR System.svg rename to design/Deighton AR High Level.svg diff --git a/design/Deighton AR System.docx b/design/Deighton AR Overview.docx similarity index 100% rename from design/Deighton AR System.docx rename to design/Deighton AR Overview.docx diff --git a/design/Deighton AR System.pdf b/design/Deighton AR System.pdf new file mode 100644 index 0000000..dee8671 Binary files /dev/null and b/design/Deighton AR System.pdf differ diff --git a/design/Deighton AR System.sketch b/design/Deighton AR System.sketch new file mode 100644 index 0000000..39f2e9e Binary files /dev/null and b/design/Deighton AR System.sketch differ diff --git a/mobile/App.test.js b/mobile/App.test.js deleted file mode 100644 index fc6f975..0000000 --- a/mobile/App.test.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import App from './App'; - -import renderer from 'react-test-renderer'; - -it('renders without crashing', () => { - const rendered = renderer.create().toJSON(); - expect(rendered).toBeTruthy(); -}); diff --git a/mobile/app.json b/mobile/app.json index 8e85d20..29cc17c 100644 --- a/mobile/app.json +++ b/mobile/app.json @@ -1,7 +1,4 @@ { - "expo": { - "sdkVersion": "25.0.0" - }, "name": "DeightonAR", "displayName": "DeightonAR" } \ No newline at end of file diff --git a/mobile/index.js b/mobile/index.js index e9a0a0f..0ce8e0b 100644 --- a/mobile/index.js +++ b/mobile/index.js @@ -1,4 +1,4 @@ -import { AppRegistry } from 'react-native'; -import App from './src/App'; +import { AppRegistry } from 'react-native' +import App from './src/App' -AppRegistry.registerComponent('DeightonAR', () => App); +AppRegistry.registerComponent('DeightonAR', () => App) diff --git a/mobile/ios/DeightonAR.xcodeproj/project.pbxproj b/mobile/ios/DeightonAR.xcodeproj/project.pbxproj index eb8c74e..ef92116 100644 --- a/mobile/ios/DeightonAR.xcodeproj/project.pbxproj +++ b/mobile/ios/DeightonAR.xcodeproj/project.pbxproj @@ -203,7 +203,6 @@ "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework", "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac/GoogleToolboxForMac.framework", "${BUILT_PRODUCTS_DIR}/React/React.framework", - "${BUILT_PRODUCTS_DIR}/ReactNativeNavigation/ReactNativeNavigation.framework", "${PODS_ROOT}/../../node_modules/react-viro/ios/dist/ViroRenderer/ViroKit.framework", "${BUILT_PRODUCTS_DIR}/react-native-maps/react_native_maps.framework", "${BUILT_PRODUCTS_DIR}/yoga/yoga.framework", @@ -218,7 +217,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactNativeNavigation.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ViroKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/react_native_maps.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/yoga.framework", diff --git a/mobile/ios/DeightonAR.xcodeproj/xcshareddata/xcschemes/DeightonAR.xcscheme b/mobile/ios/DeightonAR.xcodeproj/xcshareddata/xcschemes/DeightonAR.xcscheme index 86578eb..8c1f88c 100644 --- a/mobile/ios/DeightonAR.xcodeproj/xcshareddata/xcschemes/DeightonAR.xcscheme +++ b/mobile/ios/DeightonAR.xcodeproj/xcshareddata/xcschemes/DeightonAR.xcscheme @@ -34,38 +34,15 @@ ReferencedContainer = "container:DeightonAR.xcodeproj"> - - - - - - - - #import -// RNN: react-native-navigation -#import "RCCManager.h" - // RNM: react-native-maps // See https://developers.google.com/maps/documentation/ios-sdk/start // and https://gist.github.com/dmachat/412658416dc7dcf3f7a829b7a10c2583 @@ -27,30 +24,25 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { -/* - VRTBundleURLProvider *bundleProvider = [[VRTBundleURLProvider alloc] init]; - jsCodeLocation = [bundleProvider jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; - - if (jsCodeLocation == nil) { - jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; - } -*/ - // RNM: Uncomment for Google Maps support - // [GMSServices provideAPIKey:@"AIzaSyDN4E_vzO4cKjKHkMg_49hX1GBnU34kx4U"]; - NSURL *jsCodeLocation; - + #ifdef DEBUG jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; #else jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; #endif - // Bootstrap RNN. See https://github.com/wix/react-native-navigation/blob/master/example/ios/example/AppDelegate.m + RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation + moduleName:@"DeightonAR" + initialProperties:nil + launchOptions:launchOptions]; + rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - self.window.backgroundColor = [UIColor whiteColor]; - [[RCCManager sharedInstance] initBridgeWithBundleURL:jsCodeLocation launchOptions:launchOptions]; - + UIViewController *rootViewController = [UIViewController new]; + rootViewController.view = rootView; + self.window.rootViewController = rootViewController; + [self.window makeKeyAndVisible]; return YES; } diff --git a/mobile/ios/Podfile b/mobile/ios/Podfile index 35df62d..3d4f198 100644 --- a/mobile/ios/Podfile +++ b/mobile/ios/Podfile @@ -26,8 +26,6 @@ target 'DeightonAR' do pod 'GLog', :podspec => '../node_modules/react-native/third-party-podspecs/GLog.podspec' pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec' - pod 'ReactNativeNavigation', :podspec => '../node_modules/react-native-navigation/ios/ReactNativeNavigation.podspec' - pod 'react-native-maps', path: '../node_modules/react-native-maps' # pod 'react-native-google-maps', path: '../node_modules/react-native-maps' # pod 'GoogleMaps' diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index 923f792..f2f4a8c 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -70,8 +70,6 @@ PODS: - React/Core - React/fishhook - React/RCTBlob - - ReactNativeNavigation (1.1.407): - - React - ViroKit (1.0): - AWSDynamoDB (~> 2.6.7) - GVRAudioSDK (= 1.120.0) @@ -93,7 +91,6 @@ DEPENDENCIES: - React/RCTNetwork (from `../node_modules/react-native`) - React/RCTText (from `../node_modules/react-native`) - React/RCTWebSocket (from `../node_modules/react-native`) - - ReactNativeNavigation (from `../node_modules/react-native-navigation/ios/ReactNativeNavigation.podspec`) - ViroKit (from `../node_modules/react-viro/ios/dist/ViroRenderer/`) - ViroReact (from `../node_modules/react-viro/ios/`) - yoga (from `../node_modules/react-native/ReactCommon/yoga`) @@ -119,8 +116,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native" react-native-maps: :path: "../node_modules/react-native-maps" - ReactNativeNavigation: - :podspec: "../node_modules/react-native-navigation/ios/ReactNativeNavigation.podspec" ViroKit: :path: "../node_modules/react-viro/ios/dist/ViroRenderer/" ViroReact: @@ -141,11 +136,10 @@ SPEC CHECKSUMS: GVRSDK: 0cb9d0ce06a84d698e61e3db9509766022dcf815 React: 541ba768b9855e10cdc76f55427a5cd0653ca806 react-native-maps: 066c2afcc89e18726377bcc685315f989ca22449 - ReactNativeNavigation: 0a0de59d82ed1accc9b762037752b11d794c7a4c ViroKit: 9631f301ef6a3f56116b23d6aac5d5c2307aa368 ViroReact: 5520f26ac4654e361786c82da3b29ce0402c3c00 yoga: 17521bbb0dd54a47c0b3ac43253e78cdac7488e0 -PODFILE CHECKSUM: b13c8a6ef20af9f80e486813619c4b2833c402f8 +PODFILE CHECKSUM: 1de520fb7d323b83502ffda9c5bf993f7545cfa9 COCOAPODS: 1.5.0.beta.1 diff --git a/mobile/package-lock.json b/mobile/package-lock.json index 6bea7fe..0c0ddba 100644 --- a/mobile/package-lock.json +++ b/mobile/package-lock.json @@ -5438,6 +5438,19 @@ } } }, + "react-native-animatable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/react-native-animatable/-/react-native-animatable-1.2.4.tgz", + "integrity": "sha512-cVTQXa/cp8gfxcl+l6I1rGAI7EeoNZ0ur9vtxb3tD5iGlJbIyUfQK61e6BycnZewdgQ639Mp6OrueXTpZlv76Q==", + "requires": { + "prop-types": "15.6.1" + } + }, + "react-native-iphone-x-helper": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.0.2.tgz", + "integrity": "sha512-5FYNC4kTi/YK86l+r8GQ0xgsSL2tleCQ5Yppu1+ARbnm2qGRmDoJTGSNsWBAWa8FP1ORyhMjxi18IlvSRKaI2g==" + }, "react-native-maps": { "version": "0.20.1", "resolved": "https://registry.npmjs.org/react-native-maps/-/react-native-maps-0.20.1.tgz", @@ -5493,6 +5506,15 @@ } } }, + "react-native-modal": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-5.4.0.tgz", + "integrity": "sha512-Bvq4FQPMAFijqjqNX6TxLgKOwdbruM6GvFwF9rb+mowbaFZVoYbHTKLaAbdPlrblgaZKWyOuuxBUoDx41+Xktg==", + "requires": { + "prop-types": "15.6.1", + "react-native-animatable": "1.2.4" + } + }, "react-proxy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/react-proxy/-/react-proxy-1.1.8.tgz", diff --git a/mobile/package.json b/mobile/package.json index 3135294..c7c19c7 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -25,7 +25,9 @@ "react": "^16.2.0", "react-form-binder": "^1.2.0", "react-native": "^0.51.1", + "react-native-iphone-x-helper": "^1.0.2", "react-native-maps": "^0.20.1", + "react-native-modal": "^5.4.0", "react-router-native": "^4.2.0", "react-viro": "^2.4.0", "socket.io-client": "^2.0.4" diff --git a/mobile/src/screens/Viewer.js b/mobile/src/ARViewer/ARViewer.js similarity index 82% rename from mobile/src/screens/Viewer.js rename to mobile/src/ARViewer/ARViewer.js index 5df97cd..651f212 100644 --- a/mobile/src/screens/Viewer.js +++ b/mobile/src/ARViewer/ARViewer.js @@ -4,6 +4,7 @@ import { ViroARSceneNavigator, ViroARScene, ViroARPlane, ViroBox, ViroText, ViroAmbientLight } from 'react-viro' import autobind from 'autobind-decorator' +import backImage from './images/back.png' const styles = { helloWorldTextStyle: { @@ -31,7 +32,9 @@ class WorkItemSceneAR extends React.Component { return ( {this.setState({text : "Hello World!"})}}> - @@ -41,11 +44,7 @@ class WorkItemSceneAR extends React.Component { } } -export class Viewer extends React.Component { - static navigatorStyle = { - navBarHidden: true, - } - +export class ARViewer extends React.Component { constructor(props) { super(props) } @@ -61,11 +60,11 @@ export class Viewer extends React.Component { + initialScene={{ scene: WorkItemSceneAR }} /> - + diff --git a/mobile/src/screens/images/back.png b/mobile/src/ARViewer/images/back.png similarity index 100% rename from mobile/src/screens/images/back.png rename to mobile/src/ARViewer/images/back.png diff --git a/mobile/src/ARViewer/index.js b/mobile/src/ARViewer/index.js new file mode 100644 index 0000000..47c20c9 --- /dev/null +++ b/mobile/src/ARViewer/index.js @@ -0,0 +1 @@ +export { ARViewer } from './ARViewer' \ No newline at end of file diff --git a/mobile/src/screens/Activity.js b/mobile/src/Activity/Activity.js similarity index 62% rename from mobile/src/screens/Activity.js rename to mobile/src/Activity/Activity.js index 159ee39..b7cf767 100644 --- a/mobile/src/screens/Activity.js +++ b/mobile/src/Activity/Activity.js @@ -1,23 +1,8 @@ import React from 'react' import { StyleSheet, View, TouchableOpacity, Image, ScrollView } from 'react-native' -import backImage from './images/back.png' +import autobind from 'autobind-decorator' export class Activity extends React.Component { - static navigatorButtons = { - leftButtons: [ - { - id: 'cancel', - title: 'Cancel', - } - ], - rightButtons: [ - { - id: 'done', - title: 'Done', - } - ] - } - static styles = StyleSheet.create({ container: { height: '100%', @@ -28,20 +13,10 @@ export class Activity extends React.Component { constructor(props) { super(props) - this.props.navigator.setOnNavigatorEvent(this._handleNavigatorEvent.bind(this)) } - _handleNavigatorEvent(event) { - switch (event.id) { - case 'cancel': - case 'done': - this.props.navigator.pop() - break - } - } - - _handlePressButton() { - this.props.navigator.pop() + _handlePushButton(event) { + this.props.history.goBack() } render() { diff --git a/mobile/src/Activity/index.js b/mobile/src/Activity/index.js new file mode 100644 index 0000000..3d89983 --- /dev/null +++ b/mobile/src/Activity/index.js @@ -0,0 +1 @@ +export { Activity } from './Activity' \ No newline at end of file diff --git a/mobile/src/Auth/DefaultRoute.js b/mobile/src/Auth/DefaultRoute.js new file mode 100644 index 0000000..38221a2 --- /dev/null +++ b/mobile/src/Auth/DefaultRoute.js @@ -0,0 +1,42 @@ +import React, { Fragment, Component } from 'react' +import { api } from '../API' +import { Route, Redirect } from 'react-router-native' +import { View } from 'react-native' +import autobind from 'autobind-decorator' + +export class DefaultRoute extends Component { + @autobind + updateComponent() { + this.forceUpdate() + } + + componentDidMount() { + api.addListener('login', this.updateComponent) + } + + componentWillUnmount() { + api.removeListener('login', this.updateComponent) + } + + render() { + const user = api.loggedInUser + let path = null + + if (user) { + if (!user.pending) { + path = '/home' + } + } else { + path = '/login' + } + + const { location } = this.props + + // Render a redirect or nothing until we finished logging on + return ( + ( + path ? : null + )} /> + ) + } +} diff --git a/mobile/src/screens/Login.js b/mobile/src/Auth/Login.js similarity index 78% rename from mobile/src/screens/Login.js rename to mobile/src/Auth/Login.js index 0e6a77c..4cfe074 100644 --- a/mobile/src/screens/Login.js +++ b/mobile/src/Auth/Login.js @@ -1,6 +1,7 @@ import React from 'react'; import { Platform, StyleSheet, Text, Image, Switch, TextInput, - KeyboardAvoidingView, ScrollView, View, Button, Alert, InteractionManager } from 'react-native'; + KeyboardAvoidingView, View, Button } from 'react-native' +import { MessageModal } from '../Modal' import logoImage from './images/deighton.png' import { FormBinder } from 'react-form-binder' import { api } from '../API' @@ -8,10 +9,6 @@ import { BoundSwitch, BoundInput, BoundButton } from '../ui' import autobind from 'autobind-decorator' export class Login extends React.Component { - static navigatorStyle = { - navBarHidden: true, - } - static bindings = { email: { alwaysGet: true, @@ -69,25 +66,37 @@ export class Login extends React.Component { constructor(props) { super(props) this.state = { - binder: new FormBinder({email: 'john@lyon-smith.org'}, Login.bindings) + binder: new FormBinder({email: 'john@lyon-smith.org'}, Login.bindings), + messageModal: null, } } @autobind handleLogin() { let obj = this.state.binder.getModifiedFieldValues() - let { navigator } = this.props + let { history } = this.props if (obj) { api.login(obj.email, obj.password, obj.rememberMe).then((user) => { - this.props.navigator.dismissAllModals() + history.replace('/home') }).catch((error) => { - console.error(api.apiURL) + this.setState({ messageModal: { + icon: 'hand', + message: 'Unable to login', + detail: error.message, + }}) }) } } + @autobind + handleMessageDismiss() { + this.setState({ messageModal: null }) + } + render() { + const { messageModal } = this.state + return ( @@ -104,6 +113,12 @@ export class Login extends React.Component { + ) } diff --git a/mobile/src/Auth/Logout.js b/mobile/src/Auth/Logout.js new file mode 100644 index 0000000..6694437 --- /dev/null +++ b/mobile/src/Auth/Logout.js @@ -0,0 +1,21 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import { api } from '../API' + +export class Logout extends Component { + static propTypes = { + history: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), + } + + componentDidMount(event) { + const cb = () => { + this.props.history.replace('/login') + } + + api.logout().then(cb, cb) + } + + render() { + return null + } +} diff --git a/mobile/src/Auth/ProtectedRoute.js b/mobile/src/Auth/ProtectedRoute.js new file mode 100644 index 0000000..c3f9e51 --- /dev/null +++ b/mobile/src/Auth/ProtectedRoute.js @@ -0,0 +1,41 @@ +import React from 'react' +import { Route, Redirect } from 'react-router-native' +import { PropTypes } from 'prop-types' +import { api } from '../API' +import autobind from 'autobind-decorator' + +export class ProtectedRoute extends React.Component { + static propTypes = { + location: PropTypes.shape({ pathname: PropTypes.string, search: PropTypes.string }), + admin: PropTypes.bool, + } + + @autobind + updateComponent() { + this.forceUpdate() + } + + componentDidMount() { + api.addListener('login', this.updateComponent) + } + + componentWillUnmount() { + api.removeListener('login', this.updateComponent) + } + + render(props) { + const user = api.loggedInUser + + if (user) { + if (user.pending) { + // The API might be in the middle of fetching the user information + return null + } else if (!this.props.admin || (this.props.admin && user.administrator)) { + return + } + } + + // TODO: Can add redirect back in here - see website + return + } +} diff --git a/mobile/src/screens/images/deighton.png b/mobile/src/Auth/images/deighton.png similarity index 100% rename from mobile/src/screens/images/deighton.png rename to mobile/src/Auth/images/deighton.png diff --git a/mobile/src/Auth/index.js b/mobile/src/Auth/index.js new file mode 100644 index 0000000..5437cba --- /dev/null +++ b/mobile/src/Auth/index.js @@ -0,0 +1,4 @@ +export { Login } from './Login' +export { Logout } from './Logout' +export { ProtectedRoute } from './ProtectedRoute' +export { DefaultRoute } from './DefaultRoute' diff --git a/mobile/src/Home/Home.js b/mobile/src/Home/Home.js new file mode 100644 index 0000000..42e27bd --- /dev/null +++ b/mobile/src/Home/Home.js @@ -0,0 +1,131 @@ +import React from 'react' +import { StyleSheet, Text, TextInput, FlatList, Image, View, TouchableOpacity } from 'react-native' +import MapView, { Marker } from 'react-native-maps' +import { Icon, Header } from '../ui' +import { api } from '../API' +import autobind from 'autobind-decorator' +import pinImage from './images/pin.png' + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#FFFFFF', + alignItems: 'flex-start', + justifyContent: 'flex-start', + } +}) + +const data = [ + {key: '1', title: 'Remove Animal Carcass', location: 'Ossington Ave. | 0.2 mi.', state: 'planned', latlng: { latitude: 43.653226, longitude: -79.383184 } }, + {key: '2', title: 'Fix sign post', location: 'Alexandre St. | 0.7 mi.', state: 'open', latlng: { latitude: 43.648118, longitude: 79.392636 }}, + {key: '3', title: 'Overflowing trash', location: 'Bay St. | 0.8 mi.', state: 'open', latlng: { latitude: 43.640168, longitude: -79.409373 }}, + {key: '4', title: 'Leaking water pipe', location: 'Bloor St. | 1.2 mi.', state: 'planned', latlng: { latitude: 43.633110, longitude: -79.415880 }}, + {key: '5', title: 'Tree branch in road', location: 'Blue Jays Way | 2.2 mi.', state: 'open', latlng: { latitude: 43.653526, longitude: -79.361385 }}, + {key: '6', title: 'Washing machine on sidewalk', location: 'Christie St. | 3.0 mi.', state: 'open', latlng: { latitude: 43.663870, longitude: -79.383705 }}, + {key: '7', title: 'Dead moose', location: 'Cummer Ave. | 4.2 mi.', state: 'open', latlng: { latitude: 43.659166, longitude: -79.391350 }}, + {key: '8', title: 'Glass in street', location: 'Danforth Ave. | 4.7 mi.', state: 'open', latlng: { latitude: 43.663538, longitude: -79.423212 }}, +] + +export class Home extends React.Component { + constructor(props) { + super(props) + } + + @autobind + _handleNavigatorEvent(event) { + switch (event.id) { + case 'logout': + api.logout().then(() => { + this.props.history.replace('/login') + }) + break + case 'viewer': + this.props.push('/viewer') + break + } + } + + @autobind + handleAdminButton() { + this.props.history.replace('/admin') + } + + @autobind + handleItemSelect(item, index) { + this.props.history.replace('/activity') + } + + @autobind + handleLogout() { + this.props.history.replace('/logout') + } + + render() { + return ( + +
+ + { + data.map(marker => ( + + )) + } + + + + + + + { + return ( + + {item.state.toUpperCase()} + + {item.title} + {item.location} + + (this._handleItemSelect(item, index))} > + + + + ) + }} /> + + + + + Hide List + + + + + + ); + } +} diff --git a/mobile/src/screens/images/pin.png b/mobile/src/Home/images/pin.png similarity index 100% rename from mobile/src/screens/images/pin.png rename to mobile/src/Home/images/pin.png diff --git a/mobile/src/Home/index.js b/mobile/src/Home/index.js new file mode 100644 index 0000000..8f0854d --- /dev/null +++ b/mobile/src/Home/index.js @@ -0,0 +1 @@ +export { Home } from './Home' \ No newline at end of file diff --git a/mobile/src/Modal/MessageModal.js b/mobile/src/Modal/MessageModal.js new file mode 100644 index 0000000..1225f29 --- /dev/null +++ b/mobile/src/Modal/MessageModal.js @@ -0,0 +1,62 @@ +import React, { Component } from 'react' +import Modal from 'react-native-modal' +import PropTypes from 'prop-types' +import { View, Text, TouchableOpacity } from 'react-native' +import { Icon } from '../ui' +import autobind from 'autobind-decorator' + +export class MessageModal extends Component { + static propTypes = { + open: PropTypes.bool, + icon: PropTypes.string.isRequired, + message: PropTypes.string.isRequired, + detail: PropTypes.string, + onDismiss: PropTypes.func + } + + @autobind + handleButtonPress() { + const { onDismiss } = this.props + + if (onDismiss) { + onDismiss() + } + } + + constructor(props) { + super(props) + this.state = { + + } + } + + render() { + const { open, icon, message, detail } = this.props + + return ( + + + + + {message} + {detail} + + OK + + + + + ) + } +} \ No newline at end of file diff --git a/mobile/src/Modal/index.js b/mobile/src/Modal/index.js new file mode 100644 index 0000000..7852cbf --- /dev/null +++ b/mobile/src/Modal/index.js @@ -0,0 +1 @@ +export { MessageModal } from './MessageModal' \ No newline at end of file diff --git a/mobile/src/screens/WorkItem.js b/mobile/src/WorkItem/WorkItem.js similarity index 70% rename from mobile/src/screens/WorkItem.js rename to mobile/src/WorkItem/WorkItem.js index 528c6bc..fd09f6e 100644 --- a/mobile/src/screens/WorkItem.js +++ b/mobile/src/WorkItem/WorkItem.js @@ -2,21 +2,6 @@ import React from 'react' import { StyleSheet, View, TouchableOpacity, Image, ScrollView, Picker, Text } from 'react-native' export class WorkItem extends React.Component { - static navigatorButtons = { - leftButtons: [ - { - id: 'cancel', - title: 'Cancel', - } - ], - rightButtons: [ - { - id: 'done', - title: 'Done', - } - ] - } - static styles = StyleSheet.create({ container: { height: '100%', @@ -27,20 +12,6 @@ export class WorkItem extends React.Component { constructor(props) { super(props) - this.props.navigator.setOnNavigatorEvent(this._handleNavigatorEvent.bind(this)) - } - - _handleNavigatorEvent(event) { - switch (event.id) { - case 'cancel': - case 'done': - this.props.navigator.pop() - break - } - } - - _handlePressButton() { - this.props.navigator.pop() } render() { diff --git a/mobile/src/WorkItem/WorkItemList.js b/mobile/src/WorkItem/WorkItemList.js new file mode 100644 index 0000000..4980bd9 --- /dev/null +++ b/mobile/src/WorkItem/WorkItemList.js @@ -0,0 +1,71 @@ +import React from 'react' +import { StyleSheet, View, TouchableOpacity, Image, FlatList, Text} from 'react-native' +import autobind from 'autobind-decorator' + +const styles = StyleSheet.create({ + container: { + height: '100%', + width: '100%', + justifyContent: 'flex-start', + backgroundColor: '#FFFFFF', + }, +}) + +const data = [ + {key: '1', type: 'work', location: 'Ossington Ave. | 0.2 mi.', state: 'open', latlng: { latitude: 43.653226, longitude: -79.383184 } }, + {key: '2', type: 'inspection', location: 'Alexandre St. | 0.7 mi.', state: 'open', latlng: { latitude: 43.648118, longitude: 79.392636 }}, + {key: '3', type: 'complaint', location: 'Bay St. | 0.8 mi.', state: 'open', latlng: { latitude: 43.640168, longitude: -79.409373 }}, + {key: '4', type: 'work', location: 'Bloor St. | 1.2 mi.', state: 'open', latlng: { latitude: 43.633110, longitude: -79.415880 }}, + {key: '5', type: 'inspection', location: 'Blue Jays Way | 2.2 mi.', state: 'open', latlng: { latitude: 43.653526, longitude: -79.361385 }}, + {key: '6', type: 'complaint', location: 'Christie St. | 3.0 mi.', state: 'open', latlng: { latitude: 43.663870, longitude: -79.383705 }}, + {key: '7', type: 'work', location: 'Cummer Ave. | 4.2 mi.', state: 'open', latlng: { latitude: 43.659166, longitude: -79.391350 }}, + {key: '8', type: 'complaint', location: 'Danforth Ave. | 4.7 mi.', state: 'open', latlng: { latitude: 43.663538, longitude: -79.423212 }}, +] + +const inspectionTypes = { + work: { + title: 'Work Order' + }, + inspection: { + title: 'Inspection', + }, + complaint: { + title: 'Complaint', + } +} + +export class WorkItemList extends React.Component { + constructor(props) { + super(props) + this._handleItemSelect = this._handleItemSelect.bind(this) + } + + @autobind + _handleItemSelect(item, index) { + this.props.history.push('/activity') + } + + render() { + return ( + + { + return ( + + {item.state.toUpperCase()} + + {Admin.inspectionTypes[item.type].title} + {item.location} + + (this._handleItemSelect(item, index))} > + + + + ) + }} /> + + ); + } +} diff --git a/mobile/src/WorkItem/index.js b/mobile/src/WorkItem/index.js new file mode 100644 index 0000000..a3f6b2c --- /dev/null +++ b/mobile/src/WorkItem/index.js @@ -0,0 +1,3 @@ +export { WorkItem } from './WorkItem' +export { WorkItemList } from './WorkItemList' + diff --git a/mobile/src/app.js b/mobile/src/app.js index 18c312b..244baf4 100644 --- a/mobile/src/app.js +++ b/mobile/src/app.js @@ -1,11 +1,15 @@ import React from 'react' -import { View, StyleSheet, Text, TouchableHighlight, Image } from 'react-native' +import { View, StyleSheet } from 'react-native' import { ViroARSceneNavigator, ViroARScene, ViroARPlane, ViroBox } from 'react-viro' -import { NativeRouter, Route, Link } from 'react-router-native' +import { NativeRouter, Route, Link, Switch } from 'react-router-native' import MapView from 'react-native-maps' -import { Home, Viewer, WorkItem, Admin, Login, Activity } from './screens' +import { WorkItem, WorkItemList } from './WorkItem' +import { Activity } from './Activity' +import { Home } from './Home' +import { ARViewer } from './ARViewer' +import { Login, Logout, ProtectedRoute, DefaultRoute } from './Auth' // See https://github.com/facebook/react-native/issues/12981 console.ignoredYellowBox = [ @@ -16,13 +20,17 @@ export default class App extends React.Component { render() { return ( - - - - - - - + + + + + + + + + + + ) diff --git a/mobile/src/screens/Admin.js b/mobile/src/screens/Admin.js deleted file mode 100644 index 0576614..0000000 --- a/mobile/src/screens/Admin.js +++ /dev/null @@ -1,93 +0,0 @@ -import React from 'react' -import { StyleSheet, View, TouchableOpacity, Image, FlatList, Text} from 'react-native' -import backImage from './images/back.png' -import rightArrowImage from './images/right-arrow.png' - -export class Admin extends React.Component { - static navigatorButtons = { - leftButtons: [ - { - id: 'back', - icon: require('./images/back.png'), - } - ] - } - - static data = [ - {key: '1', type: 'work', location: 'Ossington Ave. | 0.2 mi.', state: 'open', latlng: { latitude: 43.653226, longitude: -79.383184 } }, - {key: '2', type: 'inspection', location: 'Alexandre St. | 0.7 mi.', state: 'open', latlng: { latitude: 43.648118, longitude: 79.392636 }}, - {key: '3', type: 'complaint', location: 'Bay St. | 0.8 mi.', state: 'open', latlng: { latitude: 43.640168, longitude: -79.409373 }}, - {key: '4', type: 'work', location: 'Bloor St. | 1.2 mi.', state: 'open', latlng: { latitude: 43.633110, longitude: -79.415880 }}, - {key: '5', type: 'inspection', location: 'Blue Jays Way | 2.2 mi.', state: 'open', latlng: { latitude: 43.653526, longitude: -79.361385 }}, - {key: '6', type: 'complaint', location: 'Christie St. | 3.0 mi.', state: 'open', latlng: { latitude: 43.663870, longitude: -79.383705 }}, - {key: '7', type: 'work', location: 'Cummer Ave. | 4.2 mi.', state: 'open', latlng: { latitude: 43.659166, longitude: -79.391350 }}, - {key: '8', type: 'complaint', location: 'Danforth Ave. | 4.7 mi.', state: 'open', latlng: { latitude: 43.663538, longitude: -79.423212 }}, - ] - - static inspectionTypes = { - work: { - title: 'Work Order' - }, - inspection: { - title: 'Inspection', - }, - complaint: { - title: 'Complaint', - } - } - - static styles = StyleSheet.create({ - container: { - height: '100%', - width: '100%', - justifyContent: 'flex-start', - backgroundColor: '#FFFFFF', - }, - }) - - constructor(props) { - super(props) - this.props.navigator.setOnNavigatorEvent(this._handleNavigatorEvent.bind(this)) - this._handleItemSelect = this._handleItemSelect.bind(this) - } - - _handleNavigatorEvent(event) { - switch (event.id) { - case 'back': - this.props.navigator.pop() - break - } - } - - _handleItemSelect(item, index) { - this.props.navigator.push({ - screen: 'app.WorkItem', - title: 'Work Item', - passProps: { item, index }, - }) - } - - render() { - return ( - - { - return ( - - {item.state.toUpperCase()} - - {Admin.inspectionTypes[item.type].title} - {item.location} - - (this._handleItemSelect(item, index))} > - - - - ) - }} /> - - ); - } -} diff --git a/mobile/src/screens/Error.js b/mobile/src/screens/Error.js deleted file mode 100644 index a068362..0000000 --- a/mobile/src/screens/Error.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import { StyleSheet, Text, Image, Button } from 'react-native'; - -export class Error extends React.Component { - static navigatorStyle = { - navBarHidden: true, - } - - static styles = StyleSheet.create({ - page: { - minHeight: '50%', - flex: 1, - backgroundColor: '#fff', - alignItems: 'center', - justifyContent: 'center', - }, - }) - - render() { - return ( - - - ) - } -} diff --git a/mobile/src/screens/Home.js b/mobile/src/screens/Home.js deleted file mode 100644 index c8a2937..0000000 --- a/mobile/src/screens/Home.js +++ /dev/null @@ -1,166 +0,0 @@ -import React from 'react' -import { StyleSheet, Text, TextInput, FlatList, Image, View, TouchableOpacity } from 'react-native' -import MapView, { Marker } from 'react-native-maps' -import { api } from '../API' -import centerImage from './images/center.png' -import settingsImage from './images/settings.png' -import searchImage from './images/search.png' -import cancelImage from './images/cancel.png' -import pinImage from './images/pin.png' -import rightArrowImage from './images/right-arrow.png' - -export class Home extends React.Component { - static navigatorButtons = { - rightButtons: [ - { - id: 'viewer', - icon: require('./images/ar-glases.png'), - } - ], - leftButtons: [ - { - id: 'logout', - icon: require('./images/logout.png'), - } - ] - } - - static styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#fff', - alignItems: 'flex-start', - justifyContent: 'flex-start', - } - }) - - static data = [ - {key: '1', title: 'Remove Animal Carcass', location: 'Ossington Ave. | 0.2 mi.', state: 'planned', latlng: { latitude: 43.653226, longitude: -79.383184 } }, - {key: '2', title: 'Fix sign post', location: 'Alexandre St. | 0.7 mi.', state: 'open', latlng: { latitude: 43.648118, longitude: 79.392636 }}, - {key: '3', title: 'Overflowing trash', location: 'Bay St. | 0.8 mi.', state: 'open', latlng: { latitude: 43.640168, longitude: -79.409373 }}, - {key: '4', title: 'Leaking water pipe', location: 'Bloor St. | 1.2 mi.', state: 'planned', latlng: { latitude: 43.633110, longitude: -79.415880 }}, - {key: '5', title: 'Tree branch in road', location: 'Blue Jays Way | 2.2 mi.', state: 'open', latlng: { latitude: 43.653526, longitude: -79.361385 }}, - {key: '6', title: 'Washing machine on sidewalk', location: 'Christie St. | 3.0 mi.', state: 'open', latlng: { latitude: 43.663870, longitude: -79.383705 }}, - {key: '7', title: 'Dead moose', location: 'Cummer Ave. | 4.2 mi.', state: 'open', latlng: { latitude: 43.659166, longitude: -79.391350 }}, - {key: '8', title: 'Glass in street', location: 'Danforth Ave. | 4.7 mi.', state: 'open', latlng: { latitude: 43.663538, longitude: -79.423212 }}, - ] - - constructor(props) { - super(props); - this.props.navigator.setOnNavigatorEvent(this._handleNavigatorEvent.bind(this)) - this._handleAdminButton = this._handleAdminButton.bind(this) - this._handleItemSelect = this._handleItemSelect.bind(this) - } - - _handleNavigatorEvent(event) { - switch (event.id) { - case 'logout': - api.logout().then(() => { - this.props.navigator.showModal({ screen: 'app.Login' }) - }) - break - case 'viewer': - this.props.navigator.push({ - screen: 'app.Viewer', - animation: 'slide-horizontal', - }) - break - case 'willAppear': - break - case 'didAppear': - if (!api.loggedInUser) { - this.props.navigator.showModal({ screen: 'app.Login' }) - } - break - case 'willDisappear': - break - case 'didDisappear': - break - case 'willCommitPreview': - break - } - } - - _handleAdminButton() { - this.props.navigator.push({ - screen: 'app.Admin', - title: 'Work Items', - }) - } - - _handleItemSelect(item, index) { - this.props.navigator.push({ - screen: 'app.Activity', - title: 'Activity', - passProps: { item, index }, - }) - } - - render() { - return ( - - - { - Home.data.map(marker => ( - - )) - } - - - - - - - { - return ( - - {item.state.toUpperCase()} - - {item.title} - {item.location} - - (this._handleItemSelect(item, index))} > - - - - ) - }} /> - - - - - Hide List - - - - - - ); - } -} diff --git a/mobile/src/screens/images/back_arrow.png b/mobile/src/screens/images/back_arrow.png deleted file mode 100644 index 5b64c34..0000000 Binary files a/mobile/src/screens/images/back_arrow.png and /dev/null differ diff --git a/mobile/src/screens/index.js b/mobile/src/screens/index.js deleted file mode 100644 index 0a2e100..0000000 --- a/mobile/src/screens/index.js +++ /dev/null @@ -1,18 +0,0 @@ -import { Home } from './Home' -import { Login } from './Login' -import { Error } from './Error' -import { Viewer } from './Viewer' -import { Activity } from './Activity' -import { WorkItem } from './WorkItem' -import { Admin } from './Admin' -import { Navigation } from 'react-native-navigation' - -export function registerScreens() { - Navigation.registerComponent('app.Home', () => Home) - Navigation.registerComponent('app.Login', () => Login) - Navigation.registerComponent('app.Viewer', () => Viewer) - Navigation.registerComponent('app.Activity', () => Activity) - Navigation.registerComponent('app.Admin', () => Admin) - Navigation.registerComponent('app.WorkItem', () => WorkItem) - Navigation.registerComponent('app.Error', () => Error) -} diff --git a/mobile/src/ui/Header.js b/mobile/src/ui/Header.js new file mode 100644 index 0000000..5d84c0d --- /dev/null +++ b/mobile/src/ui/Header.js @@ -0,0 +1,39 @@ +import React, { Component } from 'react' +import { View, TouchableOpacity, Text } from 'react-native' +import { Icon } from './Icon' +import PropTypes from 'prop-types' +import { ifIphoneX } from 'react-native-iphone-x-helper' + +const headerButtonShape = { icon: PropTypes.string.isRequired, onPress: PropTypes.func } + +export class Header extends Component { + static propTypes = { + title: PropTypes.string.isRequired, + leftButton: PropTypes.shape(headerButtonShape), + rightButton: PropTypes.shape(headerButtonShape), + } + + render() { + const { title, leftButton, rightButton } = this.props + + return ( + + + + + {title} + + + + + ) + } +} diff --git a/mobile/src/ui/Icon.js b/mobile/src/ui/Icon.js new file mode 100644 index 0000000..b2022e2 --- /dev/null +++ b/mobile/src/ui/Icon.js @@ -0,0 +1,37 @@ +import React, { Component } from 'react' +import { Image } from 'react-native' +import PropTypes from 'prop-types' + +const images = { + logout: require('./images/logout.png'), + glasses: require('./images/ar-glasses.png'), + back: require('./images/back.png'), + hand: require('./images/hand.png'), + center: require('./images/center.png'), + rightArrow: require('./images/right-arrow.png'), + search: require('./images/search.png'), + settings: require('./images/settings.png'), +} + +export class Icon extends Component { + static propTypes = { + name: PropTypes.string.isRequired, + size: PropTypes.number, + margin: PropTypes.number, + style: PropTypes.object, + } + + static defaultProps = { + size: 50, + margin: 0, + } + + render() { + let { size, name, margin, style } = this.props + let source = images[name] || images['hand'] + + size -= margin * 2 + + return + } +} diff --git a/mobile/src/screens/images/ar-glases.png b/mobile/src/ui/images/ar-glasses.png similarity index 100% rename from mobile/src/screens/images/ar-glases.png rename to mobile/src/ui/images/ar-glasses.png diff --git a/mobile/src/ui/images/back.png b/mobile/src/ui/images/back.png new file mode 100644 index 0000000..40b09e4 Binary files /dev/null and b/mobile/src/ui/images/back.png differ diff --git a/mobile/src/screens/images/cancel.png b/mobile/src/ui/images/cancel.png similarity index 100% rename from mobile/src/screens/images/cancel.png rename to mobile/src/ui/images/cancel.png diff --git a/mobile/src/screens/images/center.png b/mobile/src/ui/images/center.png similarity index 100% rename from mobile/src/screens/images/center.png rename to mobile/src/ui/images/center.png diff --git a/mobile/src/ui/images/hand.png b/mobile/src/ui/images/hand.png new file mode 100644 index 0000000..aeb51f6 Binary files /dev/null and b/mobile/src/ui/images/hand.png differ diff --git a/mobile/src/screens/images/logout.png b/mobile/src/ui/images/logout.png similarity index 100% rename from mobile/src/screens/images/logout.png rename to mobile/src/ui/images/logout.png diff --git a/mobile/src/screens/images/right-arrow.png b/mobile/src/ui/images/right-arrow.png similarity index 100% rename from mobile/src/screens/images/right-arrow.png rename to mobile/src/ui/images/right-arrow.png diff --git a/mobile/src/screens/images/search.png b/mobile/src/ui/images/search.png similarity index 100% rename from mobile/src/screens/images/search.png rename to mobile/src/ui/images/search.png diff --git a/mobile/src/screens/images/settings.png b/mobile/src/ui/images/settings.png similarity index 100% rename from mobile/src/screens/images/settings.png rename to mobile/src/ui/images/settings.png diff --git a/mobile/src/ui/index.js b/mobile/src/ui/index.js index 1e79c76..ca42c78 100644 --- a/mobile/src/ui/index.js +++ b/mobile/src/ui/index.js @@ -1,3 +1,5 @@ export { BoundSwitch } from './BoundSwitch' export { BoundInput } from './BoundInput' export { BoundButton } from './BoundButton' +export { Icon } from './Icon' +export { Header } from './Header' diff --git a/server/src/api/routes/AuthRoutes.js b/server/src/api/routes/AuthRoutes.js index 2fcd8a0..7d06e68 100644 --- a/server/src/api/routes/AuthRoutes.js +++ b/server/src/api/routes/AuthRoutes.js @@ -77,7 +77,7 @@ export class AuthRoutes { } let cr = credential() - const isValid = cr.verify(JSON.stringify(user.passwordHash), req.body.password) + const isValid = await cr.verify(JSON.stringify(user.passwordHash), req.body.password) if (isValid) { user.loginToken = loginToken.pack(user._id.toString(), user.email) diff --git a/website/src/Auth/DefaultRoute.js b/website/src/Auth/DefaultRoute.js index 4612f70..6886e36 100644 --- a/website/src/Auth/DefaultRoute.js +++ b/website/src/Auth/DefaultRoute.js @@ -20,14 +20,14 @@ export class DefaultRoute extends Component { render() { const user = api.loggedInUser - let redirect = null + let path = null if (user) { if (!user.pending) { - redirect = + path = user.administrator ? '/home' : '/profile' } } else { - redirect = + path = '/login' } return ( @@ -37,7 +37,7 @@ export class DefaultRoute extends Component { return ( - {redirect} + {path ? : null} ) }} diff --git a/website/src/Auth/ProtectedRoute.js b/website/src/Auth/ProtectedRoute.js index 11d9a52..6e8eb5f 100644 --- a/website/src/Auth/ProtectedRoute.js +++ b/website/src/Auth/ProtectedRoute.js @@ -1,5 +1,5 @@ import React from 'react' -import { Route, Redirect } from 'react-router-dom' +import { Route, Redirect } from 'react-router' import { PropTypes } from 'prop-types' import { api } from 'src/API' import autobind from 'autobind-decorator'