Work Item and Activity screens mostly complete

This commit is contained in:
John Lyon-Smith
2018-04-03 17:25:59 -07:00
parent 410d2fde4f
commit 72af9a7035
25 changed files with 512 additions and 141 deletions

Binary file not shown.

View File

@@ -44,7 +44,8 @@ class APIError extends Error {
class API extends EventEmitter { class API extends EventEmitter {
constructor() { constructor() {
super() super()
this.user = null // 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) => { AsyncStorage.getItem(authTokenName).then((token) => {
if (!token) { if (!token) {
@@ -52,7 +53,6 @@ class API extends EventEmitter {
} }
this.token = token this.token = token
this.user = { pending: true }
return this.who() return this.who()
}).then((user) => { }).then((user) => {
this.user = user this.user = user
@@ -61,7 +61,7 @@ class API extends EventEmitter {
}).catch(() => { }).catch(() => {
AsyncStorage.removeItem(authTokenName) AsyncStorage.removeItem(authTokenName)
this.token = null this.token = null
this.user = null this.user = {}
this.socket = null this.socket = null
this.emit('logout') this.emit('logout')
}) })
@@ -83,12 +83,6 @@ class API extends EventEmitter {
// Filter the few massages that affect our cached user data to avoid a server round trip // Filter the few massages that affect our cached user data to avoid a server round trip
switch (eventName) { switch (eventName) {
case 'newThumbnailImage':
this.user.thumbnailImageId = eventData.imageId
break
case 'newProfileImage':
this.user.imageId = eventData.imageId
break
default: default:
// Nothing to see here... // Nothing to see here...
break break
@@ -219,7 +213,7 @@ class API extends EventEmitter {
// Regardless of response, always logout in the client // Regardless of response, always logout in the client
AsyncStorage.removeItem(authTokenName) AsyncStorage.removeItem(authTokenName)
this.token = null this.token = null
this.user = null this.user = {}
this.disconnectSocket() this.disconnectSocket()
this.emit('logout') this.emit('logout')
} }

View File

@@ -1,43 +1,72 @@
import React from 'react' import React from 'react'
import { StyleSheet, View } from 'react-native' import { StyleSheet, View, TouchableHighlight, Image } from 'react-native'
import { import {
ViroARSceneNavigator, ViroARScene, ViroARPlane, ViroBox, ViroText, ViroAmbientLight ViroARSceneNavigator,
ViroARScene,
ViroARPlane,
ViroBox,
ViroNode,
ViroAmbientLight,
ViroSpotLight,
Viro3DObject,
ViroSurface,
} from 'react-viro' } from 'react-viro'
import autobind from 'autobind-decorator' import autobind from 'autobind-decorator'
import backImage from './images/back.png' import backImage from './images/back.png'
const styles = { const styles = {
helloWorldTextStyle: {
fontFamily: 'Arial',
fontSize: 30,
color: '#ffffff',
textAlignVertical: 'center',
textAlign: 'center',
},
buttons : { buttons : {
height: 80, height: 80,
width: 80, width: 80,
}, },
} }
const shapes = {
hardhat: { shape: require('./models/hardhat.obj'), materials: [ require('./models/hardhat.mtl') ] },
question: { shape: require('./models/question.obj'), materials: [ require('./models/question.mtl') ] },
clipboard: { shape: require('./models/question.obj'), materials: [ require('./models/clipboard.mtl') ] },
}
class WorkItemSceneAR extends React.Component { class WorkItemSceneAR extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { this.state = {
text : "Initializing AR..." position: [0, .2, 0],
scale: [.2, .2, .2],
} }
} }
render() { render() {
return ( return (
<ViroARScene onTrackingInitialized={()=>{this.setState({text : "Hello World!"})}}> <ViroARScene>
<ViroAmbientLight color="#ffffff" intensity={200}/> <ViroAmbientLight color="#ffffff" intensity={200}/>
<ViroText
text={this.state.text} scale={[.5, .5, .5]}
position={[0, 0, -1]}
style={styles.helloWorldTextStyle} />
<ViroARPlane> <ViroARPlane>
<ViroBox position={[0, .5, 0]} /> <ViroNode
visible={true}
position={this.state.position}
scale={this.state.scale}
key='hardhat'>
<ViroSpotLight
innerAngle={5}
outerAngle={20}
direction={[0, -1 ,0]}
position={[0, 4, 0]}
color="#ffffff"
castsShadow={true}
shadowNearZ={.1}
shadowFarZ={6}
shadowOpacity={.9} />
<Viro3DObject
position={[0, 0, 0]}
source={shapes['hardhat'].shape}
type = "OBJ" />
<ViroSurface
rotation={[-90, 0, 0]}
position={[0, -.001, 0]}
width={2.5} height={2.5}
arShadowReceiver={true}
ignoreEventHandling={true} />
</ViroNode>
</ViroARPlane> </ViroARPlane>
</ViroARScene> </ViroARScene>
) )
@@ -62,7 +91,7 @@ export class ARViewer extends React.Component {
apiKey='06F37B6A-74DA-4A83-965A-7DE2209A5C46' apiKey='06F37B6A-74DA-4A83-965A-7DE2209A5C46'
initialScene={{ scene: WorkItemSceneAR }} /> initialScene={{ scene: WorkItemSceneAR }} />
<View style={{position: 'absolute', left: 50, right: 0, top: 50}}> <View style={{position: 'absolute', left: 30, right: 0, top: 50}}>
<TouchableHighlight style={styles.buttons} onPress={this._handlePress} underlayColor={'#00000000'} > <TouchableHighlight style={styles.buttons} onPress={this._handlePress} underlayColor={'#00000000'} >
<Image source={backImage} /> <Image source={backImage} />
</TouchableHighlight> </TouchableHighlight>

View File

@@ -1,30 +1,142 @@
import React from 'react' import React from 'react'
import { StyleSheet, View, TouchableOpacity, Image, ScrollView } from 'react-native' import {
StyleSheet,
View,
Image,
ScrollView,
Text,
TextInput,
KeyboardAvoidingView,
Platform,
TouchableOpacity
} from 'react-native'
import MapView, { Marker } from 'react-native-maps'
import { FormBinder } from 'react-form-binder'
import { Icon, Header, PhotoButton, BoundInput, BoundButton, BoundOptionStrip } from '../ui'
import autobind from 'autobind-decorator' import autobind from 'autobind-decorator'
import { ifIphoneX, isIphoneX } from 'react-native-iphone-x-helper'
const styles = StyleSheet.create({
container: {
flexGrow: 1,
backgroundColor: '#DDDDDD',
},
panel: {
width: '94%',
backgroundColor: 'white',
alignSelf: 'center',
marginTop: '3%',
shadowColor: 'gray',
shadowOffset: { width: 2, height: 2 },
shadowRadius: 2,
shadowOpacity: .5,
padding: 10,
},
label: {
fontSize: 14,
marginBottom: 4,
}
})
export class Activity extends React.Component { export class Activity extends React.Component {
static styles = StyleSheet.create({ static bindings = {
container: { dateTime: {
height: '100%', isValid: true,
width: '100%',
backgroundColor: '#AAAAAA',
}, },
}) location: {
isValid: true,
},
details: {
isValid: true,
},
resolution: {
isValid: true,
},
notes: {
isValid: true,
},
status: {
isValid: true,
}
}
constructor(props) { constructor(props) {
super(props) super(props)
this.state = {
binder: new FormBinder({}, Activity.bindings),
messageModal: null,
}
} }
_handlePushButton(event) { @autobind
this.props.history.goBack() handleBackPress() {
const { history } = this.props
if (history.length > 1) {
history.goBack()
} else {
history.replace('/home')
}
} }
render() { render() {
const { binder } = this.state
return ( return (
<ScrollView style={Activity.styles.container}> <View style={{ width: '100%', height: '100%' }}>
<View style={{ width: '94%', height: 300, backgroundColor: 'white', alignSelf: 'center', marginTop: '3%', shadowColor: 'gray', shadowOffset: { width: 2, height: 2 }, shadowRadius: 2, shadowOpacity: .5 }} /> <Header title='Activity' leftButton={{ icon: 'back', onPress: this.handleBackPress }} />
<View style={{ width: '94%', height: 300, backgroundColor: 'white', alignSelf: 'center', marginTop: '3%', shadowColor: 'gray', shadowOffset: { width: 2, height: 2 }, shadowRadius: 2, shadowOpacity: .5 }} /> <ScrollView style={styles.container}>
</ScrollView> <View style={styles.panel}>
<BoundInput binder={binder} name='resolution' label='Resolution:' />
</View>
<View style={styles.panel}>
<BoundOptionStrip
binder={binder}
options={[
{ value: 'planned', text: 'Planned' },
{ value: 'open', text: 'Open' },
{ value: 'onHold', text: 'On Hold' },
{ value: 'closed', text: 'Closed' },
]}
label='Status:'
name='status' />
</View>
<View style={styles.panel}>
<BoundInput binder={binder} name='notes' label='Notes:' />
</View>
<View style={styles.panel}>
<BoundInput binder={binder} name='dateTime' label='Date &amp; Time:' />
<BoundInput binder={binder} name='location' label='Location:' />
<MapView
style={{
width: '100%',
height: 400,
marginTop: 10,
}}
zoomControlEnabled
initialRegion={{
latitude: 43.653908,
longitude: -79.384293,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}} />
</View>
<View style={styles.panel}>
<Text style={styles.label}>Pictures:</Text>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginBottom: 5 }}>
<PhotoButton />
<PhotoButton />
<PhotoButton />
</View>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: 5 }}>
<PhotoButton />
<PhotoButton />
<PhotoButton />
</View>
</View>
{ isIphoneX ? <View style={{ height: 30, width: '100%' }} /> : null }
</ScrollView>
</View>
); );
} }
} }

View File

@@ -1,42 +1,6 @@
import React, { Fragment, Component } from 'react' import React, { Fragment, Component } from 'react'
import { api } from '../API'
import { Route, Redirect } from 'react-router-native' import { Route, Redirect } from 'react-router-native'
import { View } from 'react-native'
import autobind from 'autobind-decorator'
export class DefaultRoute extends Component { export const DefaultRoute = () => {
@autobind return <Route render={() => (<Redirect to={'/home'} />)} />
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
)} />
)
}
} }

View File

@@ -17,25 +17,25 @@ export class ProtectedRoute extends React.Component {
componentDidMount() { componentDidMount() {
api.addListener('login', this.updateComponent) api.addListener('login', this.updateComponent)
api.addListener('logout', this.updateComponent)
} }
componentWillUnmount() { componentWillUnmount() {
api.removeListener('login', this.updateComponent) api.removeListener('login', this.updateComponent)
api.removeListener('logout', this.updateComponent)
} }
render(props) { render(props) {
const user = api.loggedInUser const user = api.loggedInUser
if (user) { if (user.pending) {
if (user.pending) { return null
// The API might be in the middle of fetching the user information } else {
return null if (!user._id || (this.props.admin && !user.administrator)) {
} else if (!this.props.admin || (this.props.admin && user.administrator)) { return <Redirect to='/login' />
} else {
return <Route {...this.props} /> return <Route {...this.props} />
} }
} }
// TODO: Can add redirect back in here - see website
return <Redirect to='/login' />
} }
} }

View File

@@ -5,6 +5,7 @@ import { Icon, Header } from '../ui'
import { api } from '../API' import { api } from '../API'
import autobind from 'autobind-decorator' import autobind from 'autobind-decorator'
import pinImage from './images/pin.png' import pinImage from './images/pin.png'
import { ifIphoneX } from 'react-native-iphone-x-helper'
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@@ -46,24 +47,29 @@ export class Home extends React.Component {
} }
@autobind @autobind
handleAdminButton() { handleWorkItemsListPress() {
this.props.history.replace('/admin') this.props.history.push('/workitemlist')
} }
@autobind @autobind
handleItemSelect(item, index) { handleItemSelect(item, index) {
this.props.history.replace('/activity') this.props.history.push('/activity')
} }
@autobind @autobind
handleLogout() { handleLogoutPress() {
this.props.history.replace('/logout') this.props.history.replace('/logout')
} }
@autobind
handleGlassesPress() {
this.props.history.push('/arviewer')
}
render() { render() {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Header title='Work Item Map' leftButton={{ icon: 'logout', onPress: this.handleLogout }} rightButton={{ icon: 'glasses' }} /> <Header title='Work Item Map' leftButton={{ icon: 'logout', onPress: this.handleLogoutPress }} rightButton={{ icon: 'glasses', onPress: this.handleGlassesPress }} />
<MapView <MapView
style={{ style={{
width: '100%', height: '50%', width: '100%', height: '50%',
@@ -103,7 +109,7 @@ export class Home extends React.Component {
<Text style={{ fontSize: 20 }}>{item.title}</Text> <Text style={{ fontSize: 20 }}>{item.title}</Text>
<Text style={{ fontSize: 14, color: 'gray' }}>{item.location}</Text> <Text style={{ fontSize: 14, color: 'gray' }}>{item.location}</Text>
</View> </View>
<TouchableOpacity style={{ alignSelf: 'center' }} onPress={() => (this._handleItemSelect(item, index))} > <TouchableOpacity style={{ alignSelf: 'center' }} onPress={() => (this.handleItemSelect(item, index))} >
<Icon name='rightArrow' size={16} /> <Icon name='rightArrow' size={16} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
@@ -116,12 +122,13 @@ export class Home extends React.Component {
width: '100%', width: '100%',
height: 45, height: 45,
backgroundColor: '#F4F4F4', backgroundColor: '#F4F4F4',
...ifIphoneX({ marginBottom: 22 }, {}),
}}> }}>
<TouchableOpacity onPress={this._handleMyLocation}> <TouchableOpacity onPress={this.handleMyLocationPress}>
<Icon name='center' size={24} style={{ marginLeft: 15, tintColor: 'gray' }} /> <Icon name='center' size={24} style={{ marginLeft: 15, tintColor: 'gray' }} />
</TouchableOpacity> </TouchableOpacity>
<Text style={{ color: 'gray', fontSize: 20, }}>Hide List</Text> <Text style={{ color: 'gray', fontSize: 20, }}>Hide List</Text>
<TouchableOpacity onPress={this._handleAdminButton}> <TouchableOpacity onPress={this.handleWorkItemsListPress}>
<Icon name='settings' size={24} style={{ marginRight: 15, tintColor: 'gray' }} /> <Icon name='settings' size={24} style={{ marginRight: 15, tintColor: 'gray' }} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>

View File

@@ -1,32 +1,113 @@
import React from 'react' import React from 'react'
import { StyleSheet, View, TouchableOpacity, Image, ScrollView, Picker, Text } from 'react-native' import {
StyleSheet,
View,
Image,
ScrollView,
Text,
TextInput,
KeyboardAvoidingView,
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 autobind from 'autobind-decorator'
import { ifIphoneX, isIphoneX } from 'react-native-iphone-x-helper'
const styles = StyleSheet.create({
container: {
flexGrow: 1,
backgroundColor: '#DDDDDD',
},
panel: {
width: '94%',
backgroundColor: 'white',
alignSelf: 'center',
marginTop: '3%',
shadowColor: 'gray',
shadowOffset: { width: 2, height: 2 },
shadowRadius: 2,
shadowOpacity: .5,
padding: 10,
},
label: {
fontSize: 14,
marginBottom: 4,
}
})
export class WorkItem extends React.Component { export class WorkItem extends React.Component {
static styles = StyleSheet.create({ static bindings = {
container: { location: {
height: '100%', isValid: true,
width: '100%',
backgroundColor: '#DDDDDD',
}, },
}) details: {
isValid: true,
}
}
constructor(props) { constructor(props) {
super(props) super(props)
this.state = {
binder: new FormBinder({}, WorkItem.bindings),
messageModal: null,
}
}
@autobind
handleBackPress() {
const { history } = this.props
if (history.length > 1) {
history.goBack()
} else {
history.replace('/home')
}
} }
render() { render() {
const { binder } = this.state
return ( return (
<ScrollView style={WorkItem.styles.container}> <KeyboardAvoidingView
<View style={{ width: '94%', backgroundColor: 'white', alignSelf: 'center', marginTop: '3%', shadowColor: 'gray', shadowOffset: { width: 2, height: 2 }, shadowRadius: 2, shadowOpacity: .5 }}> style={{ width: '100%', height: '100%' }}
<Text>Work Item</Text> behavior='padding'
<Picker selectedValue={this.props.item.type}> keyboardVerticalOffset={Platform.select({ios: 0, android: -220})}>
<Picker.Item label='Work Item' value='work' /> <Header title='Work Item' leftButton={{ icon: 'back', onPress: this.handleBackPress }} rightButton={{ icon: 'done' }} />
<Picker.Item label='Inspection' value='inspection' /> <ScrollView style={styles.container}>
<Picker.Item label='Complaint' value='complaint' /> <View style={styles.panel}>
</Picker> <MapView
</View> style={{
<View style={{ width: '94%', height: 300, backgroundColor: 'white', alignSelf: 'center', marginTop: '3%', shadowColor: 'gray', shadowOffset: { width: 2, height: 2 }, shadowRadius: 2, shadowOpacity: .5 }} /> width: '100%',
</ScrollView> height: 400,
); marginBottom: 10,
}}
zoomControlEnabled
initialRegion={{
latitude: 43.653908,
longitude: -79.384293,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}} />
<BoundInput binder={binder} name='location' label='Location:' />
</View>
<View style={styles.panel}>
<Text style={styles.label}>Pictures:</Text>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<PhotoButton />
<PhotoButton />
<PhotoButton />
</View>
</View>
<View style={styles.panel}>
<BoundInput binder={binder} name='details' label='Details:' message='You must supply details for the work item' />
</View>
{ isIphoneX ? <View style={{ height: 30, width: '100%' }} /> : null }
</ScrollView>
</KeyboardAvoidingView>
)
} }
} }

View File

@@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import { StyleSheet, View, TouchableOpacity, Image, FlatList, Text} from 'react-native' import { StyleSheet, View, TouchableOpacity, Image, FlatList, Text} from 'react-native'
import { Icon, Header } from '../ui'
import autobind from 'autobind-decorator' import autobind from 'autobind-decorator'
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@@ -37,35 +38,51 @@ const inspectionTypes = {
export class WorkItemList extends React.Component { export class WorkItemList extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
this._handleItemSelect = this._handleItemSelect.bind(this)
} }
@autobind @autobind
_handleItemSelect(item, index) { handleItemSelect(item, index) {
this.props.history.push('/activity') this.props.history.push('/activity')
} }
@autobind
handleAddPress(item, index) {
this.props.history.push('/workitem')
}
@autobind
handleBackPress() {
const { history } = this.props
if (history.length > 1) {
history.goBack()
} else {
history.replace('/home')
}
}
render() { render() {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<FlatList <Header title='Work Items' leftButton={{ icon: 'back', onPress: this.handleBackPress }} rightButton={{ icon: 'add', onPress: this.handleAddPress }} />
style={{ width: '100%', flexGrow: 1, paddingTop: 20, paddingBottom: 20 }} <FlatList
data={data} style={{ width: '100%', flexGrow: 1, paddingTop: 20, paddingBottom: 20 }}
renderItem={({item, index}) => { data={data}
return ( renderItem={({item, index}) => {
<View style={{ flexDirection: 'row', height: 50 }}> return (
<Text style={{ fontSize: 8, width: 45, marginLeft: 15, alignSelf: 'center' }}>{item.state.toUpperCase()}</Text> <View style={{ flexDirection: 'row', height: 50 }}>
<View style={{ flexDirection: 'column', width: '75%' }}> <Text style={{ fontSize: 8, width: 45, marginLeft: 15, alignSelf: 'center' }}>{item.state.toUpperCase()}</Text>
<Text style={{ fontSize: 20 }}>{Admin.inspectionTypes[item.type].title}</Text> <View style={{ flexDirection: 'column', width: '75%' }}>
<Text style={{ fontSize: 14, color: 'gray' }}>{item.location}</Text> <Text style={{ fontSize: 20 }}>{inspectionTypes[item.type].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>
<TouchableOpacity style={{ alignSelf: 'center' }} onPress={() => (this._handleItemSelect(item, index))} > )
<Image source={rightArrowImage} style={{ width: 16, height: 16 }} /> }} />
</TouchableOpacity>
</View>
)
}} />
</View> </View>
); )
} }
} }

View File

@@ -25,7 +25,7 @@ export default class App extends React.Component {
<Route exact path='/login' component={Login}/> <Route exact path='/login' component={Login}/>
<Route exact path='/logout' component={Logout}/> <Route exact path='/logout' component={Logout}/>
<ProtectedRoute exact path='/home' component={Home}/> <ProtectedRoute exact path='/home' component={Home}/>
<ProtectedRoute exact path='/viewer' component={ARViewer}/> <ProtectedRoute exact path='/arviewer' component={ARViewer}/>
<ProtectedRoute exact path='/activity' component={Activity}/> <ProtectedRoute exact path='/activity' component={Activity}/>
<ProtectedRoute exact admin path='/workitem' component={WorkItem}/> <ProtectedRoute exact admin path='/workitem' component={WorkItem}/>
<ProtectedRoute exact admin path='/workitemlist' component={WorkItemList}/> <ProtectedRoute exact admin path='/workitemlist' component={WorkItemList}/>

View File

@@ -0,0 +1,53 @@
import React from 'react'
import PropTypes from 'prop-types'
import { View, Text } from 'react-native'
import { OptionStrip } from '.'
import autobind from 'autobind-decorator'
export class BoundOptionStrip extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string,
binder: PropTypes.object.isRequired,
options: PropTypes.arrayOf(PropTypes.shape({ value: PropTypes.string, text: PropTypes.string })).isRequired,
}
constructor(props) {
super(props)
this.state = props.binder.getFieldState(props.name)
}
@autobind
handleValueChange() {
const { binder, name } = this.props
const state = binder.getFieldState(name)
if (!state.readOnly && !state.disabled) {
this.setState(binder.updateFieldValue(name, !state.value))
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.binder !== this.props.binder) {
this.setState(nextProps.binder.getFieldState(nextProps.name))
}
}
render() {
const { name, label, options } = this.props
const { visible, disabled, value } = this.state
return (
<View style={{
display: visible ? 'flex' : 'none',
flexDirection: 'column',
}}>
<Text style={{ color: 'black', fontSize: 14, marginBottom: 5 }}>{label}</Text>
<OptionStrip
value={value}
options={options}
/>
</View>
)
}
}

View File

@@ -16,12 +16,12 @@ export class BoundSwitch extends React.Component {
} }
@autobind @autobind
handleValueChange() { handleValueChange(newValue) {
const { binder, name } = this.props const { binder, name } = this.props
const state = binder.getFieldState(name) const state = binder.getFieldState(name)
if (!state.readOnly && !state.disabled) { if (!state.readOnly && !state.disabled) {
this.setState(binder.updateFieldValue(name, !state.value)) this.setState(binder.updateFieldValue(name, newValue))
} }
} }

View File

@@ -13,6 +13,11 @@ export class Header extends Component {
rightButton: PropTypes.shape(headerButtonShape), rightButton: PropTypes.shape(headerButtonShape),
} }
static defaultProps = {
rightButton: { icon: 'none' },
leftButton: { icon: 'none' },
}
render() { render() {
const { title, leftButton, rightButton } = this.props const { title, leftButton, rightButton } = this.props

View File

@@ -1,5 +1,5 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { Image } from 'react-native' import { Image, View } from 'react-native'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
const images = { const images = {
@@ -11,6 +11,8 @@ const images = {
rightArrow: require('./images/right-arrow.png'), rightArrow: require('./images/right-arrow.png'),
search: require('./images/search.png'), search: require('./images/search.png'),
settings: require('./images/settings.png'), settings: require('./images/settings.png'),
add: require('./images/add.png'),
done: require('./images/done.png'),
} }
export class Icon extends Component { export class Icon extends Component {
@@ -27,11 +29,14 @@ export class Icon extends Component {
} }
render() { render() {
let { size, name, margin, style } = this.props let { name, margin, style } = this.props
let source = images[name] || images['hand'] let size = this.props.size - (margin * 2)
let source = images[name]
size -= margin * 2 if (!source) {
return <View style={{ width: size, height: size, margin }} />
return <Image style={[{ width: size, height: size, margin }, style]} source={source} resizeMode='stretch' /> } else {
return <Image style={[{ width: size, height: size, margin }, style]} source={source} resizeMode='stretch' />
}
} }
} }

View File

@@ -0,0 +1,70 @@
import React, { Component } from 'react'
import { View, Text, TouchableHighlight } from 'react-native'
import PropTypes from 'prop-types'
import autobind from 'autobind-decorator';
export class OptionStrip extends Component {
static propTypes = {
options: PropTypes.arrayOf(PropTypes.shape({ value: PropTypes.string, text: PropTypes.string })).isRequired,
value: PropTypes.string.isRequired,
onValueChanged: PropTypes.func,
}
constructor(props) {
super(props)
this.state = {
selectedOption: this.getSelectedOption(props.options, props.value)
}
}
@autobind
getSelectedOption(options, value) {
return options.find((option) => (value === option.value)) || options[0]
}
componentWillReceiveProps(newProps) {
if (newProps.options !== props.options || newProps.value !== props.value) {
this.setState({ selectedIndex: this.getSelectedIndex(newProps.options, newProps.value)})
}
}
@autobind
handlePress(option) {
const { onValueChanged } = this.props
this.setState({ selectedOption: option })
if (onValueChanged) {
onValueChanged(option.value)
}
}
render() {
const { style, options, value } = this.props
const { selectedOption } = this.state
return (
<View style={{ flexDirection: 'row' }}>
{options.map((option, index) => (
<TouchableHighlight
key={index}
underlayColor='#3BB0FD'
style={[
{ flexGrow: 1, flexBasis: 0, height: 40 },
option === selectedOption && { backgroundColor: '#3BB0FD' },
index === 0 && { borderTopLeftRadius: 6, borderBottomLeftRadius: 6 },
index === options.length - 1 && { borderTopRightRadius: 6, borderBottomRightRadius: 6 }
]}
onPress={() => this.handlePress(option)}>
<View style={[
{ flex: 1, justifyContent: 'center', borderTopWidth: 1, borderBottomWidth: 1, borderLeftWidth: 1, borderColor: 'black' },
index === 0 && { borderTopLeftRadius: 6, borderBottomLeftRadius: 6 },
index === options.length - 1 && { borderRightWidth: 1, borderTopRightRadius: 6, borderBottomRightRadius: 6 }
]}>
<Text style={{ alignSelf: 'center', color: 'black' }}>{option.text}</Text>
</View>
</TouchableHighlight>
))}
</View>
)
}
}

View File

@@ -0,0 +1,31 @@
import React from 'react'
import {
StyleSheet,
View,
Image,
TouchableOpacity
} from 'react-native'
import { Icon } from '.'
import autobind from 'autobind-decorator'
// const styles = StyleSheet.create()
export class PhotoButton extends React.Component {
render() {
return (
<TouchableOpacity>
<View style={{
flexDirection: 'row',
width: 100,
height: 60,
borderWidth: 2,
borderColor: 'gray',
borderRadius: 4,
justifyContent: 'center',
}}>
<Icon name='add' size={24} style={{ alignSelf: 'center' }} />
</View>
</TouchableOpacity>
)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

View File

@@ -1,5 +1,8 @@
export { BoundSwitch } from './BoundSwitch' export { BoundSwitch } from './BoundSwitch'
export { BoundInput } from './BoundInput' export { BoundInput } from './BoundInput'
export { BoundButton } from './BoundButton' export { BoundButton } from './BoundButton'
export { BoundOptionStrip } from './BoundOptionStrip'
export { Icon } from './Icon' export { Icon } from './Icon'
export { Header } from './Header' export { Header } from './Header'
export { PhotoButton } from './PhotoButton'
export { OptionStrip } from './OptionStrip'