diff --git a/design/Deighton AR Design.sketch b/design/Deighton AR Design.sketch
index 8277f85..dd63102 100644
Binary files a/design/Deighton AR Design.sketch and b/design/Deighton AR Design.sketch differ
diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml
index e949e02..ea1b81f 100644
--- a/mobile/android/app/src/main/AndroidManifest.xml
+++ b/mobile/android/app/src/main/AndroidManifest.xml
@@ -6,6 +6,7 @@
+
NSCameraUsageDescription
$(PRODUCT_NAME) camera use
- NSLocationWhenInUseUsageDescription
-
NSPhotoLibraryUsageDescription
$(PRODUCT_NAME) photo use
UILaunchStoryboardName
@@ -52,6 +50,8 @@
UIInterfaceOrientationPortrait
UIInterfaceOrientationPortraitUpsideDown
+ NSLocationWhenInUseUsageDescription
+ $(PRODUCT_NAME) location use
UIViewControllerBasedStatusBarAppearance
diff --git a/mobile/package-lock.json b/mobile/package-lock.json
index 0c0ddba..476c6ab 100644
--- a/mobile/package-lock.json
+++ b/mobile/package-lock.json
@@ -5451,6 +5451,11 @@
"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-keyboard-spacer": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/react-native-keyboard-spacer/-/react-native-keyboard-spacer-0.4.1.tgz",
+ "integrity": "sha1-RvGKMgQyCYol6p+on1FD3SVNMy0="
+ },
"react-native-maps": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/react-native-maps/-/react-native-maps-0.20.1.tgz",
diff --git a/mobile/package.json b/mobile/package.json
index c7c19c7..babe66d 100644
--- a/mobile/package.json
+++ b/mobile/package.json
@@ -26,6 +26,7 @@
"react-form-binder": "^1.2.0",
"react-native": "^0.51.1",
"react-native-iphone-x-helper": "^1.0.2",
+ "react-native-keyboard-spacer": "^0.4.1",
"react-native-maps": "^0.20.1",
"react-native-modal": "^5.4.0",
"react-router-native": "^4.2.0",
diff --git a/mobile/src/API.js b/mobile/src/API.js
index c97589f..2009db2 100644
--- a/mobile/src/API.js
+++ b/mobile/src/API.js
@@ -44,7 +44,6 @@ class APIError extends Error {
class API extends EventEmitter {
constructor() {
super()
- // We don't know if the user has a valid token yet so assume they do and clean-up if they don't
this.user = { pending: true }
AsyncStorage.getItem(authTokenName).then((token) => {
diff --git a/mobile/src/Auth/DefaultRoute.js b/mobile/src/Auth/DefaultRoute.js
index 43d04d4..475a030 100644
--- a/mobile/src/Auth/DefaultRoute.js
+++ b/mobile/src/Auth/DefaultRoute.js
@@ -2,5 +2,6 @@ import React, { Fragment, Component } from 'react'
import { Route, Redirect } from 'react-router-native'
export const DefaultRoute = () => {
- return ()} />
+ // NOTE: When working on the app, change this to the page you are working on
+ return ()} />
}
diff --git a/mobile/src/Home/Home.js b/mobile/src/Home/Home.js
index 785d606..56290b8 100644
--- a/mobile/src/Home/Home.js
+++ b/mobile/src/Home/Home.js
@@ -1,5 +1,13 @@
import React from 'react'
-import { StyleSheet, Text, TextInput, FlatList, Image, View, TouchableOpacity } from 'react-native'
+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'
@@ -66,20 +74,34 @@ export class Home extends React.Component {
this.props.history.push('/arviewer')
}
+ @autobind
+ handleMyLocationPress() {
+ navigator.geolocation.getCurrentPosition((info) => {
+ if (this.map) {
+ this.map.animateToCoordinate({latitude: info.coords.latitude, longitude: info.coords.longitude})
+ }
+ })
+ }
+
render() {
return (
{ this.map = ref }}
style={{
width: '100%', height: '50%',
}}
+ showsUserLocation
+ showsBuildings={false}
+ showsTraffic={false}
+ showsIndoors={false}
zoomControlEnabled
initialRegion={{
latitude: 43.653908,
longitude: -79.384293,
latitudeDelta: 0.0922,
- longitudeDelta: 0.0421,
+ longitudeDelta: 0.0922,
}}>
{
data.map(marker => (
diff --git a/mobile/src/WorkItem/WorkItem.js b/mobile/src/WorkItem/WorkItem.js
index 54dc9ac..3076f45 100644
--- a/mobile/src/WorkItem/WorkItem.js
+++ b/mobile/src/WorkItem/WorkItem.js
@@ -10,10 +10,17 @@ import {
Platform,
TouchableOpacity
} from 'react-native'
-import { Icon, Header, PhotoButton } from '../ui'
import MapView, { Marker } from 'react-native-maps'
import { FormBinder } from 'react-form-binder'
-import { BoundInput, BoundButton } from '../ui'
+import {
+ BoundInput,
+ BoundButton,
+ BoundHeader,
+ Icon,
+ Header,
+ PhotoButton,
+ BoundOptionStrip,
+} from '../ui'
import autobind from 'autobind-decorator'
import { ifIphoneX, isIphoneX } from 'react-native-iphone-x-helper'
@@ -39,12 +46,26 @@ const styles = StyleSheet.create({
}
})
+const workItemOptions = [
+ { value: 'workOrder', text: 'Work Order' },
+ { value: 'inspection', text: 'Inspection' },
+ { value: 'complaint', text: 'Complaint' }
+]
+
export class WorkItem extends React.Component {
static bindings = {
+ header: {
+ noValue: true,
+ isDisabled: (r) => (!(r.anyModified && r.allValid)),
+ },
location: {
isValid: true,
+ isDisabled: true,
},
details: {
+ isValid: (r, v) => (v !== ''),
+ },
+ workItemType: {
isValid: true,
}
}
@@ -68,6 +89,22 @@ export class WorkItem extends React.Component {
}
}
+ @autobind
+ handleDonePress() {
+
+ }
+
+ @autobind
+ handleRegionChange(region) {
+ const { binder } = this.state
+ // NOTE: For some reason, object destructuring does not work here :(
+ const lon = region.longitude
+ const lat = region.latitude
+
+ this.setState(binder.updateFieldValue('location',
+ `${Math.abs(lon).toFixed(4)}°${lon > 0 ? 'S' : 'N'}, ${Math.abs(lat).toFixed(4)}°${lat > 0 ? 'W' : 'E'}`))
+ }
+
render() {
const { binder } = this.state
@@ -76,11 +113,21 @@ export class WorkItem extends React.Component {
style={{ width: '100%', height: '100%' }}
behavior='padding'
keyboardVerticalOffset={Platform.select({ios: 0, android: -220})}>
-
+
+
+
+
+ }}
+ onRegionChange={this.handleRegionChange}>
+
+
@@ -103,7 +153,7 @@ export class WorkItem extends React.Component {
-
+
{ isIphoneX ? : null }
diff --git a/mobile/src/ui/BoundButton.js b/mobile/src/ui/BoundButton.js
index 4802683..cfc16cb 100644
--- a/mobile/src/ui/BoundButton.js
+++ b/mobile/src/ui/BoundButton.js
@@ -8,7 +8,6 @@ export class BoundButton extends React.Component {
name: PropTypes.string.isRequired,
title: PropTypes.string,
binder: PropTypes.object.isRequired,
- submit: PropTypes.string,
onPress: PropTypes.func,
width: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
}
@@ -16,7 +15,7 @@ export class BoundButton extends React.Component {
constructor(props) {
super(props)
- let { name, binder } = this.props
+ const { name, binder } = this.props
binder.addListener(name, this.updateValue)
this.state = binder.getFieldState(name)
@@ -40,7 +39,7 @@ export class BoundButton extends React.Component {
}
render() {
- const { name, title, submit, width, onPress } = this.props
+ const { name, title, width, onPress } = this.props
const { visible, disabled } = this.state
if (!visible) {
diff --git a/mobile/src/ui/BoundHeader.js b/mobile/src/ui/BoundHeader.js
new file mode 100644
index 0000000..11d6c5e
--- /dev/null
+++ b/mobile/src/ui/BoundHeader.js
@@ -0,0 +1,60 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { Header } from '.'
+import autobind from 'autobind-decorator'
+
+const headerButtonShape = { icon: PropTypes.string.isRequired, onPress: PropTypes.func }
+
+export class BoundHeader extends React.Component {
+ static propTypes = {
+ name: PropTypes.string.isRequired,
+ title: PropTypes.string,
+ leftButton: PropTypes.shape(headerButtonShape).isRequired,
+ rightButton: PropTypes.shape(headerButtonShape),
+ binder: PropTypes.object.isRequired,
+ }
+
+ constructor(props) {
+ super(props)
+
+ const { name, binder } = this.props
+
+ binder.addListener(name, this.updateValue)
+ this.state = binder.getFieldState(name)
+ }
+
+ @autobind
+ updateValue(e) {
+ this.setState(e.state)
+ }
+
+ componentWillUnmount() {
+ this.props.binder.removeListener(this.props.name, this.updateValue)
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.binder !== this.props.binder) {
+ this.props.binder.removeListener(this.props.name, this.updateValue)
+ nextProps.binder.addListener(nextProps.name, this.updateValue)
+ this.setState(nextProps.binder.getFieldState(nextProps.name))
+ }
+ }
+
+ render() {
+ const { title, leftButton } = this.props
+ let { rightButton } = this.props
+ const { visible, disabled } = this.state
+
+ if (!visible) {
+ rightButton = null
+ }
+
+ return (
+
+ )
+ }
+}
diff --git a/mobile/src/ui/BoundInput.js b/mobile/src/ui/BoundInput.js
index 2d1d1a9..ac14906 100644
--- a/mobile/src/ui/BoundInput.js
+++ b/mobile/src/ui/BoundInput.js
@@ -10,7 +10,12 @@ export class BoundInput extends React.Component {
label: PropTypes.string,
binder: PropTypes.object.isRequired,
password: PropTypes.bool,
- placeholder: PropTypes.string
+ placeholder: PropTypes.string,
+ lines: PropTypes.number,
+ }
+
+ static defaultProps = {
+ lines: 1,
}
constructor(props) {
@@ -29,19 +34,30 @@ export class BoundInput extends React.Component {
}
render() {
- const { label, password, name, placeholder, message } = this.props
+ const { label, password, name, placeholder, message, lines } = this.props
const { visible, disabled, value, valid } = this.state
if (!visible) {
return null
}
- // TODO: Disabled
-
return (
{label}
- 1}
+ numberOfLines={lines}
+ editable={!disabled}
autoCapitalize='none'
underlineColorAndroid='white'
value={value}
diff --git a/mobile/src/ui/Header.js b/mobile/src/ui/Header.js
index 573d06a..b5fcd5b 100644
--- a/mobile/src/ui/Header.js
+++ b/mobile/src/ui/Header.js
@@ -9,17 +9,36 @@ const headerButtonShape = { icon: PropTypes.string.isRequired, onPress: PropType
export class Header extends Component {
static propTypes = {
title: PropTypes.string.isRequired,
- leftButton: PropTypes.shape(headerButtonShape),
+ leftButton: PropTypes.shape(headerButtonShape).isRequired,
rightButton: PropTypes.shape(headerButtonShape),
+ disabled: PropTypes.bool,
}
static defaultProps = {
rightButton: { icon: 'none' },
leftButton: { icon: 'none' },
+ visible: true,
}
render() {
- const { title, leftButton, rightButton } = this.props
+ const { title, leftButton, rightButton, disabled, visible } = this.props
+ let right = null
+
+ if (!rightButton) {
+ right = (
+
+ )
+ } else if (disabled) {
+ right = (
+
+ )
+ } else {
+ right = (
+
+
+
+ )
+ }
return (
{title}
-
-
-
+ {right}
)
}
diff --git a/mobile/src/ui/Icon.js b/mobile/src/ui/Icon.js
index 218ad83..4834018 100644
--- a/mobile/src/ui/Icon.js
+++ b/mobile/src/ui/Icon.js
@@ -13,6 +13,7 @@ const images = {
settings: require('./images/settings.png'),
add: require('./images/add.png'),
done: require('./images/done.png'),
+ target: require('./images/target.png'),
}
export class Icon extends Component {
diff --git a/mobile/src/ui/OptionStrip.js b/mobile/src/ui/OptionStrip.js
index 60b1bf2..305e4a7 100644
--- a/mobile/src/ui/OptionStrip.js
+++ b/mobile/src/ui/OptionStrip.js
@@ -23,7 +23,7 @@ export class OptionStrip extends Component {
}
componentWillReceiveProps(newProps) {
- if (newProps.options !== props.options || newProps.value !== props.value) {
+ if (newProps.options !== this.props.options || newProps.value !== this.props.value) {
this.setState({ selectedIndex: this.getSelectedIndex(newProps.options, newProps.value)})
}
}
diff --git a/mobile/src/ui/images/done.png b/mobile/src/ui/images/done.png
index 5afda9f..8c07bb1 100644
Binary files a/mobile/src/ui/images/done.png and b/mobile/src/ui/images/done.png differ
diff --git a/mobile/src/ui/images/target.png b/mobile/src/ui/images/target.png
new file mode 100644
index 0000000..f9f30a4
Binary files /dev/null and b/mobile/src/ui/images/target.png differ
diff --git a/mobile/src/ui/index.js b/mobile/src/ui/index.js
index 4d00679..c65ea16 100644
--- a/mobile/src/ui/index.js
+++ b/mobile/src/ui/index.js
@@ -1,8 +1,9 @@
-export { BoundSwitch } from './BoundSwitch'
-export { BoundInput } from './BoundInput'
-export { BoundButton } from './BoundButton'
-export { BoundOptionStrip } from './BoundOptionStrip'
export { Icon } from './Icon'
export { Header } from './Header'
export { PhotoButton } from './PhotoButton'
export { OptionStrip } from './OptionStrip'
+export { BoundSwitch } from './BoundSwitch'
+export { BoundInput } from './BoundInput'
+export { BoundButton } from './BoundButton'
+export { BoundOptionStrip } from './BoundOptionStrip'
+export { BoundHeader } from './BoundHeader'