Files
deighton-ar/mobile/src/Home/Home.js
2018-04-27 19:38:58 -07:00

525 lines
15 KiB
JavaScript

import React from "react"
import {
StyleSheet,
Text,
TextInput,
SectionList,
Image,
View,
TouchableOpacity,
TouchableHighlight,
PermissionsAndroid,
Platform,
} from "react-native"
import MapView, { Marker, Callout } from "react-native-maps"
import { Icon, Header } from "../ui"
import { MessageModal } from "../Modal"
import { api } from "../API"
import autobind from "autobind-decorator"
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"
const neverAskForLocationPermissionKeyName = "NeverAskForLocationPermission"
const neverAskForCameraKeyName = "NeverAskForCameraPermission"
export class Home extends React.Component {
constructor(props) {
super(props)
this.state = {
sections: [],
showWorkItems: true,
region: config.initialRegion,
positionInfo: null,
haveCameraPermission: false,
workItemDistance: -1,
}
this.watchId = null
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 })
navigator.geolocation.getCurrentPosition(this.handlePositionChange)
}
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.",
},
})
}
)
api
.listTeams()
.then((list) => {
this.teams = list.items
return api.listWorkItemActivities()
})
.then((list) => {
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,
region:
regionContainingPoints(
list.items.map((item) => item.coordinate),
0.02
) || this.state.region,
})
})
.catch((err) => {
this.setState({
messageModal: {
icon: "hand",
message: "Unable to get a list of work items, activities and teams",
detail: err.message,
},
})
})
}
componentWillUnmount() {
if (this.watchId) {
navigator.geolocation.clearWatch(this.watchId)
this.watchId = null
}
}
@autobind
handleMessageDismiss() {
this.setState({ messageModal: null })
}
@autobind
handlePositionChange(positionInfo) {
this.setState({ positionInfo })
}
@autobind
handleMarkerPress(e, sectionIndex) {
if (this.sectionList) {
this.sectionList.scrollToLocation({
sectionIndex,
itemIndex: 0,
viewOffset: 45,
})
}
if (this.state.positionInfo) {
const coords = this.state.positionInfo.coords
const workItem = this.state.sections[sectionIndex]
const { latitude, longitude } = workItem.coordinate
this.setState({
workItemDistance: geoDistance(
coords.latitude,
coords.longitude,
latitude,
longitude,
"K"
).toFixed(2),
})
}
}
@autobind
handleWorkItemsListPress() {
const { positionInfo } = this.state
if (positionInfo) {
const { coords } = positionInfo
this.props.history.push(
`/workItemList?latLng=${coords.latitude},${coords.longitude}`
)
} else {
this.props.history.push("/workItemList")
}
}
@autobind
handleItemSelect(activity) {
this.props.history.push(`/activity?id=${activity._id}`)
}
@autobind
handleSectionSelect(workItem) {
const { latitude, longitude } = workItem.coordinate
const region = {
latitude,
longitude,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
}
this.setState({ region })
}
@autobind
handleLogoutPress() {
this.props.history.replace("/logout")
}
@autobind
handleGlassesPress() {
const { sections: workItems } = this.state
const {
latitude: latitude1,
longitude: longitude1,
} = this.state.positionInfo.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
: ""
}`
)
}
@autobind
handleMyLocationPress() {
if (this.state.positionInfo) {
const coords = this.state.positionInfo.coords
this.map.animateToCoordinate({
latitude: coords.latitude,
longitude: coords.longitude,
})
}
}
@autobind
handleToggleWorkItemsList() {
this.setState({ showWorkItems: !this.state.showWorkItems })
}
render() {
const {
sections,
showWorkItems,
region,
positionInfo,
messageModal,
haveCameraPermission,
haveLocationPermission,
workItemDistance,
} = this.state
if (!this.watchId && haveLocationPermission) {
this.watchId = navigator.geolocation.watchPosition(
this.handlePositionChange,
null,
{ distanceFilter: 10 }
)
}
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={
!(
positionInfo &&
positionInfo.coords.accuracy <= config.minGPSAccuracy
) ||
!haveCameraPermission ||
!haveLocationPermission
}
/>
<MapView
ref={(ref) => {
this.map = ref
}}
style={[
{
width: "100%",
},
showWorkItems && { height: "40%" },
!showWorkItems && { flexGrow: 1 },
]}
showsUserLocation
showsBuildings={false}
showsTraffic={false}
showsIndoors={false}
zoomControlEnabled={false}
showsMyLocationButton={false}
region={region}>
{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>
<View>
<Text>
{pad(workItem.ticketNumber, 4) +
": " +
workItemTypeText[workItem.workItemType] +
" (" +
(workItemDistance > 0
? workItemDistance.toString()
: "?") +
" km)"}
</Text>
<Text>{dotify(workItem.address)}</Text>
</View>
</Callout>
</Marker>
))}
</MapView>
<View
style={{
display: showWorkItems ? "flex" : "none",
flexDirection: "column",
flexGrow: 1,
flexBasis: 0,
width: "100%",
}}>
{/*
// TODO: Search feature
<View
style={{
flexDirection: "row",
alignItems: "center",
width: "100%",
height: 40,
backgroundColor: "white",
}}>
<Icon
name="search"
size={16}
style={{ marginLeft: 10, marginRight: 5, tintColor: "gray" }}
/>
<TextInput
style={{ flexGrow: 1, flexBasis: 0, height: "100%" }}
underlineColorAndroid="white"
placeholder="Search"
/>
<Icon
style={{ marginLeft: 5, marginRight: 10 }}
name="cancel"
size={16}
/>
</View> */}
<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())}
</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
disabled={!positionInfo}
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>
<MessageModal
open={!!messageModal}
icon={messageModal ? messageModal.icon : ""}
message={messageModal ? messageModal.message : ""}
detail={messageModal ? messageModal.detail : ""}
onDismiss={messageModal && this.handleMessageDismiss}
/>
{Platform.OS === "ios" && <KeyboardSpacer />}
</View>
)
}
}