├── .babelrc ├── .flowconfig ├── .gitignore ├── .watchmanconfig ├── App.js ├── App.test.js ├── LICENSE ├── README.md ├── app.json ├── app ├── components │ ├── DateView.js │ ├── Input.js │ ├── Title.js │ ├── TodoRowItem.js │ └── styles │ │ ├── DateViewStyles.js │ │ ├── InputStyles.js │ │ ├── TitleStyles.js │ │ └── TodoRowItemStyles.js ├── config │ ├── config.json │ └── index.js ├── containers │ ├── ActiveTodosScreen.js │ ├── CompletedTodosScreen.js │ └── styles │ │ ├── ActiveTodosStyles.js │ │ ├── CommonStyles.js │ │ └── index.js ├── models │ └── Todo.js ├── navigation │ └── AppNavigator.js └── redux │ ├── actions │ └── TodoActionCreators.js │ ├── reducers │ ├── AppReducer.js │ ├── NavReducer.js │ └── TodosReducer.js │ └── store │ └── TodosStore.js ├── main.js └── package.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["babel-preset-expo"], 3 | "env": { 4 | "development": { 5 | "plugins": ["transform-react-jsx-source"] 6 | }, 7 | "production": { 8 | "plugins": ["transform-remove-console"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore unexpected extra "@providesModule" 9 | .*/node_modules/.*/node_modules/fbjs/.* 10 | 11 | ; Ignore duplicate module providers 12 | ; For RN Apps installed via npm, "Libraries" folder is inside 13 | ; "node_modules/react-native" but in the source repo it is in the root 14 | .*/Libraries/react-native/React.js 15 | .*/Libraries/react-native/ReactNative.js 16 | 17 | ; Additional create-react-native-app ignores 18 | 19 | ; Ignore duplicate module providers 20 | .*/node_modules/fbemitter/lib/* 21 | 22 | ; Ignore misbehaving dev-dependencies 23 | .*/node_modules/xdl/build/* 24 | .*/node_modules/reqwest/tests/* 25 | 26 | ; Ignore missing expo-sdk dependencies (temporarily) 27 | ; https://github.com/expo/expo/issues/162 28 | .*/node_modules/expo/src/* 29 | 30 | ; Ignore react-native-fbads dependency of the expo sdk 31 | .*/node_modules/react-native-fbads/* 32 | 33 | [include] 34 | 35 | [libs] 36 | node_modules/react-native/Libraries/react-native/react-native-interface.js 37 | node_modules/react-native/flow 38 | flow/ 39 | 40 | [options] 41 | module.system=haste 42 | 43 | emoji=true 44 | 45 | experimental.strict_type_args=true 46 | 47 | munge_underscores=true 48 | 49 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 50 | 51 | suppress_type=$FlowIssue 52 | suppress_type=$FlowFixMe 53 | suppress_type=$FixMe 54 | 55 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(4[0-9]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 56 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(4[0-9]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 57 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 58 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 59 | 60 | unsafe.enable_getters_and_setters=true 61 | 62 | [version] 63 | ^0.49.1 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | npm-debug.* 4 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { View, Platform } from 'react-native'; 4 | 5 | import { addNavigationHelpers } from 'react-navigation'; 6 | import AppNavigator from './app/navigation/AppNavigator'; 7 | 8 | 9 | class App extends React.Component { 10 | 11 | render() { 12 | 13 | return ( 14 | 15 | 19 | 20 | ); 21 | } 22 | } 23 | 24 | const mapStateToProps = (state) => ({ 25 | nav: state.nav, 26 | }) 27 | 28 | export default connect(mapStateToProps)(App) 29 | -------------------------------------------------------------------------------- /App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import App from './App'; 3 | 4 | import renderer from 'react-test-renderer'; 5 | 6 | it('renders without crashing', () => { 7 | const rendered = renderer.create().toJSON(); 8 | expect(rendered).toBeTruthy(); 9 | }); 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Rishabh bhatia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Todo's app Preview [(Watch it on YouTube)](https://youtu.be/Dql1nQ73CY4) 2 | 3 | ![alt text](http://res.cloudinary.com/randomstuffibuy/image/upload/c_scale,w_200/v1504442580/github/todo-mobile/todo-app-v1.0.gif) 4 | 5 | ## Running 6 | 7 | #### Clone & install 8 | 9 | * Clone this repo `git clone https://github.com/rishabhbhatia/react-native-todo.git` 10 | * `cd Todo` 11 | * Run `npm install` 12 | * Run `npm start` 13 | * Press `i` to run in iOS simulator or press `a` for Android 14 | 15 | #### Mobile Device 16 | * Download Expo app 17 | * Scan QR code from mobile or Open explore tab, Press search and Enter url shown in terminal. 18 | 19 | ## React and Expo versions 20 | 21 | * React 22 | * [react](https://github.com/facebook/react): v16.0.0-alpha.12 23 | * [react-native](https://github.com/facebook/react-native): v0.47.0 24 | * [Expo](https://expo.io): v20.0.0 25 | 26 | ## Credits 27 | 28 | UI Inspiration: Tob Siripak [(Dribbble)](https://dribbble.com/shots/1074906-GIF-Delete-task-and-assign-task-to-your-teammate-in-action) 29 | 30 | ## License 31 | 32 | Released under the [Mit License](https://opensource.org/licenses/MIT) 33 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "sdkVersion": "20.0.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /app/components/DateView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View, Text, StyleSheet } from 'react-native'; 3 | 4 | import moment from 'moment'; 5 | import styles from './styles/DateViewStyles'; 6 | 7 | export default class DateView extends Component { 8 | 9 | constructor(props) { 10 | super(props); 11 | this.day = moment().format('ddd'); 12 | this.date = moment().format('D'); 13 | this.month = moment().format('MMMM'); 14 | }; 15 | 16 | render() { 17 | 18 | return ( 19 | 20 | {this.day.toUpperCase()} 21 | {this.date} 22 | {this.month.toUpperCase()} 23 | 24 | ); 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /app/components/Input.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Animated, View, TextInput, StyleSheet } from 'react-native'; 3 | 4 | import styles from './styles/InputStyles'; 5 | 6 | export default class Input extends Component { 7 | 8 | state = { 9 | text: '', 10 | }; 11 | 12 | onChangeText = (text) => this.setState({text}) 13 | 14 | onSubmitEditing = () => { 15 | const {onSubmitEditing} = this.props; 16 | const {text} = this.state; 17 | 18 | if (!text) 19 | return; 20 | 21 | onSubmitEditing(text); 22 | this.setState({ text: '' }); 23 | }; 24 | 25 | render() { 26 | const {placeholder, placeholderTextColor} = this.props; 27 | const {selectionColor, underlineColorAndroid} = this.props; 28 | const {maxLength, clearTextOnFocus} = this.props; 29 | const {text} = this.state; 30 | 31 | return ( 32 | 44 | ) 45 | }; 46 | 47 | }; 48 | -------------------------------------------------------------------------------- /app/components/Title.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View, Text } from 'react-native'; 3 | 4 | import styles from './styles/TitleStyles'; 5 | 6 | const Title = (title) => { 7 | 8 | return ( 9 | 10 | {title} 11 | 12 | ); 13 | }; 14 | 15 | export default Title; 16 | -------------------------------------------------------------------------------- /app/components/TodoRowItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View, Text, StyleSheet, Animated } from 'react-native'; 3 | 4 | import config from '../config'; 5 | 6 | import Icon from 'react-native-vector-icons/FontAwesome'; 7 | import styles from './styles/TodoRowItemStyles'; 8 | 9 | export default class TodoRowItem extends Component { 10 | 11 | render() { 12 | const {todo, time} = this.props; 13 | const {text} = todo; 14 | 15 | return ( 16 | 17 | 18 | 19 | 24 | 25 | 26 | {text} 27 | {time} 28 | 29 | 30 | ); 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /app/components/styles/DateViewStyles.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet } from 'react-native'; 3 | 4 | const styles = StyleSheet.create({ 5 | container: { 6 | padding: 10, 7 | justifyContent: 'center', 8 | alignItems: 'center' 9 | }, 10 | day: { 11 | color: 'white', 12 | fontSize: 10, 13 | fontWeight: '400' 14 | }, 15 | date: { 16 | color: 'white', 17 | fontSize: 30, 18 | fontWeight: '600' 19 | }, 20 | month: { 21 | color: 'white', 22 | fontSize: 8, 23 | fontWeight: '400' 24 | } 25 | }); 26 | 27 | export default styles; 28 | -------------------------------------------------------------------------------- /app/components/styles/InputStyles.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Dimensions } from 'react-native'; 3 | 4 | const styles = StyleSheet.create({ 5 | input: { 6 | padding: 12, 7 | backgroundColor: '#526373', 8 | color: 'white', 9 | fontSize: 15, 10 | borderRadius: 3, 11 | width: Dimensions.get('window').width * 0.7, 12 | }, 13 | }) 14 | 15 | export default styles; 16 | -------------------------------------------------------------------------------- /app/components/styles/TitleStyles.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { StyleSheet, Platform } from 'react-native'; 3 | 4 | const APPBAR_HEIGHT = Platform.OS === 'ios' ? 44 : 56; 5 | 6 | const styles = StyleSheet.create({ 7 | header: { 8 | backgroundColor: '#313842', 9 | borderRadius: 2, 10 | borderColor: '#1B2127', 11 | borderBottomWidth: 1, 12 | shadowColor: '#000', 13 | shadowOffset: { width: 0, height: 2 }, 14 | shadowOpacity: 0.8, 15 | shadowRadius: 2, 16 | elevation: 3, 17 | height: APPBAR_HEIGHT, 18 | justifyContent: 'center', 19 | }, 20 | title: { 21 | textAlign: 'center', 22 | color: '#e7d629', 23 | fontWeight: '600', 24 | }, 25 | }); 26 | 27 | export default styles; 28 | -------------------------------------------------------------------------------- /app/components/styles/TodoRowItemStyles.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet } from 'react-native'; 3 | 4 | const ROW_HEIGHT = 70; 5 | 6 | const styles = StyleSheet.create({ 7 | row: { 8 | backgroundColor: '#313842', 9 | paddingLeft: 15, 10 | paddingRight: 15, 11 | flexDirection: 'row', 12 | justifyContent: 'flex-start', 13 | alignItems: 'center', 14 | height: ROW_HEIGHT, 15 | }, 16 | timeline: { 17 | height: ROW_HEIGHT, 18 | width: 8, 19 | justifyContent: 'center', 20 | alignItems: 'center', 21 | }, 22 | timelineVerticalLink: { 23 | height: ROW_HEIGHT, 24 | width: 1, 25 | backgroundColor: '#526373', 26 | justifyContent: 'center' 27 | }, 28 | icon: { 29 | color: '#e7d629', 30 | backgroundColor: 'transparent', 31 | position: 'absolute', 32 | alignItems: 'center' 33 | }, 34 | content: { 35 | flex: 1, 36 | flexDirection: 'column', 37 | justifyContent: 'center', 38 | alignItems: 'flex-start', 39 | paddingRight: 10, 40 | paddingLeft: 10, 41 | paddingTop: 10, 42 | }, 43 | text: { 44 | fontSize: 17, 45 | fontWeight: '500', 46 | color: 'white', 47 | }, 48 | time: { 49 | fontSize: 10, 50 | fontWeight: '400', 51 | color: '#828B7B', 52 | } 53 | }); 54 | 55 | export default styles; 56 | -------------------------------------------------------------------------------- /app/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "todos": { 3 | "types": { 4 | "active": "active", 5 | "completed": "completed" 6 | }, 7 | "actions": { 8 | "add_todo": "ADD_TODO", 9 | "delete_active_todo": "DELETE_ACTIVE_TODO", 10 | "delete_completed_todo": "DELETE_COMPLETED_TODO", 11 | "complete_todo": "COMPLETE_TODO" 12 | } 13 | }, 14 | "colors": { 15 | "golden": "#e7d629", 16 | "white": "#ffffff", 17 | "transparent": "transparent", 18 | "dim_gray": "#313842", 19 | 20 | }, 21 | "constants": { 22 | "active_todos_screen": { 23 | "title": "My Todo List!", 24 | "add_todo_placeholder": "Type a todo, then hit enter!", 25 | "add_todo_input_maxlength": 25, 26 | "add_todo_input_clear_text_on_focus": true, 27 | }, 28 | "completed_todos_screen": { 29 | "title": "Completed Todos!", 30 | "disable_right_swipe": true, 31 | }, 32 | "hidden_row_icon_size": 20, 33 | "row_swipe_duration": 200, 34 | "row_swipe_open_percent": 40, 35 | "row_timeline_icon_size": 6, 36 | }, 37 | "icons": { 38 | "check": "check", 39 | "times": "times", 40 | "circle": "circle", 41 | "pencil_square": "pencil-square-o", 42 | "check_square": "check-square-o" 43 | }, 44 | "navigation": { 45 | "label_active": "Active", 46 | "label_completed": "Completed", 47 | "tab_bar_position": "bottom", 48 | "swipe_enabled": false, 49 | "animation_enabled": true, 50 | "lazy": false, 51 | "show_label": true, 52 | "show_icon": true, 53 | "tab_padding": 5, 54 | "label_font_size": 12, 55 | "tab_icon_size": 18 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/config/index.js: -------------------------------------------------------------------------------- 1 | import config from './config'; 2 | 3 | export default config; 4 | -------------------------------------------------------------------------------- /app/containers/ActiveTodosScreen.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { Text, ListView, FlatList, View, Dimensions} from 'react-native'; 3 | import Icon from 'react-native-vector-icons/FontAwesome'; 4 | 5 | import { connect } from 'react-redux'; 6 | import { bindActionCreators } from 'redux'; 7 | 8 | import moment from 'moment'; 9 | import SwipeView from 'react-native-swipeview'; 10 | 11 | import config from '../config'; 12 | 13 | import * as TodoActionCreators from '../redux/actions/TodoActionCreators'; 14 | 15 | import Title from '../components/Title'; 16 | import Input from '../components/Input'; 17 | import TodoRowItem from '../components/TodoRowItem'; 18 | import DateView from '../components/DateView'; 19 | 20 | import styles from './styles/ActiveTodosStyles'; 21 | import commonStyles from './styles'; 22 | 23 | 24 | class ActiveTodosScreen extends Component { 25 | 26 | render() { 27 | const {todosReducer} = this.props; 28 | const {active} = todosReducer; 29 | const {todos} = active; 30 | const {addTodo, completeTodo, deleteActiveTodo} = this.props; 31 | 32 | this.leftOpenValue = Dimensions.get('window').width; 33 | this.rightOpenValue = -Dimensions.get('window').width; 34 | 35 | return ( 36 | 37 | { Title(config.constants.active_todos_screen.title) } 38 | 39 | 40 | 49 | 50 | 51 | 52 | todo.id} 55 | enableEmptySections={true} 56 | ItemSeparatorComponent={() => } 57 | renderItem={({item, index}) => ( 58 | ( 60 | 65 | )} 66 | renderLeftView={() => ( 67 | 68 | 73 | 74 | )} 75 | renderRightView={() => ( 76 | 77 | 82 | 83 | )} 84 | leftOpenValue={this.leftOpenValue} 85 | rightOpenValue={this.rightOpenValue} 86 | swipeDuration={config.constants.row_swipe_duration} 87 | swipeToOpenPercent={config.constants.row_swipe_open_percent} 88 | onSwipedLeft={() => deleteActiveTodo(index)} 89 | onSwipedRight={() => { 90 | completeTodo(index); 91 | deleteActiveTodo(index); 92 | }} 93 | /> 94 | )} 95 | /> 96 | 97 | ); 98 | }; 99 | 100 | }; 101 | 102 | const mapDispatchToProps = (dispatch) => { 103 | return bindActionCreators(TodoActionCreators, dispatch); 104 | }; 105 | 106 | const mapStateToProps = (state) => ({ 107 | todosReducer: state.todosReducer, 108 | nav: state.nav, 109 | }); 110 | 111 | export default connect(mapStateToProps, mapDispatchToProps)(ActiveTodosScreen); 112 | -------------------------------------------------------------------------------- /app/containers/CompletedTodosScreen.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { StyleSheet, Text, FlatList, ListView, View, Platform, Dimensions, Animated} from 'react-native'; 3 | import Icon from 'react-native-vector-icons/FontAwesome'; 4 | 5 | import { connect } from 'react-redux'; 6 | import { bindActionCreators } from 'redux'; 7 | 8 | import moment from 'moment'; 9 | import SwipeView from 'react-native-swipeview'; 10 | 11 | import config from '../config'; 12 | 13 | import * as TodoActionCreators from '../redux/actions/TodoActionCreators'; 14 | 15 | import Title from '../components/Title'; 16 | import TodoRowItem from '../components/TodoRowItem'; 17 | 18 | import commonStyles from './styles'; 19 | 20 | 21 | class CompletedTodosScreen extends Component { 22 | 23 | render() { 24 | const {dispatch, todosReducer} = this.props; 25 | const {completed} = todosReducer; 26 | const {todos} = completed; 27 | 28 | const {deleteCompletedTodo} = this.props; 29 | 30 | this.rightOpenValue = -Dimensions.get('window').width; 31 | 32 | return ( 33 | 34 | { Title(config.constants.completed_todos_screen.title) } 35 | todo.id} 38 | extraData={this.props} 39 | enableEmptySections={true} 40 | ItemSeparatorComponent={() => } 41 | renderItem={({item, index}) => ( 42 | ( 44 | 49 | )} 50 | renderRightView={() => ( 51 | 52 | 57 | 58 | )} 59 | rightOpenValue={this.rightOpenValue} 60 | swipeDuration={config.constants.row_swipe_duration} 61 | swipeToOpenPercent={config.constants.row_swipe_open_percent} 62 | disableSwipeToRight={config.constants.completed_todos_screen.disable_right_swipe} 63 | onSwipedLeft={() => deleteCompletedTodo(index)} 64 | /> 65 | )} 66 | /> 67 | 68 | ); 69 | }; 70 | 71 | }; 72 | 73 | const mapDispatchToProps = (dispatch) => { 74 | return bindActionCreators(TodoActionCreators, dispatch); 75 | }; 76 | 77 | const mapStateToProps = (state) => ({ 78 | todosReducer: state.todosReducer, 79 | nav: state.nav, 80 | }); 81 | 82 | export default connect(mapStateToProps, mapDispatchToProps)(CompletedTodosScreen); 83 | -------------------------------------------------------------------------------- /app/containers/styles/ActiveTodosStyles.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Platform } from 'react-native'; 3 | 4 | const styles = StyleSheet.create({ 5 | header: { 6 | flexDirection: 'row', 7 | justifyContent: 'center', 8 | padding: 5 9 | }, 10 | inputContainer: { 11 | justifyContent: 'center', 12 | alignItems: 'center', 13 | padding: 10, 14 | } 15 | }); 16 | 17 | export default styles; 18 | -------------------------------------------------------------------------------- /app/containers/styles/CommonStyles.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Platform } from 'react-native'; 3 | 4 | const commonStyles = StyleSheet.create({ 5 | container: { 6 | marginTop: (Platform.OS === 'ios') ? 20 : 0, 7 | flex: 1, 8 | backgroundColor: '#1B2127', 9 | }, 10 | rowLeft: { 11 | flex: 1, 12 | flexDirection: 'row', 13 | justifyContent: 'flex-start', 14 | alignItems: 'center', 15 | paddingLeft: 20, 16 | paddingRight: 20, 17 | backgroundColor: 'green' 18 | }, 19 | rowRight: { 20 | flex: 1, 21 | flexDirection: 'row', 22 | justifyContent: 'flex-end', 23 | alignItems: 'center', 24 | paddingLeft: 20, 25 | paddingRight: 20, 26 | backgroundColor: '#FE4D33' 27 | }, 28 | icon: { 29 | color: 'white', 30 | }, 31 | separator: { 32 | flex: 1, 33 | height: StyleSheet.hairlineWidth, 34 | backgroundColor: '#182129', 35 | } 36 | }); 37 | 38 | export default commonStyles; 39 | -------------------------------------------------------------------------------- /app/containers/styles/index.js: -------------------------------------------------------------------------------- 1 | import commonStyles from './CommonStyles'; 2 | 3 | export default commonStyles; 4 | -------------------------------------------------------------------------------- /app/models/Todo.js: -------------------------------------------------------------------------------- 1 | import uuid from 'uuid'; 2 | import moment from 'moment'; 3 | 4 | class Todo { 5 | 6 | constructor(todo) { 7 | this.id = uuid.v1(); 8 | this.text = todo.text; 9 | this.time = moment().startOf('hour').fromNow(); 10 | this.type = 'active'; 11 | this.completed = false; 12 | } 13 | 14 | getId = () => { 15 | return this.id; 16 | } 17 | 18 | getText = () => { 19 | return this.text; 20 | } 21 | 22 | setText = (text) => this.text = text; 23 | 24 | getType = () => { 25 | return this.type; 26 | } 27 | 28 | setType = (type) => this.type = type; 29 | 30 | isCompleted = () => { 31 | return this.completed; 32 | } 33 | 34 | setCompleted = (completed) => this.completed = completed; 35 | } 36 | 37 | export default Todo; 38 | -------------------------------------------------------------------------------- /app/navigation/AppNavigator.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TabBarBottom, addNavigationHelpers, TabNavigator } from 'react-navigation'; 3 | 4 | import Icon from 'react-native-vector-icons/FontAwesome'; 5 | import config from '../config'; 6 | 7 | import ActiveTodosScreen from '../containers/ActiveTodosScreen'; 8 | import CompletedTodosScreen from '../containers/CompletedTodosScreen'; 9 | 10 | const tabBarConfig = { 11 | tabBarComponent: TabBarBottom, 12 | tabBarPosition: config.navigation.tab_bar_position, 13 | swipeEnabled: config.navigation.swipe_enabled, 14 | animationEnabled: config.navigation.animation_enabled, 15 | lazy: config.navigation.lazy, 16 | tabBarOptions: { 17 | activeTintColor: config.colors.golden, 18 | showLabel: config.navigation.show_label, 19 | showIcon: config.navigation.show_icon, 20 | style: { 21 | padding: config.navigation.tab_padding, 22 | backgroundColor: config.colors.dim_gray 23 | }, 24 | labelStyle: { 25 | fontSize: config.navigation.label_font_size 26 | }, 27 | } 28 | }; 29 | 30 | const AppNavigator = TabNavigator({ 31 | ActiveTodos: { 32 | screen: ActiveTodosScreen, 33 | navigationOptions: { 34 | tabBarLabel: config.navigation.label_active, 35 | tabBarIcon: ({tintColor, focused}) => ( 36 | 40 | ) 41 | } 42 | }, 43 | CompletedTodos: { 44 | screen: CompletedTodosScreen, 45 | navigationOptions: { 46 | tabBarLabel: config.navigation.label_completed, 47 | tabBarIcon: ({tintColor, focused}) => ( 48 | 52 | ) 53 | } 54 | }, 55 | }, tabBarConfig); 56 | 57 | export default AppNavigator; 58 | -------------------------------------------------------------------------------- /app/redux/actions/TodoActionCreators.js: -------------------------------------------------------------------------------- 1 | import config from '../../config'; 2 | import Todo from '../../models/Todo'; 3 | 4 | const actions = config.todos.actions; 5 | 6 | export function addTodo(text) { 7 | return { 8 | type: actions.add_todo, 9 | payload: new Todo({undefined ,text}) 10 | }; 11 | }; 12 | 13 | export function deleteActiveTodo(index) { 14 | return { 15 | type: actions.delete_active_todo, 16 | payload: index 17 | }; 18 | }; 19 | 20 | export function deleteCompletedTodo(index) { 21 | return { 22 | type: actions.delete_completed_todo, 23 | payload: index 24 | }; 25 | }; 26 | 27 | export function completeTodo(index) { 28 | return { 29 | type: actions.complete_todo, 30 | payload: index 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /app/redux/reducers/AppReducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import todosReducer from './TodosReducer'; 4 | import navReducer from './NavReducer'; 5 | 6 | 7 | const appReducer = combineReducers({ 8 | todosReducer, 9 | nav: navReducer, 10 | }); 11 | 12 | export default appReducer; 13 | -------------------------------------------------------------------------------- /app/redux/reducers/NavReducer.js: -------------------------------------------------------------------------------- 1 | import { NavigationActions } from 'react-navigation'; 2 | import AppNavigator from '../../navigation/AppNavigator'; 3 | 4 | const firstAction = AppNavigator.router.getActionForPathAndParams('ActiveTodos'); 5 | const initialNavState = AppNavigator.router.getStateForAction(firstAction); 6 | 7 | const navReducer = (state = initialNavState, action) => { 8 | const nextState = AppNavigator.router.getStateForAction(action, state); 9 | return nextState || state; 10 | } 11 | 12 | export default navReducer; 13 | -------------------------------------------------------------------------------- /app/redux/reducers/TodosReducer.js: -------------------------------------------------------------------------------- 1 | import Todo from '../../models/Todo' 2 | import config from '../../config' 3 | 4 | let todoFour = new Todo({ 'id': 4, 'text':'Watch Boardwalk Empire'}); 5 | todoFour.setType('completed'); 6 | 7 | let todoFive = new Todo({ 'id': 5, 'text':'Get Netflix'}); 8 | todoFive.setType('completed'); 9 | 10 | const initialState = { 11 | active: { 12 | todos: [new Todo({ 'id': 1, 'text':'Go for a walk'}), new Todo({ 'id': 2, 'text':'Meeting at 11 AM'}), 13 | new Todo({ 'id': 3, 'text':'Coffee in morning'})], 14 | }, 15 | completed: { 16 | todos: [todoFour, todoFive], 17 | } 18 | } 19 | 20 | const todosReducer = (state = initialState, action) => { 21 | 22 | const {active, completed} = state; 23 | 24 | const activeTodos = active.todos; 25 | const completedTodos = completed.todos; 26 | 27 | const {type, payload} = action; 28 | 29 | const actions = config.todos.actions; 30 | 31 | switch (type) { 32 | case actions.add_todo: 33 | return { 34 | ...state, 35 | active: { 36 | ...state.active, 37 | todos: [payload, ...activeTodos] 38 | } 39 | } 40 | break; 41 | case actions.delete_active_todo: 42 | return { 43 | ...state, 44 | active: { 45 | ...state.active, 46 | todos: activeTodos.filter((todo, i) => payload != i), 47 | } 48 | } 49 | break; 50 | case actions.delete_completed_todo: 51 | return { 52 | ...state, 53 | completed: { 54 | todos: completedTodos.filter((todo, i) => payload != i) 55 | } 56 | } 57 | break; 58 | case actions.complete_todo: 59 | let completedTodo; 60 | return { 61 | ...state, 62 | active: { 63 | ...state.active, 64 | todos: activeTodos.map((todo, index) => { 65 | if (index === payload) { 66 | completedTodo = todo; 67 | return { 68 | ...todo, 69 | completed: !todo.completed 70 | } 71 | } 72 | return todo 73 | }), 74 | }, 75 | completed: { 76 | todos: [completedTodo, ...completedTodos] 77 | } 78 | } 79 | break; 80 | default: return state; 81 | }; 82 | }; 83 | 84 | export default todosReducer; 85 | -------------------------------------------------------------------------------- /app/redux/store/TodosStore.js: -------------------------------------------------------------------------------- 1 | import { AsyncStorage } from 'react-native'; 2 | import { createStore, applyMiddleware } from 'redux'; 3 | 4 | import { persistStore, autoRehydrate, purgeStoredState } from 'redux-persist'; 5 | 6 | import appReducer from '../reducers/AppReducer'; 7 | 8 | 9 | const todosStore = createStore( 10 | appReducer, 11 | undefined, 12 | autoRehydrate() 13 | ); 14 | 15 | persistStore(todosStore, {storage: AsyncStorage}); 16 | // purgeStoredState({storage: AsyncStorage}) // Clear persistStore 17 | 18 | export default todosStore; 19 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { AppRegistry, View } from 'react-native' 3 | 4 | import { Provider } from 'react-redux' 5 | 6 | import App from './App' 7 | import todosStore from './app/redux/store/TodosStore' 8 | 9 | const AppWithStore = () => ( 10 | 11 | 12 | 13 | ) 14 | 15 | AppRegistry.registerComponent('main', () => AppWithStore) 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Todo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-native-scripts": "1.2.1", 7 | "jest-expo": "~20.0.0", 8 | "react-test-renderer": "16.0.0-alpha.12" 9 | }, 10 | "main_old": "./node_modules/react-native-scripts/build/bin/crna-entry.js", 11 | "main": "./main.js", 12 | "scripts": { 13 | "start": "react-native-scripts start", 14 | "eject": "react-native-scripts eject", 15 | "android": "react-native-scripts android", 16 | "ios": "react-native-scripts ios", 17 | "test": "node node_modules/jest/bin/jest.js --watch" 18 | }, 19 | "jest": { 20 | "preset": "jest-expo" 21 | }, 22 | "dependencies": { 23 | "babel-plugin-transform-remove-console": "^6.8.5", 24 | "expo": "^20.0.0", 25 | "moment": "^2.18.1", 26 | "react": "16.0.0-alpha.12", 27 | "react-native": "^0.47.0", 28 | "react-native-elements": "^0.16.0", 29 | "react-native-swipeview": "^1.0.1", 30 | "react-navigation": "^1.0.0-beta.11", 31 | "redux-persist": "^4.9.1", 32 | "uuid": "^3.1.0" 33 | } 34 | } 35 | --------------------------------------------------------------------------------