Skip to content
This repository has been archived by the owner on Apr 14, 2023. It is now read-only.

Commit

Permalink
Add form UI (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
pandananta authored Aug 22, 2018
1 parent 787259b commit 23549a8
Show file tree
Hide file tree
Showing 19 changed files with 8,060 additions and 394 deletions.
Binary file modified .DS_Store
Binary file not shown.
Binary file added expo_project/.DS_Store
Binary file not shown.
Binary file removed expo_project/assets/fonts/SpaceMono-Regular.ttf
Binary file not shown.
Binary file added expo_project/assets/fonts/monaco.ttf
Binary file not shown.
35 changes: 35 additions & 0 deletions expo_project/components/ColoredButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import PropTypes from "prop-types";
import React from "react";

import { StyleSheet, Text, TouchableOpacity } from "react-native";

class ColoredButton extends React.Component {
render() {
const { backgroundColor, color, onPress, label } = this.props;
return (
<TouchableOpacity
style={[styles.button, { backgroundColor }]}
onPress={onPress}
>
<Text style={[styles.text, { color }]}>{label}</Text>
</TouchableOpacity>
);
}
}

const styles = StyleSheet.create({
button: {
backgroundColor: "#5B93D9",
padding: 12,
marginVertical: 20,
justifyContent: "center",
alignItems: "center"
},
text: { fontWeight: "bold" }
});

ColoredButton.propTypes = {
color: PropTypes.string.isRequired
};

export default ColoredButton;
82 changes: 82 additions & 0 deletions expo_project/components/MapWithMarkers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import PropTypes from "prop-types";
import React from "react";
import { StyleSheet } from "react-native";
import { MapView } from "expo";
import PersonIcon from "./PersonIcon";

class MapWithMarkers extends React.Component {
constructor(props) {
super(props);
}

render() {
const {
markers,
activeMarkerId,
onMarkerDragEnd,
onMarkerPress,
onMapPress,
onMapLongPress
} = this.props;
return (
<MapView
style={styles.mapStyle}
onPress={onMapPress}
onLongPress={onMapLongPress}
showsUserLocation
followsUserLocation
zoomEnabled
rotateEnabled
pitchEnabled={false}
>
{markers.map(marker => {
const selected = marker.id === activeMarkerId;
// Update the key when selected or delected, so the marker re renders and centers itself based on the new child size
const key = marker.id + (selected ? "-selected" : "");
return (
<MapView.Marker
coordinate={marker.coordinate}
key={key}
identifier={marker.id}
stopPropagation
draggable
onDragEnd={onMarkerDragEnd}
onPress={() => onMarkerPress(marker.id)}
anchor={{ x: 0, y: 0 }}
calloutAnchor={{ x: 0, y: 0 }}
>
<PersonIcon
backgroundColor={marker.color}
size={selected ? 24 : 16}
shadow
/>
</MapView.Marker>
);
})}
</MapView>
);
}
}

const styles = StyleSheet.create({
mapStyle: { flex: 1 }
});

MapWithMarkers.propTypes = {
markers: PropTypes.arrayOf(
PropTypes.shape({
coordinate: PropTypes.any,
color: PropTypes.string,
title: PropTypes.string,
dateLabel: PropTypes.string,
id: PropTypes.string
})
).isRequired,
activeMarkerId: PropTypes.string,
onMarkerDragEnd: PropTypes.func.isRequired,
onMarkerPress: PropTypes.func.isRequired,
onMapPress: PropTypes.func.isRequired,
onMapLongPress: PropTypes.func.isRequired
};

export default MapWithMarkers;
132 changes: 132 additions & 0 deletions expo_project/components/MarkerCarousel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import PropTypes from "prop-types";
import React from "react";

import PersonIcon from "./PersonIcon";

import { FlatList, StyleSheet, TouchableOpacity } from "react-native";

import * as _ from "lodash";

const CAROUSEL_ICON_SIZE = 50;
const CAROUSEL_ITEM_PADDING = 12;
const CAROUSEL_ITEM_LENGTH = CAROUSEL_ICON_SIZE + 2 * CAROUSEL_ITEM_PADDING;

const VIEWABILITY_CONFIG = { itemVisiblePercentThreshold: 100 };

class MarkerCarousel extends React.Component {
constructor(props) {
super(props);

this.state = {
viewableIndices: []
};
this.onViewableItemsChanged = this.onViewableItemsChanged.bind(this);
}

onViewableItemsChanged({ viewableItems }) {
const viewableIndices = _.map(viewableItems, "index");
this.setState({ viewableIndices });
}

componentDidUpdate(prevProps, prevState) {
// If user selects a marker, and it's not visible, scroll to it
// Note that Adding / removing markers trigger their own animation (see: onContentSizeChange)
// Therefore we stop if props.markers has changed
if (
this.props.markers === prevProps.markers &&
this.props.activeMarkerId !== prevProps.activeMarkerId
) {
const index = _.findIndex(this.props.markers, {
id: this.props.activeMarkerId
});
if (index > -1) {
// Only scroll if the new selection isn't already visible
if (!_.includes(this.state.viewableIndices, index)) {
this.flatList.scrollToIndex({
index,
viewPosition: 0.5,
animated: true
});
}
}
}
}

render() {
const { activeMarkerId, markers, onMarkerPress } = this.props;
return (
<FlatList
style={styles.container}
data={markers}
keyExtractor={item => item.id}
extraData={activeMarkerId}
horizontal
removeClippedSubviews
showsHorizontalScrollIndicator={false}
ref={ref => (this.flatList = ref)}
onContentSizeChange={(contentWidth, contentHeight) => {
if (markers.length > 1) {
// This is janky sometimes when there's only one item for some reason ...
this.flatList.scrollToEnd({ animated: true });
}
}}
getItemLayout={(data, index) => ({
length: CAROUSEL_ITEM_LENGTH,
offset: CAROUSEL_ITEM_LENGTH * index,
index
})}
onViewableItemsChanged={this.onViewableItemsChanged}
viewabilityConfig={VIEWABILITY_CONFIG}
renderItem={({ item, index }) => {
const selected = item.id === activeMarkerId;
return (
<TouchableOpacity
Index={index}
style={[
styles.cell,
selected && { borderBottomColor: item.color }
]}
onPress={() => onMarkerPress(item.id)}
>
<PersonIcon
backgroundColor={item.color}
size={CAROUSEL_ICON_SIZE}
/>
</TouchableOpacity>
);
}}
/>
);
}
}

const styles = StyleSheet.create({
container: {
borderBottomColor: "rgba(0, 0, 0, 0.12)",
borderBottomWidth: 1
},
cell: {
padding: CAROUSEL_ITEM_PADDING,
// there's a border on selected cells, so put an inivisble border on all cells to keep cell height consistent
borderBottomColor: "transparent",
borderBottomWidth: 4,
justifyContent: "center",
alignItems: "center"
}
});

MarkerCarousel.propTypes = {
markers: PropTypes.arrayOf(
PropTypes.shape({
coordinate: PropTypes.any,
color: PropTypes.string,
title: PropTypes.string,
dateLabel: PropTypes.string,
id: PropTypes.string
})
).isRequired,
activeMarkerId: PropTypes.string,
onMarkerPress: PropTypes.func.isRequired
};

export default MarkerCarousel;
56 changes: 56 additions & 0 deletions expo_project/components/PersonIcon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Icon } from "expo";
import PropTypes from "prop-types";
import React from "react";

import { Platform, View, StyleSheet } from "react-native";

class PersonIcon extends React.Component {
render() {
const { size, backgroundColor, shadow } = this.props;
return (
<View
style={[
styles.container,
shadow && styles.shadow,
{
backgroundColor,
height: size,
width: size,
borderRadius: size / 2
}
]}
>
<Icon.Ionicons name="md-person" size={size * 0.5} color="white" />
</View>
);
}
}

const styles = StyleSheet.create({
container: {
justifyContent: "center",
alignItems: "center"
},
shadow: {
...Platform.select({
ios: {
shadowColor: "black",
shadowOffset: { height: 3 },
shadowOpacity: 0.5,
shadowRadius: 3
},
android: {
// TODO: verify
elevation: 20
}
})
}
});

PersonIcon.propTypes = {
size: PropTypes.number.isRequired,
backgroundColor: PropTypes.string.isRequired,
shadow: PropTypes.bool
};

export default PersonIcon;
Loading

0 comments on commit 23549a8

Please sign in to comment.