Files
deighton-ar/mobile/src/Home/Home.js
2018-06-28 08:32:00 -07:00

524 lines
15 KiB
JavaScript

import React from "react"
import {
Text,
SectionList,
View,
TouchableOpacity,
TouchableHighlight,
PermissionsAndroid,
Platform,
} from "react-native"
import MapView, { Marker, Callout } from "react-native-maps"
import { Icon, Header, Geolocation } from "../ui"
import { MessageModal, WaitModal } from "../Modal"
import { api } from "../API"
import { reactAutoBind } from "auto-bind2"
import { ifIphoneX } from "react-native-iphone-x-helper"
import {
geoDistance,
workItemTypeText,
pad,
regionContainingPoints,
dotify,
} from "../util"
import { ensurePermissions } from "../App"
import { versionInfo } from "../version"
import { config } from "../config"
import KeyboardSpacer from "react-native-keyboard-spacer"
import hardhatPinImage from "./images/hardhat-pin.png"
import clipboardPinImage from "./images/clipboard-pin.png"
import questionPinImage from "./images/question-pin.png"
import moment from "moment"
export class Home extends React.Component {
constructor(props) {
super(props)
reactAutoBind(this)
this.state = {
waitModal: null,
messageModal: null,
sections: [],
showWorkItems: true,
haveCameraPermission: false,
haveLocationPermission: false,
}
this.currentPosition = null
this.isMapReady = false
this.goToRegionWhenReady = false
this.region = null
if (Platform.OS !== "ios") {
ensurePermissions(
[
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
PermissionsAndroid.PERMISSIONS.CAMERA,
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
],
{
title: versionInfo.title,
message:
"This app needs these permissions so that you can " +
"find, access and photograph geo located items.",
},
(results) => {
if (
results[PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION] ===
PermissionsAndroid.RESULTS.GRANTED
) {
this.setState({ haveLocationPermission: true })
}
if (
results[PermissionsAndroid.PERMISSIONS.CAMERA] ===
PermissionsAndroid.RESULTS.GRANTED
) {
this.setState({ haveCameraPermission: true })
}
},
() => {
this.setState({
messageModal: {
icon: "hand",
message:
"You have denied the app access to phone features it needs to function. " +
"Some parts of the app are disabled.",
detail:
"To enable these features in future " +
"please go to Settings.",
},
})
}
)
} else {
navigator.geolocation.requestAuthorization()
this.state.haveLocationPermission = true
this.state.haveCameraPermission = true
}
}
componentDidMount() {
this.loadWorkItems()
}
loadWorkItems() {
this.setState({ waitModal: { message: "Loading Work Items..." } })
api
.listTeams()
.then((list) => {
this.teams = list.items
return api.listWorkItemActivities()
})
.then((list) => {
this.setState({ waitModal: null })
list.items.forEach((item) => {
item.data.forEach((datum) => {
const team = this.teams.find((team) => team._id === datum.team)
datum.teamName = team ? team.name : "???"
})
})
this.setState({
sections: list.items,
})
this.region =
regionContainingPoints(
list.items.map((item) => item.coordinate),
config.homeRegionInset
) || config.initialRegion
if (this.isMapReady) {
this.mapView.animateToRegion(this.region)
} else {
this.goToRegionWhenReady = true
}
})
.catch((err) => {
this.setState({
waitModal: null,
messageModal: {
icon: "hand",
message: "Unable to get a list of work items, activities and teams",
detail: err.message,
},
})
})
}
handleMessageDismiss() {
this.setState({ messageModal: null })
}
handleOnMapReady() {
if (this.goToRegionWhenReady) {
this.mapView.animateToRegion(this.region)
this.goToRegionWhenReady = false
}
this.isMapReady = true
}
handleMarkerPress(e, sectionIndex) {
if (this.sectionList) {
this.sectionList.scrollToLocation({
sectionIndex,
itemIndex: 0,
viewOffset: 45,
})
}
}
handleWorkItemsListPress() {
if (this.currentPosition) {
const { coords } = this.currentPosition
this.props.history.push(
`/workItemList?latLng=${coords.latitude},${coords.longitude}`
)
}
}
handleItemSelect(activity) {
this.props.history.push(`/activity?id=${activity._id}`)
}
handleSectionSelect(workItem) {
if (!this.isMapReady) {
return
}
const { latitude, longitude } = workItem.coordinate
this.region = {
latitude,
longitude,
latitudeDelta: config.homeRegionDelta,
longitudeDelta: config.homeRegionDelta,
}
this.mapView.animateToRegion(this.region)
}
handleLogoutPress() {
this.props.history.replace("/logout")
}
handleGlassesPress() {
const { sections: workItems } = this.state
if (this.currentPosition) {
const {
latitude: latitude1,
longitude: longitude1,
} = this.currentPosition.coords
let closestWorkItem = config.alwaysShowWorkItemInAR ? workItems[0] : null
let shortestDistance = config.minDistanceToItem
workItems.forEach((workItem) => {
const {
latitude: latitude2,
longitude: longitude2,
} = workItem.coordinate
const distance =
geoDistance(latitude1, longitude1, latitude2, longitude2, "K") * 1000
if (distance <= shortestDistance) {
closestWorkItem = workItem
shortestDistance = distance
}
})
this.props.history.push(
`/arviewer${
closestWorkItem
? "?workItemId=" +
closestWorkItem._id +
"&workItemType=" +
closestWorkItem.workItemType
: ""
}`
)
}
}
handleMyLocationPress() {
if (this.currentPosition && this.isMapReady) {
const { coords } = this.currentPosition
this.mapView.animateToCoordinate({
latitude: coords.latitude,
longitude: coords.longitude,
})
}
}
handleToggleWorkItemsList() {
const newShowWorkItems = !this.state.showWorkItems
this.setState({ showWorkItems: newShowWorkItems })
if (newShowWorkItems) {
this.loadWorkItems()
}
}
handleCalloutPress(workItem) {
if (api.loggedInUser.administrator) {
this.props.history.push(
`/arviewer?workItemId=${workItem._id}&workItemType=${
workItem.workItemType
}`
)
}
}
handlePositionUpdate(position) {
this.currentPosition = position
}
getCoordinateDistance(coordinate) {
if (this.currentPosition) {
const { coords } = this.currentPosition
const { latitude, longitude } = coordinate
return (
geoDistance(
coords.latitude,
coords.longitude,
latitude,
longitude,
"K"
).toFixed(2) + " km"
)
} else {
return "?"
}
}
render() {
const {
sections,
showWorkItems,
messageModal,
waitModal,
haveCameraPermission,
haveLocationPermission,
} = this.state
return (
<View
style={{
flexDirection: "column",
height: "100%",
backgroundColor: "#FFFFFF",
}}>
<Header
title="Work Item Map"
leftButton={{ icon: "logout", onPress: this.handleLogoutPress }}
rightButton={{ icon: "glasses", onPress: this.handleGlassesPress }}
disabled={!haveCameraPermission || !haveLocationPermission}
/>
<MapView
ref={(ref) => {
this.mapView = ref
}}
style={[
{
width: "100%",
},
showWorkItems && { height: "40%" },
!showWorkItems && { flexGrow: 1 },
]}
showsUserLocation
showsBuildings={false}
showsTraffic={false}
showsIndoors={false}
zoomControlEnabled={false}
showsMyLocationButton={false}
initialRegion={config.initialRegion}
onMapReady={this.handleOnMapReady}>
{sections.map((workItem, index) => (
<Marker
key={index}
coordinate={workItem.coordinate}
anchor={{ x: 0.5, y: 1.0 }}
image={
workItem.workItemType === "inspection"
? clipboardPinImage
: workItem.workItemType === "complaint"
? questionPinImage
: hardhatPinImage
}
onPress={(e) => this.handleMarkerPress(e, index)}>
<Callout onPress={() => this.handleCalloutPress(workItem)}>
<View>
<Text>
{pad(workItem.ticketNumber, 4) +
": " +
workItemTypeText[workItem.workItemType] +
" (" +
this.getCoordinateDistance(workItem.coordinate) +
")"}
</Text>
<Text>{dotify(workItem.address)}</Text>
</View>
</Callout>
</Marker>
))}
</MapView>
<View
style={{
display: showWorkItems ? "flex" : "none",
flexDirection: "column",
flexGrow: 1,
flexBasis: 0,
width: "100%",
}}>
<SectionList
ref={(ref) => (this.sectionList = ref)}
style={{ width: "100%", flexGrow: 1 }}
sections={sections}
stickySectionHeadersEnabled={true}
renderSectionHeader={({ section: workItem }) => (
<TouchableHighlight
style={{
paddingLeft: 8,
height: 45,
backgroundColor: "#F4F4F4",
}}
underlayColor="#EEEEEE"
onPress={() => this.handleSectionSelect(workItem)}>
<View
key={workItem._id}
style={{
height: "100%",
width: "100%",
flexDirection: "row",
justifyContent: "flex-start",
alignItems: "center",
}}>
<Icon
name={
workItem.workItemType === "order"
? "hardhat"
: workItem.workItemType === "complaint"
? "question"
: "clipboard"
}
size={16}
style={{ marginRight: 10 }}
/>
<Text style={{ fontSize: 16 }}>
{workItemTypeText[workItem.workItemType].toUpperCase()}{" "}
{pad(workItem.ticketNumber, 4)}
</Text>
</View>
</TouchableHighlight>
)}
keyExtractor={(item) => item._id}
renderItem={({ item: activity, section }) => {
return (
<TouchableHighlight
style={{
height: 50,
paddingLeft: 10,
paddingRight: 20,
backgroundColor: "white",
}}
underlayColor="#EEEEEE"
onPress={() => this.handleItemSelect(activity)}>
<View
style={{
height: "100%",
width: "100%",
flexDirection: "row",
}}>
<Text
style={{
fontSize: 9,
width: 45,
alignSelf: "center",
}}>
{activity.status.toUpperCase()}
</Text>
<View
style={{
flexGrow: 1,
flexBasis: 0,
flexDirection: "column",
marginLeft: 10,
}}>
<Text style={{ fontSize: 20, fontWeight: "bold" }}>
{activity.resolution}
</Text>
<Text style={{ fontSize: 14, color: "gray" }}>
{activity.teamName +
" | " +
dotify(moment(activity.createdAt).format(), -15)}
</Text>
</View>
<Icon
name="rightArrow"
size={16}
style={{ alignSelf: "center" }}
/>
</View>
</TouchableHighlight>
)
}}
/>
</View>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
height: 55,
backgroundColor: "#F4F4F4",
...ifIphoneX({ marginBottom: 22 }, {}),
}}>
<TouchableOpacity onPress={this.handleMyLocationPress}>
<Icon
name="center"
size={24}
style={{ marginLeft: 15, tintColor: "gray" }}
/>
</TouchableOpacity>
<TouchableOpacity onPress={this.handleToggleWorkItemsList}>
<Text style={{ color: "gray", fontSize: 20 }}>
{showWorkItems ? "Hide List" : "Show List"}
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.handleWorkItemsListPress}>
<Icon
name="settings"
size={24}
style={{
display: api.loggedInUser.administrator ? "flex" : "none",
marginRight: 15,
tintColor: "gray",
}}
/>
</TouchableOpacity>
</View>
{haveLocationPermission && (
<Geolocation onUpdate={this.handlePositionUpdate} />
)}
<WaitModal
open={!!waitModal}
message={waitModal ? waitModal.message : ""}
/>
<MessageModal
open={!!messageModal}
icon={messageModal ? messageModal.icon : ""}
message={messageModal ? messageModal.message : ""}
detail={messageModal ? messageModal.detail : ""}
onDismiss={messageModal && this.handleMessageDismiss}
/>
{Platform.OS === "ios" && <KeyboardSpacer />}
</View>
)
}
}