summaryrefslogtreecommitdiff
path: root/frontend/app/components
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/app/components')
-rw-r--r--frontend/app/components/avatar/index.js66
-rw-r--r--frontend/app/components/avatar/types.js49
-rw-r--r--frontend/app/components/cardInput.js67
-rw-r--r--frontend/app/components/charts/areaChart.js120
-rw-r--r--frontend/app/components/charts/areaSmoothedChart.js118
-rw-r--r--frontend/app/components/charts/doughnutChart.js125
-rw-r--r--frontend/app/components/charts/index.js4
-rw-r--r--frontend/app/components/charts/progessChart.js96
-rw-r--r--frontend/app/components/ellipsis.js30
-rw-r--r--frontend/app/components/findFriends.js64
-rw-r--r--frontend/app/components/gallery.js78
-rw-r--r--frontend/app/components/gradientButton/index.js45
-rw-r--r--frontend/app/components/gradientButton/types.js48
-rw-r--r--frontend/app/components/index.js14
-rw-r--r--frontend/app/components/navBar.js145
-rw-r--r--frontend/app/components/paginationIndicator.js49
-rw-r--r--frontend/app/components/passwordTextInput.js47
-rw-r--r--frontend/app/components/picker/datePicker.js103
-rw-r--r--frontend/app/components/picker/index.js1
-rw-r--r--frontend/app/components/progressBar.js63
-rw-r--r--frontend/app/components/socialBar/index.js61
-rw-r--r--frontend/app/components/socialBar/types.js43
-rw-r--r--frontend/app/components/socialSetting.js66
-rw-r--r--frontend/app/components/switch/index.android.js171
-rw-r--r--frontend/app/components/switch/index.ios.js36
-rw-r--r--frontend/app/components/switch/types.js35
-rw-r--r--frontend/app/components/walkthrough.js63
27 files changed, 1807 insertions, 0 deletions
diff --git a/frontend/app/components/avatar/index.js b/frontend/app/components/avatar/index.js
new file mode 100644
index 0000000..b01800a
--- /dev/null
+++ b/frontend/app/components/avatar/index.js
@@ -0,0 +1,66 @@
+import React from 'react';
+import {
+ Image,
+ View,
+} from 'react-native';
+import {
+ RkComponent,
+ RkText,
+ RkTheme,
+} from 'react-native-ui-kitten';
+import { FontAwesome } from '../../assets/icons';
+
+export class Avatar extends RkComponent {
+ componentName = 'Avatar';
+ typeMapping = {
+ container: {},
+ image: {},
+ badge: {},
+ badgeText: {},
+ };
+
+ getBadgeStyle = (badgeProps) => {
+ switch (badgeProps) {
+ case 'like':
+ return {
+ symbol: FontAwesome.heart,
+ backgroundColor: RkTheme.current.colors.badge.likeBackground,
+ color: RkTheme.current.colors.badge.likeForeground,
+ };
+ case 'follow':
+ return {
+ symbol: FontAwesome.plus,
+ backgroundColor: RkTheme.current.colors.badge.plusBackground,
+ color: RkTheme.current.colors.badge.plusForeground,
+ };
+ default: return {};
+ }
+ };
+
+ renderImg = (styles) => (
+ <View>
+ <Image style={styles.image} source={this.props.img} />
+ { this.props.badge && this.renderBadge(styles.badge)}
+ </View>
+ );
+
+ renderBadge = (style, textStyle) => {
+ const badgeStyle = this.getBadgeStyle(this.props.badge);
+ return (
+ <View style={[style, { backgroundColor: badgeStyle.backgroundColor }]}>
+ <RkText rkType='awesome' style={[textStyle, { color: badgeStyle.color }]}>
+ {badgeStyle.symbol}
+ </RkText>
+ </View>
+ );
+ };
+
+ render() {
+ const { container, ...other } = this.defineStyles();
+ return (
+ <View style={[container, this.props.style]}>
+ {this.renderImg(other)}
+ </View>
+ );
+ }
+}
diff --git a/frontend/app/components/avatar/types.js b/frontend/app/components/avatar/types.js
new file mode 100644
index 0000000..2d732d8
--- /dev/null
+++ b/frontend/app/components/avatar/types.js
@@ -0,0 +1,49 @@
+export const AvatarTypes = () => ({
+ _base: {
+ container: {
+ alignItems: 'center',
+ flexDirection: 'row',
+ },
+ image: {
+ width: 40,
+ height: 40,
+ },
+ badge: {
+ width: 15,
+ height: 15,
+ borderRadius: 7.5,
+ alignItems: 'center',
+ justifyContent: 'center',
+ position: 'absolute',
+ bottom: -2,
+ right: -2,
+ },
+ badgeText: {
+ backgroundColor: 'transparent',
+ fontSize: 9,
+ },
+ },
+ big: {
+ image: {
+ width: 110,
+ height: 110,
+ borderRadius: 55,
+ marginBottom: 19,
+ },
+ container: {
+ flexDirection: 'column',
+ },
+ },
+ small: {
+ image: {
+ width: 32,
+ height: 32,
+ borderRadius: 16,
+ },
+ },
+ circle: {
+ image: {
+ borderRadius: 20,
+ },
+ },
+});
diff --git a/frontend/app/components/cardInput.js b/frontend/app/components/cardInput.js
new file mode 100644
index 0000000..b286d56
--- /dev/null
+++ b/frontend/app/components/cardInput.js
@@ -0,0 +1,67 @@
+import React from 'react';
+import {
+ RkButton,
+ RkTextInput,
+ RkText,
+ RkStyleSheet,
+} from 'react-native-ui-kitten';
+import { FontAwesome } from '../assets/icons';
+
+export class CardInput extends React.Component {
+ state = {
+ hidden: true,
+ cardNumber: '',
+ };
+
+ formatCreditNumber = (number, isHidden) => (
+ isHidden
+ ? number.replace(/\D/g, '')
+ : number.replace(/[^\dA-Z]/g, '').replace(/(.{4})/g, '$1 ').trim()
+ );
+
+ onInputLabelPressed = () => {
+ this.setState({
+ hidden: !this.state.hidden,
+ cardNumber: this.formatCreditNumber(this.state.cardNumber, !this.state.hidden),
+ });
+ };
+
+ onInputChanged = (text) => {
+ this.setState({
+ cardNumber: this.formatCreditNumber(text, this.state.hidden),
+ });
+ };
+
+ renderInputLabel = () => (
+ <RkButton
+ style={styles.button}
+ rkType='clear'
+ onPress={this.onInputLabelPressed}>
+ <RkText style={styles.icon} rkType='awesome secondaryColor'>{FontAwesome.slashEye}</RkText>
+ </RkButton>
+ );
+
+ render = () => (
+ <RkTextInput
+ autoCapitalize='none'
+ rkType='bordered rounded iconRight'
+ autoCorrect={false}
+ label={this.renderInputLabel()}
+ secureTextEntry={this.state.hidden}
+ onChangeText={this.onInputChanged}
+ value={this.state.cardNumber}
+ keyboardType='numeric'
+ maxLength={19}
+ {...this.props}
+ />
+ );
+}
+
+let styles = RkStyleSheet.create({
+ icon: {
+ fontSize: 24,
+ },
+ button: {
+ right: 17,
+ },
+});
diff --git a/frontend/app/components/charts/areaChart.js b/frontend/app/components/charts/areaChart.js
new file mode 100644
index 0000000..be40960
--- /dev/null
+++ b/frontend/app/components/charts/areaChart.js
@@ -0,0 +1,120 @@
+import React from 'react';
+import {
+ View,
+ Dimensions,
+} from 'react-native';
+import {
+ RkComponent,
+ RkTheme,
+ RkText,
+} from 'react-native-ui-kitten';
+
+import {
+ VictoryChart,
+ VictoryAxis,
+ VictoryArea,
+ VictoryScatter,
+ VictoryGroup,
+} from 'victory-native';
+
+
+export class AreaChart extends RkComponent {
+ state = {
+ data: [
+ { x: 1, y: 1 },
+ { x: 2, y: 2 },
+ { x: 3, y: 1 },
+ { x: 4, y: 2 },
+ { x: 5, y: 3 },
+ { x: 6, y: 3 },
+ { x: 7, y: 4 },
+ { x: 8, y: 3 },
+ { x: 9, y: 2 },
+ { x: 10, y: 4 },
+ ],
+ };
+
+ componentWillMount() {
+ this.size = Dimensions.get('window').width;
+ }
+
+ componentDidMount() {
+ this.setStateInterval = setInterval(() => {
+ let positive = Math.random() > 0.5;
+ let newValue = this.state.data[this.state.data.length - 1].y;
+ if (newValue > 3) {
+ positive = false;
+ } else if (newValue < 2) {
+ positive = true;
+ }
+ newValue = positive ? newValue + 1 : newValue - 1;
+ const newData = this.state.data.map((d, i) => ({
+ x: d.x,
+ y: i === this.state.data.length - 1 ? newValue : this.state.data[i + 1].y,
+ }));
+ this.setState({
+ data: newData,
+ });
+ }, 3000);
+ }
+
+ componentWillUnmount() {
+ clearInterval(this.setStateInterval);
+ }
+
+ render = () => (
+ <View>
+ <RkText rkType='header4'>REAL TIME VISITORS</RkText>
+ <VictoryChart
+ padding={{
+ top: 20, left: 40, right: 5, bottom: 5,
+ }}
+ width={this.size - 60}>
+ <VictoryAxis
+ tickValues={[]}
+ style={{
+ axis: { stroke: 'transparent' },
+ }}
+ />
+ <VictoryAxis
+ dependentAxis
+ tickValues={['50', '100', '150', '200']}
+ style={{
+ axis: { stroke: 'transparent' },
+ grid: { stroke: RkTheme.current.colors.disabled, strokeWidth: 0.5 },
+ tickLabels: {
+ fontSize: 14,
+ stroke: RkTheme.current.colors.text.secondary,
+ fill: RkTheme.current.colors.text.secondary,
+ fontFamily: RkTheme.current.fonts.family.regular,
+ strokeWidth: 0.5,
+ },
+ }}
+ />
+ <VictoryGroup data={this.state.data}>
+ <VictoryArea
+ style={{
+ data: {
+ fill: RkTheme.current.colors.charts.area.fill,
+ fillOpacity: 0.5,
+ stroke: RkTheme.current.colors.charts.area.stroke,
+ strokeOpacity: 0.8,
+ strokeWidth: 1.5,
+ },
+ }}
+ />
+ <VictoryScatter
+ style={{
+ data: {
+ fill: 'white',
+ stroke: RkTheme.current.colors.charts.area.stroke,
+ strokeOpacity: 0.8,
+ strokeWidth: 1.5,
+ },
+ }}
+ />
+ </VictoryGroup>
+ </VictoryChart>
+ </View>
+ );
+}
diff --git a/frontend/app/components/charts/areaSmoothedChart.js b/frontend/app/components/charts/areaSmoothedChart.js
new file mode 100644
index 0000000..54ca435
--- /dev/null
+++ b/frontend/app/components/charts/areaSmoothedChart.js
@@ -0,0 +1,118 @@
+import React from 'react';
+import {
+ View,
+ Dimensions,
+} from 'react-native';
+import {
+ RkComponent,
+ RkText,
+ RkTheme,
+} from 'react-native-ui-kitten';
+import {
+ VictoryChart,
+ VictoryAxis,
+ VictoryArea,
+} from 'victory-native';
+
+export class AreaSmoothedChart extends RkComponent {
+ state = {
+ data: [
+ [
+ { x: 1, y: 1.0, key: 0 },
+ { x: 2, y: 1.5, key: 1 },
+ { x: 3, y: 1.0, key: 2 },
+ { x: 4, y: 0.5, key: 3 },
+ { x: 5, y: 1.0, key: 4 },
+ { x: 6, y: 2.0, key: 5 },
+ { x: 7, y: 2.5, key: 6 },
+ ],
+ [
+ { x: 1, y: 1.5, key: 0 },
+ { x: 2, y: 2.0, key: 1 },
+ { x: 3, y: 1.5, key: 2 },
+ { x: 4, y: 0.8, key: 3 },
+ { x: 5, y: 1.5, key: 4 },
+ { x: 6, y: 2.6, key: 5 },
+ { x: 7, y: 3.3, key: 6 },
+ ],
+ [
+ { x: 1, y: 2.0, key: 0 },
+ { x: 2, y: 2.5, key: 1 },
+ { x: 3, y: 2.0, key: 2 },
+ { x: 4, y: 1.1, key: 3 },
+ { x: 5, y: 2.0, key: 4 },
+ { x: 6, y: 3.2, key: 5 },
+ { x: 7, y: 4.0, key: 6 },
+ ],
+ [
+ { x: 1, y: 2.5, key: 0 },
+ { x: 2, y: 3.0, key: 1 },
+ { x: 3, y: 2.5, key: 2 },
+ { x: 4, y: 1.4, key: 3 },
+ { x: 5, y: 2.5, key: 4 },
+ { x: 6, y: 3.7, key: 5 },
+ { x: 7, y: 4.7, key: 6 },
+ ],
+ ],
+ };
+ colors = RkTheme.current.colors.charts.followersArea;
+
+ componentWillMount() {
+ this.size = Dimensions.get('window').width;
+ }
+
+ renderChartAreas = () => this.state.data.reverse().map((area, index) => (
+ <VictoryArea
+ key={`${area.length * index}`}
+ interpolation="natural"
+ style={{
+ data: {
+ fill: this.colors[index],
+ stroke: this.colors[index],
+ },
+ }}
+ data={area}
+ />
+ ));
+
+ render = () => (
+ <View>
+ <RkText rkType='header4'>NEW FOLLOWERS</RkText>
+ <VictoryChart
+ padding={{
+ top: 20, left: 40, right: 15, bottom: 40,
+ }}
+ width={this.size - 60}>
+ <VictoryAxis
+ tickValues={['Sun', 'Mon', 'Tue', ' Wed', 'Thu', 'Fri', 'Sat']}
+ style={{
+ axis: { stroke: 'transparent' },
+ tickLabels: {
+ fontSize: 14,
+ stroke: RkTheme.current.colors.text.secondary,
+ fill: RkTheme.current.colors.text.secondary,
+ fontFamily: RkTheme.current.fonts.family.regular,
+ strokeWidth: 0.5,
+ },
+ }}
+ />
+ <VictoryAxis
+ dependentAxis
+ tickValues={['10k', '20k', '30k', '40k']}
+ style={{
+ axis: { stroke: 'transparent' },
+ grid: { stroke: RkTheme.current.colors.disabled, strokeWidth: 0.5 },
+ tickLabels: {
+ fontSize: 14,
+ stroke: RkTheme.current.colors.text.secondary,
+ fill: RkTheme.current.colors.text.secondary,
+ fontFamily: RkTheme.current.fonts.family.regular,
+ strokeWidth: 0.5,
+ },
+ }}
+ />
+ {this.renderChartAreas()}
+ </VictoryChart>
+ </View>
+ )
+}
diff --git a/frontend/app/components/charts/doughnutChart.js b/frontend/app/components/charts/doughnutChart.js
new file mode 100644
index 0000000..5b67ba7
--- /dev/null
+++ b/frontend/app/components/charts/doughnutChart.js
@@ -0,0 +1,125 @@
+import React from 'react';
+import { View } from 'react-native';
+import {
+ RkComponent,
+ RkText,
+ RkTheme,
+ RkStyleSheet,
+} from 'react-native-ui-kitten';
+import { VictoryPie } from 'victory-native';
+import { Svg, Text as SvgText } from 'react-native-svg';
+import { scale } from '../../utils/scale';
+
+export class DoughnutChart extends RkComponent {
+ state = {
+ selected: 0,
+ data: [
+ {
+ x: 1,
+ y: 240,
+ title: '24%',
+ name: 'Likes',
+ color: RkTheme.current.colors.charts.doughnut[0],
+ },
+ {
+ x: 2,
+ y: 270,
+ title: '27%',
+ name: 'Comments',
+ color: RkTheme.current.colors.charts.doughnut[1],
+ },
+ {
+ x: 3,
+ y: 170,
+ title: '17%',
+ name: 'Shares',
+ color: RkTheme.current.colors.charts.doughnut[2],
+ },
+ {
+ x: 4,
+ y: 320,
+ title: '32%',
+ name: 'People',
+ color: RkTheme.current.colors.charts.doughnut[3],
+ },
+ ],
+ };
+ size = 300;
+ fontSize = 40;
+
+ computeColors = () => this.state.data.map(i => i.color);
+
+ onPeopleChartPressed = (event, props) => {
+ this.setState({
+ selected: props.index,
+ });
+ };
+
+ renderMarkdown = () => this.state.data.map(this.renderMarkdownItem);
+
+ renderMarkdownItem = (item) => (
+ <View key={item.name} style={styles.legendItem}>
+ <View style={[styles.itemBadge, { backgroundColor: item.color }]} />
+ <RkText rkType="primary3">{item.name}</RkText>
+ </View>
+ );
+
+ render = () => (
+ <View>
+ <RkText rkType='header4'>AUDIENCE OVERVIEW</RkText>
+ <View style={{ alignSelf: 'center' }}>
+ <Svg width={scale(this.size)} height={scale(this.size)}>
+ <VictoryPie
+ labels={[]}
+ width={scale(this.size)}
+ height={scale(this.size)}
+ colorScale={this.computeColors()}
+ data={this.state.data}
+ standalone={false}
+ padding={scale(25)}
+ innerRadius={scale(70)}
+ events={[{
+ target: 'data',
+ eventHandlers: {
+ onPressIn: this.onPeopleChartPressed,
+ },
+ }]}
+ />
+ <SvgText
+ textAnchor="middle"
+ verticalAnchor="middle"
+ x={scale(this.size / 2)}
+ y={scale(this.size / 2)}
+ height={scale(this.fontSize)}
+ fontSize={scale(this.fontSize)}
+ fontFamily={RkTheme.current.fonts.family.regular}
+ stroke={RkTheme.current.colors.text.base}
+ fill={RkTheme.current.colors.text.base}>
+ {this.state.data[this.state.selected].title}
+ </SvgText>
+ </Svg>
+ </View>
+ <View style={styles.legendContainer}>
+ {this.renderMarkdown()}
+ </View>
+ </View>
+ );
+}
+
+const styles = RkStyleSheet.create(() => ({
+ legendContainer: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ justifyContent: 'space-around',
+ },
+ legendItem: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ itemBadge: {
+ width: 10,
+ height: 10,
+ borderRadius: 5,
+ marginRight: 5,
+ },
+}));
diff --git a/frontend/app/components/charts/index.js b/frontend/app/components/charts/index.js
new file mode 100644
index 0000000..c4438e8
--- /dev/null
+++ b/frontend/app/components/charts/index.js
@@ -0,0 +1,4 @@
+export * from './progessChart';
+export * from './doughnutChart';
+export * from './areaChart';
+export * from './areaSmoothedChart';
diff --git a/frontend/app/components/charts/progessChart.js b/frontend/app/components/charts/progessChart.js
new file mode 100644
index 0000000..bd70dcf
--- /dev/null
+++ b/frontend/app/components/charts/progessChart.js
@@ -0,0 +1,96 @@
+import React from 'react';
+import { View } from 'react-native';
+import {
+ RkComponent,
+ RkText,
+ RkTheme,
+ RkStyleSheet,
+} from 'react-native-ui-kitten';
+import { VictoryPie } from 'victory-native';
+import { Svg, Text as SvgText } from 'react-native-svg';
+import { scale } from '../../utils/scale';
+
+export class ProgressChart extends RkComponent {
+ state = {
+ percents: 72,
+ };
+ size = 120;
+ fontSize = 25;
+
+ componentDidMount() {
+ this.setStateInterval = setInterval(this.updatePercent, 1500);
+ }
+
+ componentWillUnmount() {
+ clearInterval(this.setStateInterval);
+ }
+
+ updatePercent = () => {
+ let positive = Math.random() > 0.5;
+ if (this.state.percents > 95) {
+ positive = false;
+ } else if (this.state.percents < 60) {
+ positive = true;
+ }
+ this.setState({
+ percents: positive ? this.state.percents + 1 : this.state.percents - 1,
+ });
+ };
+
+ getChartData = () => [
+ { x: 1, y: this.state.percents },
+ { x: 2, y: 100 - this.state.percents },
+ ];
+
+ onChartFill = (data) => {
+ const themeColor = RkTheme.current.colors.charts.followersProgress;
+ return data.x === 1 ? themeColor : 'transparent';
+ };
+
+ render = () => (
+ <View>
+ <RkText rkType='header4'>FOLLOWERS</RkText>
+ <View style={styles.chartContainer}>
+ <Svg width={scale(this.size)} height={scale(this.size)}>
+ <VictoryPie
+ labels={[]}
+ padding={0}
+ standalone={false}
+ width={scale(this.size)}
+ height={scale(this.size)}
+ style={{ data: { fill: this.onChartFill } }}
+ data={this.getChartData()}
+ cornerRadius={scale(25)}
+ innerRadius={scale(40)}
+ />
+ <SvgText
+ textAnchor="middle"
+ verticalAnchor="middle"
+ x={scale(this.size / 2)}
+ y={scale(this.size / 2)}
+ height={scale(this.fontSize)}
+ fontSize={scale(this.fontSize)}
+ fontFamily={RkTheme.current.fonts.family.regular}
+ stroke={RkTheme.current.colors.text.base}
+ fill={RkTheme.current.colors.text.base}>
+ {`${this.state.percents}%`}
+ </SvgText>
+ </Svg>
+ <View>
+ <RkText rkType='header4'>REACH</RkText>
+ <RkText rkType='header2'>1 500 356</RkText>
+ <RkText rkType='secondary2'>+6 per day in average</RkText>
+ </View>
+ </View>
+ </View>
+ );
+}
+
+const styles = RkStyleSheet.create(() => ({
+ chartContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-around',
+ alignItems: 'center',
+ marginTop: 10,
+ },
+}));
diff --git a/frontend/app/components/ellipsis.js b/frontend/app/components/ellipsis.js
new file mode 100644
index 0000000..1cdcae1
--- /dev/null
+++ b/frontend/app/components/ellipsis.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import { View } from 'react-native';
+import { RkStyleSheet } from 'react-native-ui-kitten';
+
+export class Ellipsis extends React.Component {
+ render() {
+ return (
+ <View style={styles.container}>
+ <View style={styles.dot} />
+ <View style={styles.dot} />
+ <View style={styles.dot} />
+ </View>
+ );
+ }
+}
+
+let styles = RkStyleSheet.create(theme => ({
+ container: {
+ flexDirection: 'row',
+ marginHorizontal: 5,
+ marginVertical: 10,
+ },
+ dot: {
+ height: 5.5,
+ width: 5.5,
+ borderRadius: 3,
+ backgroundColor: theme.colors.text.base,
+ marginHorizontal: 2.5,
+ },
+}));
diff --git a/frontend/app/components/findFriends.js b/frontend/app/components/findFriends.js
new file mode 100644
index 0000000..0e54034
--- /dev/null
+++ b/frontend/app/components/findFriends.js
@@ -0,0 +1,64 @@
+import React from 'react';
+import {
+ StyleSheet,
+ TouchableOpacity,
+ View,
+ ViewPropTypes,
+} from 'react-native';
+import {
+ RkText,
+ RkTheme,
+} from 'react-native-ui-kitten';
+import PropTypes from 'prop-types';
+import { FontAwesome } from '../assets/icons';
+
+export class FindFriends extends React.Component {
+ static propTypes = {
+ selected: PropTypes.bool,
+ color: PropTypes.string,
+ icon: PropTypes.node.isRequired,
+ text: PropTypes.string.isRequired,
+ onPress: PropTypes.func,
+ style: ViewPropTypes.style,
+ };
+ static defaultProps = {
+ selected: false,
+ color: RkTheme.current.colors.text.base,
+ onPress: (() => null),
+ style: {},
+ };
+
+ render = () => {
+ const color = this.props.selected ? this.props.color : RkTheme.current.colors.disabled;
+ return (
+ <TouchableOpacity style={[styles.wrapper, this.props.style]} onPress={this.props.onPress}>
+ <View style={styles.container}>
+ <View style={styles.text}>
+ <RkText rkType='awesome' style={[styles.icon, { color }]}>{this.props.icon}</RkText>
+ <RkText rkType='header6' style={{ color }}>{`Find Friends With ${this.props.text}`}</RkText>
+ </View>
+ <RkText rkType='awesome small' style={{ color }}>{FontAwesome.chevronRight}</RkText>
+ </View>
+ </TouchableOpacity>
+ );
+ };
+}
+
+let styles = StyleSheet.create({
+ wrapper: {
+ flex: 1,
+ },
+ container: {
+ flex: 1,
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ paddingVertical: 18,
+ },
+ text: {
+ flexDirection: 'row',
+ },
+ icon: {
+ width: 35,
+ },
+});
diff --git a/frontend/app/components/gallery.js b/frontend/app/components/gallery.js
new file mode 100644
index 0000000..bcc57ea
--- /dev/null
+++ b/frontend/app/components/gallery.js
@@ -0,0 +1,78 @@
+import React from 'react';
+import {
+ View,
+ FlatList,
+ Dimensions,
+ StyleSheet,
+} from 'react-native';
+import {
+ RkText,
+ RkButton,
+ RkModalImg,
+} from 'react-native-ui-kitten';
+import PropTypes from 'prop-types';
+import { Ellipsis } from './ellipsis';
+import { SocialBar } from './socialBar';
+
+export class Gallery extends React.Component {
+ static propTypes = {
+ items: PropTypes.arrayOf(PropTypes.node).isRequired,
+ };
+
+ constructor(props) {
+ super(props);
+ const itemSize = (Dimensions.get('window').width - 12) / 3;
+ this.state = {
+ data: this.props.items,
+ itemSize,
+ };
+ }
+
+ extractItemKey = (index) => `${index}`;
+
+ renderHeader = (options) => (
+ <View style={styles.header}>
+ <RkButton rkType='clear contrast' onPress={options.closeImage}>Close</RkButton>
+ <RkText rkType='header4'>{`${options.pageNumber}/${options.totalPages}`}</RkText>
+ <RkButton rkType='clear'>
+ <Ellipsis />
+ </RkButton>
+ </View>
+ );
+
+ renderFooter = () => (
+ <SocialBar />
+ );
+
+ renderItem = ({ index }) => (
+ <RkModalImg
+ style={{ width: this.state.itemSize, height: this.state.itemSize }}
+ renderHeader={this.renderHeader}
+ renderFooter={this.renderFooter}
+ source={this.props.items}
+ index={index}
+ />
+ );
+
+ render = () => (
+ <View style={styles.images}>
+ <FlatList
+ data={this.state.data}
+ numColumns={3}
+ keyExtractor={this.extractItemKey}
+ renderItem={this.renderItem}
+ />
+ </View>
+ );
+}
+
+const styles = StyleSheet.create({
+ images: {
+ flexDirection: 'row',
+ paddingHorizontal: 0.5,
+ },
+ header: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ },
+});
diff --git a/frontend/app/components/gradientButton/index.js b/frontend/app/components/gradientButton/index.js
new file mode 100644
index 0000000..92ebd79
--- /dev/null
+++ b/frontend/app/components/gradientButton/index.js
@@ -0,0 +1,45 @@
+import React from 'react';
+import { LinearGradient } from 'expo';
+import {
+ RkButton,
+ RkText,
+ RkComponent,
+} from 'react-native-ui-kitten';
+
+export class GradientButton extends RkComponent {
+ componentName = 'GradientButton';
+ typeMapping = {
+ button: {},
+ gradient: {},
+ text: {},
+ };
+
+ renderContent = (textStyle) => {
+ const hasText = this.props.text === undefined;
+ return hasText ? this.props.children : this.renderText(textStyle);
+ };
+
+ renderText = (textStyle) => (
+ <RkText style={textStyle}>{this.props.text}</RkText>
+ );
+
+ render() {
+ const { button, gradient, text: textStyle } = this.defineStyles();
+ const { style, rkType, ...restProps } = this.props;
+ const colors = this.props.colors || this.extractNonStyleValue(gradient, 'colors');
+ return (
+ <RkButton
+ rkType='stretch'
+ style={[button, style]}
+ {...restProps}>
+ <LinearGradient
+ colors={colors}
+ start={{ x: 0.0, y: 0.5 }}
+ end={{ x: 1, y: 0.5 }}
+ style={[gradient]}>
+ {this.renderContent(textStyle)}
+ </LinearGradient>
+ </RkButton>
+ );
+ }
+}
diff --git a/frontend/app/components/gradientButton/types.js b/frontend/app/components/gradientButton/types.js
new file mode 100644
index 0000000..62bcf22
--- /dev/null
+++ b/frontend/app/components/gradientButton/types.js
@@ -0,0 +1,48 @@
+import { scaleVertical } from '../../utils/scale';
+
+export const GradientButtonTypes = (theme) => ({
+ _base: {
+ button: {
+ alignItems: 'stretch',
+ paddingVertical: 0,
+ paddingHorizontal: 0,
+ height: scaleVertical(40),
+ borderRadius: 20,
+ },
+ gradient: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ borderRadius: 20,
+ colors: theme.colors.gradients.base,
+ },
+ text: {
+ backgroundColor: 'transparent',
+ color: theme.colors.text.inverse,
+ },
+ },
+ large: {
+ button: {
+ alignSelf: 'stretch',
+ height: scaleVertical(56),
+ borderRadius: 28,
+ },
+ gradient: {
+ borderRadius: 28,
+ },
+ },
+ statItem: {
+ button: {
+ flex: 1,
+ borderRadius: 5,
+ marginHorizontal: 10,
+ height: null,
+ alignSelf: 'auto',
+ },
+ gradient: {
+ flex: 1,
+ borderRadius: 5,
+ padding: 10,
+ },
+ },
+});
diff --git a/frontend/app/components/index.js b/frontend/app/components/index.js
new file mode 100644
index 0000000..786a99b
--- /dev/null
+++ b/frontend/app/components/index.js
@@ -0,0 +1,14 @@
+export * from './avatar';
+export * from './gradientButton';
+export * from './charts';
+export * from './socialBar';
+export * from './switch/index';
+export * from './ellipsis';
+export * from './gallery';
+export * from './socialSetting';
+export * from './findFriends';
+export * from './progressBar';
+export * from './navBar';
+export * from './paginationIndicator';
+export * from './passwordTextInput';
+export * from './cardInput';
diff --git a/frontend/app/components/navBar.js b/frontend/app/components/navBar.js
new file mode 100644
index 0000000..2947209
--- /dev/null
+++ b/frontend/app/components/navBar.js
@@ -0,0 +1,145 @@
+import React from 'react';
+import {
+ StyleSheet,
+ View,
+} from 'react-native';
+import { DrawerActions } from 'react-navigation';
+import PropTypes from 'prop-types';
+import _ from 'lodash';
+import { RkText, RkButton, RkStyleSheet } from 'react-native-ui-kitten';
+import { FontAwesome } from '../assets/icons';
+import { UIConstants } from '../config/appConstants';
+import NavigationType from '../config/navigation/propTypes';
+
+export class NavBar extends React.Component {
+ static propTypes = {
+ navigation: NavigationType.isRequired,
+ headerProps: PropTypes.shape().isRequired,
+ };
+
+ onNavigationLeftMenuButtonPressed = () => {
+ this.props.navigation.dispatch(DrawerActions.openDrawer());
+ };
+
+ onNavigationLeftBackButtonPressed = () => {
+ this.props.navigation.goBack();
+ };
+
+ renderTitleItem = (title, options) => {
+ const isCustom = options !== undefined;
+ return isCustom ? this.renderCustomTitleItem(options) : this.renderNavigationTitleItem(title);
+ };
+
+ renderLeftItem = (options) => {
+ const isCustom = options !== undefined;
+ return isCustom ? this.renderCustomLeftItem(options) : this.renderNavigationLeftItem();
+ };
+
+ renderRightItem = (options) => {
+ const isCustom = options !== undefined;
+ return isCustom ? this.renderCustomRightItem(options) : this.renderNavigationRightItem();
+ };
+
+ renderNavigationTitleItem = (title) => (
+ <View style={styles.title}>
+ <RkText>{title}</RkText>
+ </View>
+ );
+
+ renderNavigationLeftBackItem = () => (
+ <RkButton
+ rkType='clear'
+ style={styles.menu}
+ onPress={this.onNavigationLeftBackButtonPressed}>
+ <RkText rkType='awesome hero'>{FontAwesome.chevronLeft}</RkText>
+ </RkButton>
+ );
+
+ renderNavigationLeftMenuItem = () => (
+ <RkButton
+ rkType='clear'
+ style={styles.menu}
+ onPress={this.onNavigationLeftMenuButtonPressed}>
+ <RkText rkType='awesome'>{FontAwesome.bars}</RkText>
+ </RkButton>
+ );
+
+ renderNavigationLeftItemContent = (sceneIndex) => {
+ const isFirstScene = sceneIndex === 0;
+ return isFirstScene ? this.renderNavigationLeftMenuItem() : this.renderNavigationLeftBackItem();
+ };
+
+ renderNavigationLeftItem = () => {
+ const sceneIndex = _.findIndex(this.props.headerProps.scenes, { isActive: true });
+ return (
+ <View style={styles.left}>
+ {this.renderNavigationLeftItemContent(sceneIndex)}
+ </View>
+ );
+ };
+
+ renderNavigationRightItem = () => undefined;
+
+ renderCustomTitleItem = (options) => (
+ <View
+ style={styles.title}>
+ {options}
+ </View>
+ );
+
+ renderCustomLeftItem = (options) => (
+ <View style={styles.left}>{options}</View>
+ );
+
+ renderCustomRightItem = (options) => (
+ <View style={styles.right}>{options}</View>
+ );
+
+ render() {
+ const { options } = this.props.headerProps.scene.descriptor;
+ return (
+ <View style={styles.layout}>
+ <View style={styles.container}>
+ {this.renderTitleItem(options.title, options.headerTitle)}
+ {this.renderLeftItem(options.headerLeft)}
+ {this.renderRightItem(options.headerRight)}
+ </View>
+ </View>
+ );
+ }
+}
+
+const styles = RkStyleSheet.create(theme => ({
+ layout: {
+ backgroundColor: theme.colors.screen.base,
+ paddingTop: UIConstants.StatusbarHeight,
+ borderBottomWidth: StyleSheet.hairlineWidth,
+ borderBottomColor: theme.colors.border.base,
+ },
+ container: {
+ flexDirection: 'row',
+ height: UIConstants.AppbarHeight,
+
+ },
+ left: {
+ position: 'absolute',
+ top: 0,
+ bottom: 0,
+ justifyContent: 'center',
+ },
+ right: {
+ position: 'absolute',
+ right: 0,
+ top: 0,
+ bottom: 0,
+ justifyContent: 'center',
+ },
+ title: {
+ ...StyleSheet.absoluteFillObject,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ menu: {
+ width: 40,
+ },
+}));
diff --git a/frontend/app/components/paginationIndicator.js b/frontend/app/components/paginationIndicator.js
new file mode 100644
index 0000000..6a043d0
--- /dev/null
+++ b/frontend/app/components/paginationIndicator.js
@@ -0,0 +1,49 @@
+import React from 'react';
+import { View } from 'react-native';
+import { RkStyleSheet } from 'react-native-ui-kitten';
+import PropTypes from 'prop-types';
+
+export class PaginationIndicator extends React.Component {
+ static propTypes = {
+ current: PropTypes.number,
+ length: PropTypes.number.isRequired,
+ };
+ static defaultProps = {
+ current: 0,
+ };
+
+ renderIndicatorItem = (index, selected) => (
+ <View style={selected ? [styles.base, styles.selected] : styles.base} key={index} />
+ );
+
+ renderIndicators = () => {
+ const indicators = [];
+ for (let i = 0; i < this.props.length; i += 1) {
+ indicators.push(this.renderIndicatorItem(i, i === this.props.current));
+ }
+ return indicators;
+ };
+
+ render = () => (
+ <View style={styles.container}>
+ {this.renderIndicators()}
+ </View>
+ );
+}
+
+const styles = RkStyleSheet.create(theme => ({
+ container: {
+ flexDirection: 'row',
+ },
+ base: {
+ width: 8,
+ height: 8,
+ borderRadius: 5,
+ borderColor: theme.colors.brand,
+ borderWidth: 1,
+ marginHorizontal: 5,
+ },
+ selected: {
+ backgroundColor: theme.colors.brand,
+ },
+}));
diff --git a/frontend/app/components/passwordTextInput.js b/frontend/app/components/passwordTextInput.js
new file mode 100644
index 0000000..5590caf
--- /dev/null
+++ b/frontend/app/components/passwordTextInput.js
@@ -0,0 +1,47 @@
+import React from 'react';
+import {
+ RkButton,
+ RkTextInput,
+ RkText,
+ RkStyleSheet,
+} from 'react-native-ui-kitten';
+import { FontAwesome } from '../assets/icons';
+
+export class PasswordTextInput extends React.Component {
+ state = {
+ hidden: true,
+ };
+
+ onInputLabelPressed = () => {
+ this.setState({ hidden: !this.state.hidden });
+ };
+
+ renderInputLabel = () => (
+ <RkButton
+ style={styles.button}
+ rkType='clear'
+ onPress={this.onInputLabelPressed}>
+ <RkText style={styles.icon} rkType='awesome secondaryColor'>{FontAwesome.slashEye}</RkText>
+ </RkButton>
+ );
+
+ render = () => (
+ <RkTextInput
+ autoCapitalize='none'
+ rkType='bordered rounded iconRight'
+ autoCorrect={false}
+ label={this.renderInputLabel()}
+ secureTextEntry={this.state.hidden}
+ {...this.props}
+ />
+ );
+}
+
+const styles = RkStyleSheet.create({
+ icon: {
+ fontSize: 24,
+ },
+ button: {
+ right: 17,
+ },
+});
diff --git a/frontend/app/components/picker/datePicker.js b/frontend/app/components/picker/datePicker.js
new file mode 100644
index 0000000..4225613
--- /dev/null
+++ b/frontend/app/components/picker/datePicker.js
@@ -0,0 +1,103 @@
+import React from 'react';
+import { RkPicker } from 'react-native-ui-kitten';
+
+export const DatePart = Object.freeze({ YEAR: 1, MONTH: 2, DAY: 3 });
+
+export class DatePicker extends React.Component {
+ componentName = 'DatePicker';
+
+ state = {
+ data: {
+ days: DatePicker.generateArrayFromRange(1, 31),
+ years: DatePicker.generateArrayFromRange(2000, 2030),
+ months: [
+ { key: 1, value: 'Jun' }, { key: 2, value: 'Feb' },
+ { key: 3, value: 'Mar' }, { key: 4, value: 'Apr' },
+ { key: 5, value: 'May' }, { key: 6, value: 'Jun' },
+ { key: 7, value: 'Jul' }, { key: 8, value: 'Aug' },
+ { key: 9, value: 'Sep' }, { key: 10, value: 'Oct' },
+ { key: 11, value: 'Nov' }, { key: 12, value: 'Dec' },
+ ],
+ },
+ };
+
+ onDatePickerConfirm = (date) => {
+ let resultDate = {};
+ if (this.props.customDateParts) {
+ let i = 0;
+ if (this.props.customDateParts.includes(DatePart.MONTH)) {
+ resultDate.month = date[i += 1];
+ }
+ if (this.props.customDateParts.includes(DatePart.DAY)) {
+ resultDate.day = date[i += 1];
+ }
+ if (this.props.customDateParts.includes(DatePart.YEAR)) {
+ resultDate.year = date[i];
+ }
+ } else {
+ resultDate = { month: date[0], day: date[1], year: date[2] };
+ }
+ this.props.onConfirm(resultDate);
+ };
+
+ static generateArrayFromRange(start, finish) {
+ return Array(...Array((finish - start) + 1)).map((_, i) => start + i);
+ }
+
+ findElementByKey(key, array) {
+ let element = array[0];
+ array.forEach((value) => {
+ if (value.key === key) element = value;
+ });
+ return element;
+ }
+
+ render() {
+ const {
+ onConfirm,
+ selectedYear,
+ selectedMonth,
+ selectedDay,
+ customDateParts,
+ ...props
+ } = this.props;
+
+ let data = [this.state.data.months, this.state.data.days, this.state.data.years];
+ let selectedOptions = [
+ this.findElementByKey(selectedMonth, this.state.data.months),
+ selectedDay || 1,
+ selectedYear || 2000,
+ ];
+ if (customDateParts) {
+ selectedOptions = [];
+ data = [];
+ if (customDateParts.includes(DatePart.MONTH)) {
+ data.push(this.state.data.months);
+ selectedOptions.push(this.findElementByKey(selectedMonth, this.state.data.months));
+ }
+ if (customDateParts.includes(DatePart.DAY)) {
+ data.push(this.state.data.days);
+ selectedOptions.push(selectedDay || 1);
+ }
+ if (customDateParts.includes(DatePart.YEAR)) {
+ data.push(this.state.data.years);
+ selectedOptions.push(selectedYear || 2000);
+ }
+ }
+ return (
+ <RkPicker
+ rkType='highlight'
+ title='Set Date'
+ data={data}
+ onConfirm={this.onDatePickerConfirm}
+ selectedOptions={selectedOptions}
+ optionRkType='subtitle small'
+ selectedOptionRkType='header4'
+ titleTextRkType='header4'
+ cancelTextRkType='light'
+ confirmTextRkType=''
+ {...props}
+ />
+ );
+ }
+}
diff --git a/frontend/app/components/picker/index.js b/frontend/app/components/picker/index.js
new file mode 100644
index 0000000..6d1605a
--- /dev/null
+++ b/frontend/app/components/picker/index.js
@@ -0,0 +1 @@
+export * from './datePicker';
diff --git a/frontend/app/components/progressBar.js b/frontend/app/components/progressBar.js
new file mode 100644
index 0000000..dd3a3a1
--- /dev/null
+++ b/frontend/app/components/progressBar.js
@@ -0,0 +1,63 @@
+import React from 'react';
+import {
+ StyleSheet,
+ View,
+ Animated,
+ Easing,
+ ViewPropTypes,
+} from 'react-native';
+import PropTypes from 'prop-types';
+import { RkTheme } from 'react-native-ui-kitten';
+
+export class ProgressBar extends React.Component {
+ static propTypes = {
+ width: PropTypes.number.isRequired,
+ progress: PropTypes.number,
+ color: PropTypes.string,
+ style: ViewPropTypes.style,
+ };
+ static defaultProps = {
+ progress: 0,
+ color: RkTheme.current.colors.accent,
+ style: {},
+ };
+
+ state = {
+ progress: new Animated.Value(0),
+ };
+
+ componentDidUpdate(prevProps) {
+ if (this.props.progress >= 0 && this.props.progress !== prevProps.progress) {
+ this.animate(this.props.progress);
+ }
+ }
+
+ animate = (endValue) => {
+ Animated.timing(this.state.progress, {
+ easing: Easing.inOut(Easing.ease),
+ duration: 500,
+ toValue: endValue,
+ }).start();
+ };
+
+ render() {
+ const width = this.state.progress.interpolate({
+ inputRange: [0, 1],
+ outputRange: [0, this.props.width],
+ });
+ return (
+ <View style={[styles.container, this.props.style, { width: this.props.width }]}>
+ <Animated.View style={[styles.value, { width }, { backgroundColor: this.props.color }]} />
+ </View>
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ container: {
+ height: 3,
+ },
+ value: {
+ height: 3,
+ },
+});
diff --git a/frontend/app/components/socialBar/index.js b/frontend/app/components/socialBar/index.js
new file mode 100644
index 0000000..5cf3fc2
--- /dev/null
+++ b/frontend/app/components/socialBar/index.js
@@ -0,0 +1,61 @@
+import React from 'react';
+import { View } from 'react-native';
+import {
+ RkText,
+ RkButton,
+ RkComponent,
+} from 'react-native-ui-kitten';
+import { FontAwesome } from '../../assets/icons';
+
+export class SocialBar extends RkComponent {
+ componentName = 'SocialBar';
+ typeMapping = {
+ container: {},
+ section: {},
+ icon: {},
+ label: {},
+ };
+ static data = {
+ comments: '26',
+ is_solved: "Doesn't solved",
+ };
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ comments: this.props.comments,
+ is_solved: this.props.is_solved || SocialBar.data.is_solved,
+ };
+ }
+
+
+ onCommentButtonPressed = () => {
+ };
+
+
+ render() {
+ const {
+ container, section, icon, label,
+ } = this.defineStyles();
+
+ const comments = this.state.comments + (this.props.showLabel ? ' Comments' : '');
+ const is_solved = this.state.is_solved + (this.props.showLabel ? '' : '');
+
+ return (
+ <View style={container}>
+ <View style={section}>
+ <RkButton rkType='clear' onPress={this.onCommentButtonPressed}>
+ <RkText rkType='awesome hintColor' style={icon}>{FontAwesome.comment}</RkText>
+ <RkText rkType='primary4 hintColor' style={label}>{comments}</RkText>
+ </RkButton>
+ </View>
+ <View style={section}>
+ <RkButton rkType='clear' >
+ <RkText rkType='awesome hintColor' style={icon}>{FontAwesome.slashEye}</RkText>
+ <RkText rkType='primary4 hintColor' style={label}>{is_solved}</RkText>
+ </RkButton>
+ </View>
+ </View>
+ );
+ }
+}
diff --git a/frontend/app/components/socialBar/types.js b/frontend/app/components/socialBar/types.js
new file mode 100644
index 0000000..3aa638d
--- /dev/null
+++ b/frontend/app/components/socialBar/types.js
@@ -0,0 +1,43 @@
+export const SocialBarTypes = (theme) => ({
+ _base: {
+ container: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ flexDirection: 'row',
+ flex: 1,
+ },
+ section: {
+ justifyContent: 'center',
+ flexDirection: 'row',
+ flex: 1,
+ },
+ icon: {
+ fontSize: 20,
+ },
+ label: {
+ marginLeft: 8,
+ alignSelf: 'flex-end',
+ },
+ },
+ leftAligned: {
+ section: {
+ alignItems: 'flex-start',
+ justifyContent: 'flex-start',
+ },
+ label: {
+ color: theme.colors.text.inverse,
+ },
+ icon: {
+ color: theme.colors.text.inverse,
+ },
+ },
+ space: {
+ container: {
+ justifyContent: 'space-between',
+ paddingHorizontal: 10,
+ },
+ section: {
+ flex: -1,
+ },
+ },
+});
diff --git a/frontend/app/components/socialSetting.js b/frontend/app/components/socialSetting.js
new file mode 100644
index 0000000..1c2f2a3
--- /dev/null
+++ b/frontend/app/components/socialSetting.js
@@ -0,0 +1,66 @@
+import React from 'react';
+import {
+ View,
+ StyleSheet,
+} from 'react-native';
+import {
+ RkText,
+ RkTheme,
+} from 'react-native-ui-kitten';
+import PropTypes from 'prop-types';
+import { RkSwitch } from './switch/index';
+
+export class SocialSetting extends React.Component {
+ static propTypes = {
+ name: PropTypes.string.isRequired,
+ icon: PropTypes.node.isRequired,
+ selected: PropTypes.bool,
+ tintColor: PropTypes.string,
+ };
+ static defaultProps = {
+ selected: true,
+ tintColor: RkTheme.current.colors.accent,
+ };
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ selected: this.props.selected,
+ };
+ }
+
+ onSwitchValueChanged = (value) => {
+ this.setState({ selected: value });
+ };
+
+ render() {
+ const color = this.state.selected ? this.props.tintColor : RkTheme.current.colors.disabled;
+ return (
+ <View style={styles.container}>
+ <View style={styles.left}>
+ <RkText rkType='awesome large' style={[styles.icon, { color }]}>{this.props.icon}</RkText>
+ <RkText rkType='small' style={{ color }}>{this.props.name}</RkText>
+ </View>
+ <RkSwitch value={this.state.selected} onValueChange={this.onSwitchValueChanged} />
+ </View>
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ flex: 1,
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ paddingVertical: 14,
+ },
+ left: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ icon: {
+ width: 35,
+ alignItems: 'center',
+ },
+});
diff --git a/frontend/app/components/switch/index.android.js b/frontend/app/components/switch/index.android.js
new file mode 100644
index 0000000..2750545
--- /dev/null
+++ b/frontend/app/components/switch/index.android.js
@@ -0,0 +1,171 @@
+/*
+ *
+ * This is modified version of https://github.com/poberwong/react-native-switch-pro
+ * Copyright (c) 2016 PoberWong
+ *
+ */
+import React from 'react';
+import {
+ Animated,
+ Easing,
+ PanResponder,
+} from 'react-native';
+import { RkComponent } from 'react-native-ui-kitten';
+
+const width = 52;
+const height = 32;
+const animationDuration = 200;
+const offLeftValue = -2;
+const onLeftValue = 20;
+
+export class RkSwitch extends RkComponent {
+ componentName = 'RkSwitch';
+ typeMapping = {
+ container: {
+ onColor: 'onColor',
+ offColor: 'offColor',
+ },
+ thumb: {},
+ };
+ selectedType = 'selected';
+
+ constructor(props) {
+ super(props);
+ this.offset = width - height;
+ this.handlerSize = height;
+ this.state = {
+ name: this.props.name,
+ value: this.props.value,
+ toggleable: true,
+ alignItems: this.props.value ? 'flex-end' : 'flex-start',
+ left: this.props.value ? onLeftValue : offLeftValue,
+ handlerAnimation: new Animated.Value(this.handlerSize),
+ switchAnimation: new Animated.Value(this.props.value ? -1 : 1),
+ };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const { value } = this.state;
+ if (nextProps === this.props) {
+ return;
+ }
+ if (typeof nextProps.value !== 'undefined' && nextProps.value !== value) {
+ this.toggleSwitch(true);
+ }
+ }
+
+ componentWillMount() {
+ this.panResponder = PanResponder.create({
+ onStartShouldSetPanResponder: () => true,
+ onStartShouldSetPanResponderCapture: () => true,
+ onMoveShouldSetPanResponder: () => true,
+ onMoveShouldSetPanResponderCapture: () => true,
+ onPanResponderTerminationRequest: () => true,
+ onPanResponderGrant: this.onPanResponderGrant,
+ onPanResponderMove: this.onPanResponderMove,
+ onPanResponderRelease: this.onPanResponderRelease,
+ });
+ }
+
+ onPanResponderGrant = () => {
+ this.animateHandler(height * 0.9);
+ };
+
+ onPanResponderMove = (evt, gestureState) => {
+ const { value } = this.state;
+
+ this.setState({
+ toggleable: value ? (gestureState.dx < 10) : (gestureState.dx > -10),
+ });
+ };
+
+ onPanResponderRelease = () => {
+ const { toggleable } = this.state;
+ const { disabled, onValueChange } = this.props;
+
+ if (toggleable && !disabled) {
+ if (onValueChange) {
+ this.toggleSwitch(onValueChange);
+ }
+ }
+ };
+
+ toggleSwitch = (result, callback = () => null) => {
+ const { value, switchAnimation } = this.state;
+ const toValue = !value;
+
+ this.animateHandler(this.handlerSize);
+
+ this.animateSwitch(toValue, () => {
+ callback(toValue);
+ this.setState({
+ value: toValue,
+ left: toValue ? onLeftValue : offLeftValue,
+ });
+ switchAnimation.setValue(toValue ? -1 : 1);
+ });
+ };
+
+ animateSwitch = (value, callback = () => null) => {
+ const { switchAnimation } = this.state;
+
+ Animated.timing(
+ switchAnimation,
+ {
+ toValue: value ? this.offset : -this.offset,
+ duration: animationDuration,
+ easing: Easing.linear,
+ },
+ ).start(callback);
+ };
+
+ animateHandler = (value, callback = () => null) => {
+ const { handlerAnimation } = this.state;
+
+ Animated.timing(
+ handlerAnimation,
+ {
+ toValue: value,
+ duration: animationDuration,
+ easing: Easing.linear,
+ },
+ ).start(callback);
+ };
+
+ render() {
+ const {
+ switchAnimation, handlerAnimation, left, value,
+ } = this.state;
+ const {
+ style,
+ ...rest
+ } = this.props;
+
+ const type = value ? this.selectedType : '';
+ const { container, thumb } = this.defineStyles(type);
+ const onColor = this.extractNonStyleValue(container, 'onColor');
+ const offColor = this.extractNonStyleValue(container, 'offColor');
+
+ const interpolatedBackgroundColor = switchAnimation.interpolate({
+ inputRange: value ? [-this.offset, -1] : [1, this.offset],
+ outputRange: [offColor, onColor],
+ });
+
+ return (
+ <Animated.View
+ {...rest}
+ {...this.panResponder.panHandlers}
+ style={[style, container, {
+ backgroundColor: interpolatedBackgroundColor,
+ }]}>
+ <Animated.View style={[thumb, {
+ position: 'absolute',
+ left,
+ height: handlerAnimation,
+ transform: [{ translateX: switchAnimation }],
+ }]}
+ />
+ </Animated.View>
+ );
+ }
+}
diff --git a/frontend/app/components/switch/index.ios.js b/frontend/app/components/switch/index.ios.js
new file mode 100644
index 0000000..068cbfb
--- /dev/null
+++ b/frontend/app/components/switch/index.ios.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import { Switch } from 'react-native';
+import { RkComponent } from 'react-native-ui-kitten';
+
+
+export class RkSwitch extends RkComponent {
+ componentName = 'RkSwitch';
+ typeMapping = {
+ container: {
+ onColor: 'onColor',
+ offColor: 'offColor',
+ },
+ main: {},
+ };
+ selectedType = 'selected';
+
+ constructor(props) {
+ super(props);
+ this.onChange = this.props.onValueChange ?
+ this.props.onValueChange
+ : () => true;
+ }
+
+ render() {
+ const { container } = this.defineStyles();
+ const onColor = this.extractNonStyleValue(container, 'onColor');
+ return (
+ <Switch
+ style={this.props.style}
+ value={this.props.value}
+ onValueChange={(value) => this.onChange(value)}
+ onTintColor={onColor}
+ />
+ );
+ }
+}
diff --git a/frontend/app/components/switch/types.js b/frontend/app/components/switch/types.js
new file mode 100644
index 0000000..96b116e
--- /dev/null
+++ b/frontend/app/components/switch/types.js
@@ -0,0 +1,35 @@
+export const SwitchTypes = (theme) => ({
+ _base: {
+ container: {
+ width: 52,
+ height: 32,
+ overflow: 'hidden',
+ justifyContent: 'center',
+ borderRadius: 16,
+ borderWidth: 1,
+ borderColor: theme.colors.border.secondary,
+ onColor: theme.colors.primary,
+ offColor: {
+ android: theme.colors.screen.base,
+ ios: theme.colors.border.base,
+ },
+ },
+ thumb: {
+ position: 'absolute',
+ height: 32,
+ width: 32,
+ borderWidth: 1,
+ borderColor: theme.colors.border.secondary,
+ backgroundColor: theme.colors.screen.base,
+ borderRadius: 16,
+ },
+ },
+ selected: {
+ thumb: {
+ borderColor: theme.colors.primary,
+ },
+ container: {
+ borderColor: theme.colors.primary,
+ },
+ },
+});
diff --git a/frontend/app/components/walkthrough.js b/frontend/app/components/walkthrough.js
new file mode 100644
index 0000000..7fdb247
--- /dev/null
+++ b/frontend/app/components/walkthrough.js
@@ -0,0 +1,63 @@
+import React from 'react';
+import {
+ View,
+ FlatList,
+ Dimensions,
+ StyleSheet,
+} from 'react-native';
+import PropTypes from 'prop-types';
+
+export class Walkthrough extends React.Component {
+ static propTypes = {
+ children: PropTypes.arrayOf(PropTypes.element).isRequired,
+ onChanged: PropTypes.func,
+ };
+ static defaultProps = {
+ onChanged: (() => null),
+ };
+
+ constructor(props) {
+ super(props);
+ this.itemWidth = Dimensions.get('window').width;
+ }
+
+ extractItemKey = (item) => `${this.props.children.indexOf(item)}`;
+
+ onScrollEnd = (e) => {
+ const { contentOffset } = e.nativeEvent;
+ const viewSize = e.nativeEvent.layoutMeasurement;
+ const pageNum = Math.floor(contentOffset.x / viewSize.width);
+ this.props.onChanged(pageNum);
+ };
+
+ renderItem = ({ item }) => (
+ <View style={[styles.item, { width: this.itemWidth }]}>
+ {item}
+ </View>
+ );
+
+ render = () => (
+ <FlatList
+ style={styles.list}
+ data={this.props.children}
+ onMomentumScrollEnd={this.onScrollEnd}
+ keyExtractor={this.extractItemKey}
+ pagingEnabled
+ horizontal
+ renderSeparator={() => null}
+ showsHorizontalScrollIndicator={false}
+ showsVerticalScrollIndicator={false}
+ directionalLockEnabled
+ renderItem={this.renderItem}
+ />
+ );
+}
+
+const styles = StyleSheet.create({
+ list: {
+ flex: 1,
+ },
+ item: {
+ flex: 1,
+ },
+});