Added Header, Icon and MessageModal. Refactor screens into directories.

This commit is contained in:
John Lyon-Smith
2018-04-02 13:22:33 -07:00
parent aa622012cd
commit 410d2fde4f
56 changed files with 556 additions and 461 deletions

View File

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

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1 @@
export { ARViewer } from './ARViewer'

View File

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

View File

@@ -0,0 +1 @@
export { Activity } from './Activity'

View 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
)} />
)
}
}

View File

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

View 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' />
}
}

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

4
mobile/src/Auth/index.js Normal file
View 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
View 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>
);
}
}

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

1
mobile/src/Home/index.js Normal file
View File

@@ -0,0 +1 @@
export { Home } from './Home'

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

View File

@@ -0,0 +1 @@
export { MessageModal } from './MessageModal'

View File

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

View 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>
);
}
}

View File

@@ -0,0 +1,3 @@
export { WorkItem } from './WorkItem'
export { WorkItemList } from './WorkItemList'

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -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
View 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
View 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' />
}
}

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,3 +1,5 @@
export { BoundSwitch } from './BoundSwitch'
export { BoundInput } from './BoundInput'
export { BoundButton } from './BoundButton'
export { Icon } from './Icon'
export { Header } from './Header'