Target tracking location, BoundHeader, WorkItem page done.

This commit is contained in:
John Lyon-Smith
2018-04-04 14:25:58 -07:00
parent 72af9a7035
commit e6bd1f8fed
18 changed files with 202 additions and 29 deletions

View File

@@ -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) => {

View File

@@ -2,5 +2,6 @@ import React, { Fragment, Component } from 'react'
import { Route, Redirect } from 'react-router-native'
export const DefaultRoute = () => {
return <Route render={() => (<Redirect to={'/home'} />)} />
// NOTE: When working on the app, change this to the page you are working on
return <Route render={() => (<Redirect to={'/workitem'} />)} />
}

View File

@@ -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 (
<View style={styles.container}>
<Header title='Work Item Map' leftButton={{ icon: 'logout', onPress: this.handleLogoutPress }} rightButton={{ icon: 'glasses', onPress: this.handleGlassesPress }} />
<MapView
ref={ref => { 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 => (

View File

@@ -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})}>
<Header title='Work Item' leftButton={{ icon: 'back', onPress: this.handleBackPress }} rightButton={{ icon: 'done' }} />
<BoundHeader
binder={binder}
name='header'
title='Work Item'
leftButton={{ icon: 'back', onPress: this.handleBackPress }}
rightButton={{ icon: 'done', onPress: this.handleDonePress }} />
<ScrollView style={styles.container}>
<View style={styles.panel}>
<BoundOptionStrip binder={binder} name='workItemType' label='Work Item Type:' options={workItemOptions} />
</View>
<View style={styles.panel}>
<MapView
style={{
flexDirection: 'column',
justifyContent: 'center',
width: '100%',
height: 400,
marginBottom: 10,
@@ -91,7 +138,10 @@ export class WorkItem extends React.Component {
longitude: -79.384293,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}} />
}}
onRegionChange={this.handleRegionChange}>
<Icon name='target' size={24} style={{ alignSelf: 'center' }} pointerEvents={false} />
</MapView>
<BoundInput binder={binder} name='location' label='Location:' />
</View>
<View style={styles.panel}>
@@ -103,7 +153,7 @@ export class WorkItem extends React.Component {
</View>
</View>
<View style={styles.panel}>
<BoundInput binder={binder} name='details' label='Details:' message='You must supply details for the work item' />
<BoundInput binder={binder} name='details' lines={4} label='Details:' message='You must supply details for the work item' />
</View>
{ isIphoneX ? <View style={{ height: 30, width: '100%' }} /> : null }
</ScrollView>

View File

@@ -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) {

View File

@@ -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 (
<Header
title={title}
disabled={disabled}
leftButton={leftButton}
rightButton={rightButton} />
)
}
}

View File

@@ -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 (
<View style={{ width: '100%' }}>
<Text style={{ color: 'black', fontSize: 14, marginBottom: 5 }}>{label}</Text>
<TextInput style={{ width: '100%', paddingLeft: 5, paddingRight: 5, height: 40, borderColor: 'gray', borderWidth: 1, fontSize: 16 }}
<TextInput
style={{
width: '100%',
paddingLeft: 5,
paddingRight: 5,
borderColor: 'gray',
borderWidth: 1,
fontSize: 16,
paddingTop: 7,
paddingBottom: 7,
}}
multiline={lines > 1}
numberOfLines={lines}
editable={!disabled}
autoCapitalize='none'
underlineColorAndroid='white'
value={value}

View File

@@ -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 = (
<Icon name='' size={24} style={{ marginRight: 15 }} />
)
} else if (disabled) {
right = (
<Icon name={rightButton.icon} size={24} style={{ marginRight: 15, tintColor: 'lightgrey' }} />
)
} else {
right = (
<TouchableOpacity onPress={rightButton.onPress}>
<Icon name={rightButton.icon} size={24} style={{ marginRight: 15, tintColor: 'grey' }} />
</TouchableOpacity>
)
}
return (
<View style={{
@@ -35,9 +54,7 @@ export class Header extends Component {
<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>
{right}
</View>
)
}

View File

@@ -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 {

View File

@@ -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)})
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 685 B

After

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -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'