Skip to content

Instantly share code, notes, and snippets.

@andigu
Last active September 27, 2017 21:16
Show Gist options
  • Save andigu/24b4c11c9b0bcf14d59bd887bf48e4a0 to your computer and use it in GitHub Desktop.
Save andigu/24b4c11c9b0bcf14d59bd887bf48e4a0 to your computer and use it in GitHub Desktop.
import React, {Component} from "react";
import PropTypes from "prop-types";
import {Card, CardItem, Container, Content, Grid, Header, Icon, Input, Item, Row, Text, View} from "native-base";
import {geocode, search} from "../../lib/Geo";
import {StyleSheet, TouchableOpacity} from "react-native";
import _ from "lodash";
export class AddressSearch extends Component {
static propTypes = {
onSelect: PropTypes.func,
initialValue: PropTypes.string,
onBack: PropTypes.func,
connected: PropTypes.bool,
location: PropTypes.object
};
locations: string[] = [];
changeText: (text: string) => void;
constructor(props) {
super(props);
this.state = {
searchText: props.initialValue,
locations: []
};
this.changeText = _.debounce((text) => {
search(text, null, this.props.location).then((data) => {
this.setState({locations: data});
});
}, 250);
this.changeText(props.initialValue);
}
render() {
return <Container>
<Header searchBar rounded>
<Item>
<TouchableOpacity onPress={() => {
this.props.onBack();
}}>
<Icon name="arrow-back"/>
</TouchableOpacity>
<Input placeholder="Search"
defaultValue={this.props.initialValue}
value={this.state.searchText}
autoFocus
disabled={!this.props.connected}
onChangeText={(text) => {
this.setState({
searchText: text
});
this.changeText(text);
}}/>
<TouchableOpacity onPress={() => {
this.setState({searchText: ""});
this.changeText("");
}}>
<Icon name="close"/>
</TouchableOpacity>
</Item>
</Header>
<Content keyboardShouldPersistTaps="always">
<Card style={styles.locationList}>
{this.props.connected ?
<View>
<TouchableOpacity onPress={() => {
const geo = this.props.location;
geocode(geo.coords).then((data: { results: { formatted_address: string }[] }) => {
this.props.onBack({
loc: geo.coords,
address: data.results[0].formatted_address
});
});
}}>
<CardItem>
<Icon name="locate"/>
<Text numberOfLines={1} style={styles.large}>
Your location
</Text>
</CardItem>
</TouchableOpacity>
{this.state.locations.map((data, i) =>
<TouchableOpacity onPress={() => {
this.props.onBack({
loc: {
latitude: data.geometry.location.lat,
longitude: data.geometry.location.lng
},
address: data.formatted_address
});
}} key={i}>
<CardItem>
<Icon name="pin"/>
{data.formatted_address.indexOf(data.name) === -1 ?
<Grid>
<Row>
<Text numberOfLines={1} style={styles.large}>{data.name}</Text>
</Row>
<Row>
<Text numberOfLines={1}>
{data.formatted_address}
</Text>
</Row>
</Grid> :
<Text numberOfLines={1} style={styles.large}>{data.formatted_address}</Text>}
</CardItem>
</TouchableOpacity>
)}</View> :
<CardItem>
<Icon name="cloud-outline"/>
<Text numberOfLines={1}>
You must be connected to wifi to search
</Text>
</CardItem>
}
</Card>
</Content>
</Container>;
}
}
const styles = StyleSheet.create({
locationList: {
paddingRight: 40
},
large: {fontSize: 18}
});
import React, {Component} from 'react';
import {Body, Button, Content, Form, Icon, Input, Item, Label, Switch, Text} from 'native-base';
import {Field, FormSection, formValueSelector, propTypes, reduxForm} from 'redux-form';
import {LayoutAnimation, Modal, Slider, StyleSheet, TouchableOpacity, View} from "react-native";
import {Map} from "../maps/Map";
import {Metrics, Theme} from "../../theme";
import {connect} from "react-redux";
import type {GeoData, GeoLocation} from "../../lib/Types";
import {geocode} from "../../lib/Geo";
import idx from "idx";
import {AddressSearch} from "./AddressSearch";
import {attachRender, createFields, extractInitialValue, formTypes, setFormValues} from "../../lib/ReduxForm";
import {fieldData as scheduleField, ScheduleForm} from "./ScheduleForm";
import {fieldData as preferenceField, PreferencesForm} from "./Preferences";
import {isDefined, objectMap} from "../../lib/Operators";
import PropTypes from "prop-types";
import autobind from "autobind-decorator";
import Color from "color";
import _ from "lodash";
const fieldData = createFields({
name: {label: "Name", required: true, initialValue: "", type: formTypes.string},
location: {initialValue: {latitude: 43.661331, longitude: -79.398625}, type: formTypes.location},
address: {label: "Address", type: formTypes.customOnFocus},
radius: {label: "Radius", initialValue: 100, type: formTypes.number},
hasSchedule: {label: "Schedule", initialValue: true, type: formTypes.switchType},
schedule: {initialValue: () => objectMap(scheduleField, extractInitialValue)},
preferences: {initialValue: objectMap(preferenceField, val => val.initialValue)}
});
export const alarmFormName = 'AlarmForm';
const selector = formValueSelector(alarmFormName);
@connect(state => ({value: selector(state, ...Object.values(fieldData).map(field => field.name))}), null)
@reduxForm({
form: alarmFormName,
initialValues: objectMap(fieldData, (value) => value.initialValue), // redundancy to preserve correct data types
validate: values => {
const errors = {};
Object.values(fieldData).filter(val => val.required).map(data => data.name).forEach(field => {
if (!values[field]) {
errors[field] = 'Required';
}
});
if (values.schedule && values.schedule.startTime > values.schedule.endTime) {
errors.schedule = {};
errors.schedule.startTime = errors.schedule.endTime = 'End time must be after start time';
}
return errors;
},
keepDirtyOnReinitialize: false,
enableReinitialize: true
})
export class AlarmForm extends Component {
static propTypes = {
...propTypes,
initialAlarm: PropTypes.object,
connected: PropTypes.bool,
location: PropTypes.object
};
fields = attachRender(fieldData, this.renderInput);
state = {
searchOpen: false
};
componentWillMount() {
if (this.props.initialAlarm) {
setFormValues(this.props.change, this.props.initialAlarm);
} else {
const loc: GeoData = this.props.location;
this.changeAddress(loc.coords);
setFormValues(this.props.change, objectMap(fieldData, extractInitialValue));
this.props.change(this.fields.location.name, {latitude: loc.coords.latitude, longitude: loc.coords.longitude});
}
}
changeAddress(location: GeoLocation) {
geocode(location).then((data) => {
this.props.change(this.fields.address.name, idx(data, (x) => x.results[0].formatted_address));
});
}
@autobind
renderInput({input, label, type, meta: {error, touched}}) {
const hasError = isDefined(error);
if (label === this.fields.hasSchedule.label) {
return <Item itemDivider>
<Text>Schedule</Text>
<Switch value={input.value}
style={styles.marginLeft}
onValueChange={(x) => {
input.onChange(x);
LayoutAnimation.configureNext(LayoutAnimation.Presets.linear);
}}
thumbTintColor={Theme.brandPrimary}
onTintColor={Color(Theme.brandPrimary).lighten(0.8).string()}
tintColor="lightgrey"/>
</Item>;
}
switch (type) {
case formTypes.location:
const name = this.props.value[this.fields.name.name];
return <View style={styles.mapContainer}>
<Map locations={[{
onDragEnd: input.onChange,
title: isDefined(name) ? name : "",
radius: this.props.value[this.fields.radius.name],
...input.value
}]}/>
</View>;
case formTypes.number:
return <Item style={styles.noInputContainer}>
<Label style={styles.sliderLabel}>{label}</Label>
<Text style={styles.sliderText}>100m</Text>
<Slider style={styles.slider}
thumbTintColor={Theme.brandPrimary}
maximumTrackTintColor={Theme.brandPrimary}
onSlidingComplete={input.onChange}
minimumValue={100}
maximumValue={1000}
value={_.isInteger(input.value) ? input.value : 100}/>
<Text style={styles.sliderText}>1000m</Text>
</Item>;
case formTypes.customOnFocus:
return <Item error={hasError} style={styles.inputContainer}>
<Label>{label}</Label>
<TouchableOpacity onPress={() => {
input.onFocus();
}} style={styles.noInputContainer}>
{input.value ? <Text numberOfLines={1}>{input.value}</Text> : <View style={styles.inputFiller}/>}
</TouchableOpacity>
</Item>;
default:
return <Item error={hasError && touched} style={styles.inputContainer}>
<Label>{label}</Label>
<Input {...input}/>
</Item>;
}
}
render() {
const {change, handleSubmit, value} = this.props;
return (
<Content keyboardShouldPersistTaps="handled" keyboardDismissMode="none">
<Modal
animationType={"slide"}
transparent={false}
onRequestClose={() => {
}}
visible={this.state.searchOpen}>
<AddressSearch
connected={this.props.connected}
location={this.props.location}
initialValue={this.props.value[this.fields.address.name]}
onBack={(data: ?{ loc: GeoLocation, address: string }) => {
if (data) {
change(this.fields.location.name, data.loc);
change(this.fields.address.name, data.address);
}
this.setState({searchOpen: false});
}}/>
</Modal>
<Form>
<Field {...this.fields.location}
onChange={(_, newValue) => {
this.changeAddress(newValue);
}}/>
<Field {...this.fields.name}/>
<Field {...this.fields.address}
onFocus={() => {
this.setState({
searchOpen: true
});
}}/>
<Field {...this.fields.radius}/>
<Field {...this.fields.hasSchedule}/>
<FormSection {...this.fields.schedule}>
{value.hasSchedule ? <ScheduleForm/> : <View/>}
</FormSection>
<FormSection {...this.fields.preferences}>
<PreferencesForm/>
</FormSection>
<Body>
<Button style={{margin: 10}} primary onPress={handleSubmit} rounded>
<Text>Save</Text>
<Icon name="checkmark" style={styles.marginLeft} small/>
</Button>
</Body>
</Form>
</Content>
);
}
}
const styles = StyleSheet.create({
mapContainer: {
height: Metrics.screenHeight * 0.5
},
slider: {
width: "62%"
},
noInputContainer: {
paddingVertical: 10,
marginRight: 15
},
sliderLabel: {
marginRight: 10
},
sliderText: {
fontSize: 13
},
inputContainer: {
marginHorizontal: 15
},
inputFiller: {
width: 200,
height: 25
},
marginLeft: {
marginLeft: 10
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment