diff --git a/App.js b/App.js index a48cc9c..d15c5c4 100644 --- a/App.js +++ b/App.js @@ -7,12 +7,13 @@ import { StackNavigator, TabNavigator } from 'react-navigation'; import FlashStatusBar from './components/ui/FlashStatusBar'; import FlashStackNavigator from './components/navigators/FlashStackNavigator'; +import reducers from './reducers'; export default class App extends Component { render() { - const store = createStore(() => { }); + const store = createStore(reducers); return ( diff --git a/actions/index.js b/actions/index.js index 2cc1dcb..33428bb 100644 --- a/actions/index.js +++ b/actions/index.js @@ -1,8 +1,16 @@ export const LOAD_DECKS = "LOAD_DECKS"; +export const NEW_DECK = "NEW_DECK"; export function loadDecks(decks){ return { - action: LOAD_DECKS, + type: LOAD_DECKS, decks } +} + +export function newDeck(deck){ + return { + type: NEW_DECK, + deck + } } \ No newline at end of file diff --git a/components/flashcards/DeckList.js b/components/flashcards/DeckList.js index 0c56883..bbfcc8b 100644 --- a/components/flashcards/DeckList.js +++ b/components/flashcards/DeckList.js @@ -8,10 +8,13 @@ import { TouchableOpacity, View } from 'react-native'; +import { NavigationActions } from 'react-navigation'; +import { Entypo } from '@expo/vector-icons'; import DeckItem from '../ui/DeckItem'; -import { loadDecks } from '../../services'; +import * as services from '../../services'; +import * as actions from '../../actions' import { COLOR_B_4 } from '../../utils/colors'; @@ -21,30 +24,47 @@ class DeckList extends Component { tabBarLabel: 'Decks' } - state = { - decks: null + async componentDidMount() { + const decks = await services.loadDecks(); + this.props.dispatch(actions.loadDecks(decks)); + } - componentDidMount() { - const decks = loadDecks(); - this.setState({ - decks - }) + navigateToNewDeck() { + const routeName = "New"; + + const navigation = NavigationActions.navigate({ + routeName + }); + + console.log(navigation); + + this.props.navigation.dispatch(navigation); } render() { - if (this.state.decks === null) { + if (this.props.decks === null) { return ( ); + } else if (this.props.decks.length === 0) { + return ( + + + You have no registered decks. + + Add a new deck! + + + ); } else { return ( - {this.state.decks.map((deck) => ( - + {this.props.decks.map((deck) => ( + ))} @@ -58,7 +78,21 @@ class DeckList extends Component { const styles = StyleSheet.create({ container: { flex: 1 + }, + btnAdd: { + backgroundColor: COLOR_B_4, + borderRadius: 2, + padding: 4, + margin: 10 } }) -export default connect()(DeckList); \ No newline at end of file +function mapStateToProps({ decks }) { + const deckArray = Object.keys(decks).map((key) => decks[key]); + + return { + decks: deckArray + } +} + +export default connect(mapStateToProps)(DeckList); \ No newline at end of file diff --git a/components/flashcards/NewDeck.js b/components/flashcards/NewDeck.js index 475504e..1368ac6 100644 --- a/components/flashcards/NewDeck.js +++ b/components/flashcards/NewDeck.js @@ -1,5 +1,7 @@ import React, { Component } from 'react'; +import { container, connect } from 'react-redux'; import { + Alert, Slider, StyleSheet, Text, @@ -7,28 +9,77 @@ import { TouchableOpacity, View } from 'react-native'; +import { NavigationActions } from 'react-navigation'; import { MaterialIcons, MaterialCommunityIcons } from '@expo/vector-icons'; import DifficultyMeter from '../ui/DifficultyMeter'; +import { newDeck } from '../../actions'; +import { persistDeck } from '../../services'; + import { COLOR_B_4, COLOR_B_5, COLOR_WHITE } from '../../utils/colors'; -export default class NewDeck extends Component { +class NewDeck extends Component { state = { deck: { name: '', description: '', - difficulty: 0 + difficulty: 0, + cards: [], + won: 0, + lost: 0 + } + } + + reset() { + this.setState({ + deck: { + name: '', + description: '', + difficulty: 0 + } + }); + } + + async createNewDeck() { + try { + if (this.state.deck.name.trim() === '') { + Alert.alert( + 'Required', + "Deck's name is required", + [{ text: 'Ok', onPress: () => { } }], + { cancelable: false }); + return; + } + + persistDeck(this.state.deck); + this.props.dispatch(newDeck(this.state.deck)); + + const routeName = "Index"; + const navigation = NavigationActions.navigate({ routeName }); + + this.props.navigation.dispatch(navigation); + + } catch (e) { + Alert.alert( + 'Error', + 'Error adding a new deck to your device.', + [ + { text: 'Ok', onPress: () => { } } + ]); + console.error(e); } + this.reset(); } render() { return ( Name - { this.setState((prev) => { const prevDeck = prev.deck; @@ -75,18 +126,10 @@ export default class NewDeck extends Component { - { - this.setState({ - deck: { - name: '', - description: '', - difficulty: 0 - } - }); - }}> + - + @@ -121,4 +164,6 @@ const styles = StyleSheet.create({ margin: 5, padding: 10 } -}) \ No newline at end of file +}) + +export default connect()(NewDeck); \ No newline at end of file diff --git a/reducers/index.js b/reducers/index.js index d7dee6c..eb12deb 100644 --- a/reducers/index.js +++ b/reducers/index.js @@ -1,17 +1,32 @@ -import { LOAD_DECKS } from '../actions'; +import { + LOAD_DECKS, + NEW_DECK +} from '../actions'; -function decks(state, action) { +function decks(state = { decks: {} }, action) { const { type } = action; + const { decks } = state; - switch(type){ - case LOAD_DECKS:{ + switch (type) { + case LOAD_DECKS: { const decks = action.decks; return { decks }; } - default:{ + case NEW_DECK: { + const deck = action.deck; + const freshDecks = { + ...decks, + [deck.name]: deck + } + + return { + decks + } + } + default: { return state; } } diff --git a/services/index.js b/services/index.js index b9fd105..12835c4 100644 --- a/services/index.js +++ b/services/index.js @@ -1,12 +1,14 @@ -export function loadDecks() { - return [ - { id: 1, name: 'Javascript 101', description: 'Basic questions about loops, data types and functions', cards: [], difficulty: 18, won: 10, lost: 3 }, - { id: 2, name: 'Cryptocurrency', description: 'Main algorithms, PoS, PoW, Bitcoin...', cards: [], difficulty: 58, won: 9, lost: 7 }, - { id: 3, name: 'Java threads', description: 'Concurrency framework, locks and APIs', cards: [], difficulty: 80, won: 18, lost: 12 }, - { id: 4, name: 'Unity 5 basics', description: 'Basic components and actions', cards: [], difficulty: 1, won: 6, lost: 1 }, - { id: 5, name: 'Pro React Native', description: 'Advanced and custom components', cards: [], difficulty: 70, won: 10, lost: 5 }, - { id: 6, name: 'RESTful', description: 'Verbs, scenarios and status codes', cards: [], difficulty: 60, won: 18, lost: 12 }, - { id: 7, name: 'Elixir', description: 'Basic structure, loops, running scripts', cards: [], difficulty: 1, won: 6, lost: 1 }, - { id: 8, name: 'Python 3', description: 'Concepts, changes and new apis', cards: [], difficulty: 22, won: 10, lost: 5 }, - ]; +import { AsyncStorage } from 'react-native'; + +const FLASHCARDS_STORAGE_KEY = "Flashcards:decks" + +export async function persistDeck(deck){ + await AsyncStorage.mergeItem(FLASHCARDS_STORAGE_KEY, JSON.stringify({ + [deck.name]: deck + })) +} + +export async function loadDecks() { + const decks = JSON.parse(await AsyncStorage.getItem(FLASHCARDS_STORAGE_KEY)); + return decks !== null ? decks : []; } \ No newline at end of file