Added Header, Icon and MessageModal. Refactor screens into directories.
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
BIN
design/Deighton AR System.pdf
Normal file
BIN
design/Deighton AR System.sketch
Normal file
@@ -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(<App />).toJSON();
|
||||
expect(rendered).toBeTruthy();
|
||||
});
|
||||
@@ -1,7 +1,4 @@
|
||||
{
|
||||
"expo": {
|
||||
"sdkVersion": "25.0.0"
|
||||
},
|
||||
"name": "DeightonAR",
|
||||
"displayName": "DeightonAR"
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -34,38 +34,15 @@
|
||||
ReferencedContainer = "container:DeightonAR.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
|
||||
BuildableName = "DeightonARTests.xctest"
|
||||
BlueprintName = "DeightonARTests"
|
||||
ReferencedContainer = "container:DeightonAR.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
|
||||
BuildableName = "DeightonARTests.xctest"
|
||||
BlueprintName = "DeightonARTests"
|
||||
ReferencedContainer = "container:DeightonAR.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
@@ -83,6 +60,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -12,9 +12,6 @@
|
||||
#import <React/RCTBundleURLProvider.h>
|
||||
#import <React/RCTRootView.h>
|
||||
|
||||
// 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,17 +24,6 @@
|
||||
|
||||
- (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
|
||||
@@ -46,11 +32,17 @@
|
||||
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
|
||||
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
self.window.backgroundColor = [UIColor whiteColor];
|
||||
[[RCCManager sharedInstance] initBridgeWithBundleURL:jsCodeLocation launchOptions:launchOptions];
|
||||
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];
|
||||
UIViewController *rootViewController = [UIViewController new];
|
||||
rootViewController.view = rootView;
|
||||
self.window.rootViewController = rootViewController;
|
||||
[self.window makeKeyAndVisible];
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
22
mobile/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 (
|
||||
<ViroARScene onTrackingInitialized={()=>{this.setState({text : "Hello World!"})}}>
|
||||
<ViroAmbientLight color="#ffffff" intensity={200}/>
|
||||
<ViroText text={this.state.text} scale={[.5, .5, .5]} position={[0, 0, -1]}
|
||||
<ViroText
|
||||
text={this.state.text} scale={[.5, .5, .5]}
|
||||
position={[0, 0, -1]}
|
||||
style={styles.helloWorldTextStyle} />
|
||||
<ViroARPlane>
|
||||
<ViroBox position={[0, .5, 0]} />
|
||||
@@ -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 {
|
||||
<ViroARSceneNavigator
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
apiKey='06F37B6A-74DA-4A83-965A-7DE2209A5C46'
|
||||
initialScene={{ scene: WorkItemSceneAR }} debug={true} />
|
||||
initialScene={{ scene: WorkItemSceneAR }} />
|
||||
|
||||
<View style={{position: 'absolute', left: 50, right: 0, top: 50}}>
|
||||
<TouchableHighlight style={styles.buttons} onPress={this._handlePress} underlayColor={'#00000000'} >
|
||||
<Image source={require("./images/back_arrow.png")} />
|
||||
<Image source={backImage} />
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
</View>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
1
mobile/src/ARViewer/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { ARViewer } from './ARViewer'
|
||||
@@ -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() {
|
||||
1
mobile/src/Activity/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { Activity } from './Activity'
|
||||
42
mobile/src/Auth/DefaultRoute.js
Normal file
@@ -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 (
|
||||
<Route path='/' render={() => (
|
||||
path ? <Redirect to={path} /> : null
|
||||
)} />
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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 (
|
||||
<KeyboardAvoidingView style={Login.styles.page} behavior='padding'
|
||||
keyboardVerticalOffset={Platform.select({ios: 0, android: -220})}>
|
||||
@@ -104,6 +113,12 @@ export class Login extends React.Component {
|
||||
<View style={Login.styles.buttonRow}>
|
||||
<BoundButton title='Login' name='login' width='100%' onPress={this.handleLogin} binder={this.state.binder} />
|
||||
</View>
|
||||
<MessageModal
|
||||
open={!!messageModal}
|
||||
icon={messageModal ? messageModal.icon : ''}
|
||||
message={messageModal ? messageModal.message : ''}
|
||||
detail={messageModal ? messageModal.detail : ''}
|
||||
onDismiss={messageModal && this.handleMessageDismiss} />
|
||||
</KeyboardAvoidingView>
|
||||
)
|
||||
}
|
||||
21
mobile/src/Auth/Logout.js
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
41
mobile/src/Auth/ProtectedRoute.js
Normal file
@@ -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 <Route {...this.props} />
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Can add redirect back in here - see website
|
||||
return <Redirect to='/login' />
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
4
mobile/src/Auth/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export { Login } from './Login'
|
||||
export { Logout } from './Logout'
|
||||
export { ProtectedRoute } from './ProtectedRoute'
|
||||
export { DefaultRoute } from './DefaultRoute'
|
||||
131
mobile/src/Home/Home.js
Normal file
@@ -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 (
|
||||
<View style={styles.container}>
|
||||
<Header title='Work Item Map' leftButton={{ icon: 'logout', onPress: this.handleLogout }} rightButton={{ icon: 'glasses' }} />
|
||||
<MapView
|
||||
style={{
|
||||
width: '100%', height: '50%',
|
||||
}}
|
||||
zoomControlEnabled
|
||||
initialRegion={{
|
||||
latitude: 43.653908,
|
||||
longitude: -79.384293,
|
||||
latitudeDelta: 0.0922,
|
||||
longitudeDelta: 0.0421,
|
||||
}}>
|
||||
{
|
||||
data.map(marker => (
|
||||
<Marker
|
||||
key={marker.key}
|
||||
coordinate={marker.latlng}
|
||||
title={marker.title}
|
||||
description={marker.location}
|
||||
image={pinImage}
|
||||
anchor={{x: 0.5, y: 1.0}} />
|
||||
))
|
||||
}
|
||||
</MapView>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', width: '100%', height: 40, backgroundColor: '#F4F4F4' }}>
|
||||
<Icon name='search' size={16} style={{marginLeft: 10, marginRight: 5, tintColor: 'gray' }} />
|
||||
<TextInput style={{ flexGrow: 1, height: '100%' }} placeholder='Search' />
|
||||
<Icon name='cancel' size={16} style={{marginLeft: 5, marginRight: 10, tintColor: 'gray' }} />
|
||||
</View>
|
||||
<FlatList
|
||||
style={{ width: '100%', flexGrow: 1 }}
|
||||
data={data}
|
||||
renderItem={({item, index}) => {
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', height: 50 }}>
|
||||
<Text style={{ fontSize: 8, width: 45, marginLeft: 5, alignSelf: 'center' }}>{item.state.toUpperCase()}</Text>
|
||||
<View style={{ width: '75%', flexDirection: 'column' }}>
|
||||
<Text style={{ fontSize: 20 }}>{item.title}</Text>
|
||||
<Text style={{ fontSize: 14, color: 'gray' }}>{item.location}</Text>
|
||||
</View>
|
||||
<TouchableOpacity style={{ alignSelf: 'center' }} onPress={() => (this._handleItemSelect(item, index))} >
|
||||
<Icon name='rightArrow' size={16} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)
|
||||
}} />
|
||||
<View style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: 45,
|
||||
backgroundColor: '#F4F4F4',
|
||||
}}>
|
||||
<TouchableOpacity onPress={this._handleMyLocation}>
|
||||
<Icon name='center' size={24} style={{ marginLeft: 15, tintColor: 'gray' }} />
|
||||
</TouchableOpacity>
|
||||
<Text style={{ color: 'gray', fontSize: 20, }}>Hide List</Text>
|
||||
<TouchableOpacity onPress={this._handleAdminButton}>
|
||||
<Icon name='settings' size={24} style={{ marginRight: 15, tintColor: 'gray' }} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
1
mobile/src/Home/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { Home } from './Home'
|
||||
62
mobile/src/Modal/MessageModal.js
Normal file
@@ -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 (
|
||||
<Modal isVisible={open}>
|
||||
<View style={{ alignSelf: 'center', padding: 5, backgroundColor: '#FFFFFF', flexDirection: 'row' }}>
|
||||
<Icon name={icon} size={130} margin={3} />
|
||||
<View style={{ marginLeft: 20, marginRight: 20, flexGrow: 1, flexDirection: 'column' }}>
|
||||
<Text style={{ marginTop: 5, fontSize: 18, alignSelf: 'center' }}>{message}</Text>
|
||||
<Text style={{ marginTop: 20, alignSelf: 'center' }}>{detail}</Text>
|
||||
<TouchableOpacity onPress={this.handleButtonPress}
|
||||
style={{
|
||||
alignSelf: 'center',
|
||||
backgroundColor: 'blue',
|
||||
marginTop: 20,
|
||||
marginBottom: 10,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 10,
|
||||
height: 40,
|
||||
width: 100,
|
||||
backgroundColor: '#3BB0FD'
|
||||
}}>
|
||||
<Text style={{ alignSelf: 'center', color: 'black' }}>OK</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
}
|
||||
1
mobile/src/Modal/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { MessageModal } from './MessageModal'
|
||||
@@ -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() {
|
||||
71
mobile/src/WorkItem/WorkItemList.js
Normal file
@@ -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 (
|
||||
<View style={styles.container}>
|
||||
<FlatList
|
||||
style={{ width: '100%', flexGrow: 1, paddingTop: 20, paddingBottom: 20 }}
|
||||
data={data}
|
||||
renderItem={({item, index}) => {
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', height: 50 }}>
|
||||
<Text style={{ fontSize: 8, width: 45, marginLeft: 15, alignSelf: 'center' }}>{item.state.toUpperCase()}</Text>
|
||||
<View style={{ flexDirection: 'column', width: '75%' }}>
|
||||
<Text style={{ fontSize: 20 }}>{Admin.inspectionTypes[item.type].title}</Text>
|
||||
<Text style={{ fontSize: 14, color: 'gray' }}>{item.location}</Text>
|
||||
</View>
|
||||
<TouchableOpacity style={{ alignSelf: 'center' }} onPress={() => (this._handleItemSelect(item, index))} >
|
||||
<Image source={rightArrowImage} style={{ width: 16, height: 16 }} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)
|
||||
}} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
3
mobile/src/WorkItem/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export { WorkItem } from './WorkItem'
|
||||
export { WorkItemList } from './WorkItemList'
|
||||
|
||||
@@ -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 (
|
||||
<NativeRouter>
|
||||
<View style={styles.container}>
|
||||
<Route exact path="/" component={Home}/>
|
||||
<Route path="/viewer" component={Viewer}/>
|
||||
<Route path="/workitem" component={WorkItem}/>
|
||||
<Route path="/admin" component={Admin}/>
|
||||
<Route path="/login" component={Login}/>
|
||||
<Route path="/activity" component={Activity}/>
|
||||
<View style={{ width: '100%', height: '100%' }}>
|
||||
<Switch>
|
||||
<Route exact path='/login' component={Login}/>
|
||||
<Route exact path='/logout' component={Logout}/>
|
||||
<ProtectedRoute exact path='/home' component={Home}/>
|
||||
<ProtectedRoute exact path='/viewer' component={ARViewer}/>
|
||||
<ProtectedRoute exact path='/activity' component={Activity}/>
|
||||
<ProtectedRoute exact admin path='/workitem' component={WorkItem}/>
|
||||
<ProtectedRoute exact admin path='/workitemlist' component={WorkItemList}/>
|
||||
<DefaultRoute />
|
||||
</Switch>
|
||||
</View>
|
||||
</NativeRouter>
|
||||
)
|
||||
|
||||
@@ -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 (
|
||||
<View style={Admin.styles.container}>
|
||||
<FlatList
|
||||
style={{ width: '100%', flexGrow: 1, paddingTop: 20, paddingBottom: 20 }}
|
||||
data={Admin.data}
|
||||
renderItem={({item, index}) => {
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', height: 50 }}>
|
||||
<Text style={{ fontSize: 8, width: 45, marginLeft: 15, alignSelf: 'center' }}>{item.state.toUpperCase()}</Text>
|
||||
<View style={{ flexDirection: 'column', width: '75%' }}>
|
||||
<Text style={{ fontSize: 20 }}>{Admin.inspectionTypes[item.type].title}</Text>
|
||||
<Text style={{ fontSize: 14, color: 'gray' }}>{item.location}</Text>
|
||||
</View>
|
||||
<TouchableOpacity style={{ alignSelf: 'center' }} onPress={() => (this._handleItemSelect(item, index))} >
|
||||
<Image source={rightArrowImage} style={{ width: 16, height: 16 }} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)
|
||||
}} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 (
|
||||
<View style={Login.styles.page}>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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 (
|
||||
<View style={Home.styles.container}>
|
||||
<MapView
|
||||
style={{
|
||||
width: '100%', height: '50%',
|
||||
}}
|
||||
zoomControlEnabled
|
||||
initialRegion={{
|
||||
latitude: 43.653908,
|
||||
longitude: -79.384293,
|
||||
latitudeDelta: 0.0922,
|
||||
longitudeDelta: 0.0421,
|
||||
}}>
|
||||
{
|
||||
Home.data.map(marker => (
|
||||
<Marker
|
||||
key={marker.key}
|
||||
coordinate={marker.latlng}
|
||||
title={marker.title}
|
||||
description={marker.location}
|
||||
image={pinImage}
|
||||
anchor={{x: 0.5, y: 1.0}} />
|
||||
))
|
||||
}
|
||||
</MapView>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', width: '100%', height: 40, backgroundColor: '#F4F4F4' }}>
|
||||
<Image source={searchImage} style={{ width: 16, height: 16, tintColor: 'gray', marginLeft: 10, marginRight: 5 }} />
|
||||
<TextInput style={{ flexGrow: 1, height: '100%' }} placeholder='Search' />
|
||||
<Image source={cancelImage} style={{ width: 16, height: 16, marginLeft: 5, marginRight: 10 }} />
|
||||
</View>
|
||||
<FlatList
|
||||
style={{ width: '100%', flexGrow: 1 }}
|
||||
data={Home.data}
|
||||
renderItem={({item, index}) => {
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', height: 50 }}>
|
||||
<Text style={{ fontSize: 8, width: 45, marginLeft: 5, alignSelf: 'center' }}>{item.state.toUpperCase()}</Text>
|
||||
<View style={{ width: '75%', flexDirection: 'column' }}>
|
||||
<Text style={{ fontSize: 20 }}>{item.title}</Text>
|
||||
<Text style={{ fontSize: 14, color: 'gray' }}>{item.location}</Text>
|
||||
</View>
|
||||
<TouchableOpacity style={{ alignSelf: 'center' }} onPress={() => (this._handleItemSelect(item, index))} >
|
||||
<Image source={rightArrowImage} style={{ width: 16, height: 16 }} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)
|
||||
}} />
|
||||
<View style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: 45,
|
||||
backgroundColor: '#F4F4F4',
|
||||
}}>
|
||||
<TouchableOpacity onPress={this._handleMyLocation}>
|
||||
<Image source={centerImage} style={{ height: 24, width: 24, marginLeft: 15, tintColor: 'gray' }} />
|
||||
</TouchableOpacity>
|
||||
<Text style={{ color: 'gray', fontSize: 20, }}>Hide List</Text>
|
||||
<TouchableOpacity onPress={this._handleAdminButton}>
|
||||
<Image source={settingsImage} style={{ height: 24, width: 24, marginRight: 15, tintColor: 'gray' }} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -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)
|
||||
}
|
||||
39
mobile/src/ui/Header.js
Normal file
@@ -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 (
|
||||
<View style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: 45,
|
||||
backgroundColor: '#F4F4F4',
|
||||
...ifIphoneX({ marginTop: 50 }, { marginTop: 20 })
|
||||
}}>
|
||||
<TouchableOpacity onPress={leftButton.onPress}>
|
||||
<Icon name={leftButton.icon} size={24} style={{ marginLeft: 15, tintColor: 'gray' }} />
|
||||
</TouchableOpacity>
|
||||
<Text style={{ color: 'gray', fontSize: 18, }}>{title}</Text>
|
||||
<TouchableOpacity onPress={rightButton.onPress}>
|
||||
<Icon name={rightButton.icon} size={24} style={{ marginRight: 15, tintColor: 'gray' }} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
37
mobile/src/ui/Icon.js
Normal file
@@ -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 <Image style={[{ width: size, height: size, margin }, style]} source={source} resizeMode='stretch' />
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
BIN
mobile/src/ui/images/back.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
BIN
mobile/src/ui/images/hand.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,3 +1,5 @@
|
||||
export { BoundSwitch } from './BoundSwitch'
|
||||
export { BoundInput } from './BoundInput'
|
||||
export { BoundButton } from './BoundButton'
|
||||
export { Icon } from './Icon'
|
||||
export { Header } from './Header'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = <Redirect to={user.administrator ? '/home' : '/profile'} />
|
||||
path = user.administrator ? '/home' : '/profile'
|
||||
}
|
||||
} else {
|
||||
redirect = <Redirect to='/login' />
|
||||
path = '/login'
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -37,7 +37,7 @@ export class DefaultRoute extends Component {
|
||||
return (
|
||||
<Fragment>
|
||||
<Column.Item grow />
|
||||
{redirect}
|
||||
{path ? <Redirect to={path} /> : null}
|
||||
</Fragment>
|
||||
)
|
||||
}}
|
||||
|
||||
@@ -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'
|
||||
|
||||