diff --git a/components/app.js b/components/app.js index cef888e..53aa680 100644 --- a/components/app.js +++ b/components/app.js @@ -1,117 +1,74 @@ import React, { Component } from 'react' import { View, BackHandler } from 'react-native' +import PropTypes from 'prop-types' + +import { LocalDate } from 'js-joda' import { connect } from 'react-redux' -import { getDate } from '../slices/date' +import { getDate, setDate } from '../slices/date' +import { getNavigation, navigate } from '../slices/navigation' import Header from './header' import Menu from './menu' -import Home from './home' -import Calendar from './calendar' -import CycleDay from './cycle-day/cycle-day-overview' -import symptomViews from './cycle-day/symptoms' -import Chart from './chart/chart' -import SettingsMenu from './settings/settings-menu' -import settingsViews from './settings' -import Stats from './stats' -import {headerTitles, menuTitles} from '../i18n/en/labels' -import setupNotifications from '../lib/notifications' -import { closeDb } from '../db' +import { pagesList, isSymptomView, isSettingsView } from './pages' -const HOME_PAGE = 'Home' -const CYCLE_DAY_PAGE = 'CycleDay' -const SETTINGS_MENU_PAGE = 'SettingsMenu' +import { headerTitles } from '../i18n/en/labels' +import setupNotifications from '../lib/notifications' +import { closeDb, getCycleDay } from '../db' class App extends Component { + + static propTypes = { + date: PropTypes.string, + navigation: PropTypes.object.isRequired, + } + constructor(props) { super(props) + + this.todayDateString = LocalDate.now().toString() + props.setDate(this.todayDateString) + this.state = { - currentPage: HOME_PAGE, - cycleDay: {}, + cycleDay: getCycleDay(this.todayDateString), } - this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.handleBackButtonPress) - setupNotifications(this.navigate) + + this.backHandler = BackHandler.addEventListener( + 'hardwareBackPress', + this.handleBackButtonPress + ) + + setupNotifications(this.props.navigate) } componentWillUnmount() { this.backHandler.remove() } - navigate = (pageName, cycleDay) => { - const { currentPage } = this.state - // for the back button to work properly, we want to - // remember two origins: which menu item we came from - // and from where we navigated to the symptom view (day - // view or home page) - if (this.isMenuItem()) { - this.menuOrigin = currentPage - } - if (!this.isSymptomView()) { - this.originForSymptomView = currentPage - } - this.setState({ currentPage: pageName, cycleDay }) - } - handleBackButtonPress = () => { - const { currentPage } = this.state - if (currentPage === HOME_PAGE) { + const { current, prev } = this.props.navigation + if (current === 'Home') { closeDb() return false } - if (this.isSymptomView()) { - this.navigate(this.originForSymptomView) - } else if (this.isSettingsView()) { - this.navigate(SETTINGS_MENU_PAGE) - } else if (currentPage === CYCLE_DAY_PAGE) { - this.navigate(this.menuOrigin) - } else { - this.navigate(HOME_PAGE) - } + this.props.navigate(prev) return true } - isMenuItem() { - return Object.keys(menuTitles).includes(this.state.currentPage) - } - - isSymptomView() { - return Object.keys(symptomViews).includes(this.state.currentPage) - } - - isSettingsView() { - return Object.keys(settingsViews).includes(this.state.currentPage) - } - - isDefaultView() { - const { currentPage } = this.state - return this.isMenuItem(currentPage) || currentPage === SETTINGS_MENU_PAGE - } - render() { - const { currentPage, cycleDay } = this.state - const pages = { - Home, - Calendar, - CycleDay, - Chart, - SettingsMenu, - ...settingsViews, - Stats, - ...symptomViews - } - const Page = pages[currentPage] + const { cycleDay } = this.state + const currentPage = this.props.navigation.current + + const Page = pagesList[currentPage] const title = headerTitles[currentPage] - const hasDefaultHeader = - !this.isSymptomView() && - currentPage !== CYCLE_DAY_PAGE - - const isSettingsSubView = this.isSettingsView() + const isSymptomEditView = isSymptomView(currentPage) + const isSettingsSubView = isSettingsView(currentPage) + const isCycleDayView = currentPage === 'CycleDay' return ( - - { hasDefaultHeader && + { !isSymptomEditView && !isCycleDayView &&
- {!this.isSymptomView() && - - } + { !isSymptomEditView && } ) } @@ -135,11 +89,19 @@ class App extends Component { const mapStateToProps = (state) => { return({ - date: getDate(state) + date: getDate(state), + navigation: getNavigation(state) + }) +} + +const mapDispatchToProps = (dispatch) => { + return({ + setDate: (date) => dispatch(setDate(date)), + navigate: (page) => dispatch(navigate(page)), }) } export default connect( mapStateToProps, - null + mapDispatchToProps )(App) \ No newline at end of file diff --git a/components/calendar.js b/components/calendar.js index faf4bea..9387e0c 100644 --- a/components/calendar.js +++ b/components/calendar.js @@ -3,6 +3,7 @@ import { CalendarList } from 'react-native-calendars' import { connect } from 'react-redux' import { setDate } from '../slices/date' +import { navigate } from '../slices/navigation' import { LocalDate } from 'js-joda' import { getBleedingDaysSortedByDate } from '../db' @@ -68,6 +69,7 @@ class CalendarView extends Component { const mapDispatchToProps = (dispatch) => { return({ setDate: (date) => dispatch(setDate(date)), + navigate: (page) => dispatch(navigate(page)), }) } diff --git a/components/chart/day-column.js b/components/chart/day-column.js index 633f636..fffc1fc 100644 --- a/components/chart/day-column.js +++ b/components/chart/day-column.js @@ -3,6 +3,7 @@ import { TouchableOpacity } from 'react-native' import { connect } from 'react-redux' import { setDate } from '../../slices/date' +import { navigate } from '../../slices/navigation' import { getCycleDay } from '../../db' @@ -109,6 +110,7 @@ class DayColumn extends Component { const mapDispatchToProps = (dispatch) => { return({ setDate: (date) => dispatch(setDate(date)), + navigate: (page) => dispatch(navigate(page)), }) } diff --git a/components/cycle-day/cycle-day-overview.js b/components/cycle-day/cycle-day-overview.js index 4df2f28..7415ab2 100644 --- a/components/cycle-day/cycle-day-overview.js +++ b/components/cycle-day/cycle-day-overview.js @@ -3,6 +3,7 @@ import { ScrollView, View } from 'react-native' import { connect } from 'react-redux' import { getDate, setDate } from '../../slices/date' +import { navigate } from '../../slices/navigation' import { LocalDate } from 'js-joda' import Header from '../header' @@ -41,11 +42,6 @@ class CycleDayOverView extends Component { this.updateCycleDay(nextDate) } - navigate(symptom) { - const { cycleDay } = this.state - this.props.navigate(symptom, cycleDay) - } - render() { const { cycleDay } = this.state const { date } = this.props @@ -89,7 +85,7 @@ class CycleDayOverView extends Component { key={symptom} symptom={symptom} symptomData={symptomData} - onPress={() => this.navigate(symptomEditView, symptomData)} + onPress={() => this.props.navigate(symptomEditView)} disabled={dateInFuture} />) }) @@ -115,6 +111,7 @@ const mapStateToProps = (state) => { const mapDispatchToProps = (dispatch) => { return({ setDate: (date) => dispatch(setDate(date)), + navigate: (page) => dispatch(navigate(page)), }) } diff --git a/components/home.js b/components/home.js index 25c46b8..a0c4e27 100644 --- a/components/home.js +++ b/components/home.js @@ -3,7 +3,7 @@ import React, { Component } from 'react' import { ScrollView, View } from 'react-native' import { connect } from 'react-redux' -import { setDate } from '../slices/date' +import { navigate } from '../slices/navigation' import DripHomeIcon from '../assets/drip-home-icons' import { @@ -17,7 +17,6 @@ import styles, { cycleDayColor, periodColor, secondaryColor } from '../styles' import AppText from './app-text' import Button from './button' import { formatDateForShortText } from './helpers/format-date' -import { getCycleDay } from '../db' const IconText = ({ children, wrapperStyles }) => { return ( @@ -71,26 +70,20 @@ class Home extends Component { } } - setTodayDate = () => { - this.props.setDate(this.todayDateString) - } - navigateToCycleDayView = () => { - this.setTodayDate() this.props.navigate('CycleDay') } navigateToBleedingEditView = () => { - this.setTodayDate() - this.props.navigate( - 'BleedingEditView', - getCycleDay(this.todayDateString) - ) + this.props.navigate('BleedingEditView') + } + + navigateToChart = () => { + this.props.navigate('Chart') } render() { const { cycleDayNumber, phase, status } = this.state - const { navigate } = this.props const cycleDayMoreText = cycleDayNumber ? labels.cycleDayKnown(cycleDayNumber) : labels.cycleDayNotEnoughInfo @@ -136,7 +129,7 @@ class Home extends Component { navigate('Chart') } + onPress={this.navigateToChart} buttonColor={ secondaryColor } buttonLabel={ labels.checkFertility } > @@ -164,7 +157,7 @@ class Home extends Component { const mapDispatchToProps = (dispatch) => { return({ - setDate: (date) => dispatch(setDate(date)), + navigate: (page) => dispatch(navigate(page)) }) } diff --git a/components/menu.js b/components/menu.js deleted file mode 100644 index 3664e15..0000000 --- a/components/menu.js +++ /dev/null @@ -1,87 +0,0 @@ -import React from 'react' -import { - View, - Text, - TouchableOpacity -} from 'react-native' - -import settingsViews from './settings' - -import { menuTitles } from '../i18n/en/labels' - -import styles, { iconStyles, secondaryColor } from '../styles' -import Icon from 'react-native-vector-icons/MaterialCommunityIcons' - -const menuTitlesLowerCase = Object.keys(menuTitles).reduce((acc, curr) => { - acc[curr] = menuTitles[curr].toLowerCase() - return acc -}, {}) - -const menuItems = [ - { - labelKey: 'Home', - icon: 'home', - component: 'Home', - }, - { - labelKey: 'Calendar', - icon: 'calendar-range', - component: 'Calendar', - }, - { - labelKey: 'Chart', - icon: 'chart-line', - component: 'Chart', - }, - { - labelKey: 'Stats', - icon: 'chart-pie', - component: 'Stats', - }, - { - labelKey: 'Settings', - icon: 'settings', - component: 'SettingsMenu', - children: Object.keys(settingsViews), - } -] - -const MenuItem = ({ icon, labelKey, active, onPress }) => { - const styleActive = active ? { color: secondaryColor } : null - return ( - - - - {menuTitlesLowerCase[labelKey]} - - - ) -} - -const Menu = ({ currentPage, navigate }) => { - return ( - - { menuItems.map(({ icon, labelKey, component, children }) => { - const isActive = (component === currentPage) || - (children && children.indexOf(currentPage) !== -1) - return ( - navigate(component)} - /> - )} - )} - - ) -} - -export default Menu \ No newline at end of file diff --git a/components/menu/index.js b/components/menu/index.js new file mode 100644 index 0000000..75dd335 --- /dev/null +++ b/components/menu/index.js @@ -0,0 +1,54 @@ +import React from 'react' +import { View } from 'react-native' +import PropTypes from 'prop-types' + +import MenuItem from './menu-item' + +import { connect } from 'react-redux' +import { getNavigation, navigate } from '../../slices/navigation' + +import { menuItems } from './menu-config' + +import styles from '../../styles' + +const Menu = ({ navigation, navigate }) => { + return ( + + { menuItems.map(({ icon, labelKey, component, children }) => { + const isActive = (component === navigation.current) || + (children && children.indexOf(navigation.current) !== -1) + return ( + navigate(component)} + /> + )} + )} + + ) +} + +Menu.propTypes = { + navigation: PropTypes.object, + navigate: PropTypes.func, +} + +const mapStateToProps = (state) => { + return({ + navigation: getNavigation(state), + }) +} + +const mapDispatchToProps = (dispatch) => { + return({ + navigate: (page) => dispatch(navigate(page)), + }) +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(Menu) \ No newline at end of file diff --git a/components/menu/menu-config.js b/components/menu/menu-config.js new file mode 100644 index 0000000..e02dfd8 --- /dev/null +++ b/components/menu/menu-config.js @@ -0,0 +1,30 @@ +import settingsViews from '../settings' + +export const menuItems = [ + { + labelKey: 'Home', + icon: 'home', + component: 'Home', + }, + { + labelKey: 'Calendar', + icon: 'calendar-range', + component: 'Calendar', + }, + { + labelKey: 'Chart', + icon: 'chart-line', + component: 'Chart', + }, + { + labelKey: 'Stats', + icon: 'chart-pie', + component: 'Stats', + }, + { + labelKey: 'Settings', + icon: 'settings', + component: 'SettingsMenu', + children: Object.keys(settingsViews), + } +] \ No newline at end of file diff --git a/components/menu/menu-item.js b/components/menu/menu-item.js new file mode 100644 index 0000000..083dd23 --- /dev/null +++ b/components/menu/menu-item.js @@ -0,0 +1,32 @@ +import React from 'react' +import { Text, TouchableOpacity } from 'react-native' + +import styles, { iconStyles, secondaryColor } from '../../styles' +import Icon from 'react-native-vector-icons/MaterialCommunityIcons' + +import { menuTitles } from '../../i18n/en/labels' + +const menuTitlesLowerCase = Object.keys(menuTitles).reduce((acc, curr) => { + acc[curr] = menuTitles[curr].toLowerCase() + return acc +}, {}) + +const MenuItem = ({ icon, labelKey, active, onPress }) => { + const styleActive = active ? { color: secondaryColor } : null + return ( + + + + {menuTitlesLowerCase[labelKey]} + + + ) +} + +export default MenuItem \ No newline at end of file diff --git a/components/pages.js b/components/pages.js new file mode 100644 index 0000000..533f77b --- /dev/null +++ b/components/pages.js @@ -0,0 +1,25 @@ +import Home from './home' +import Calendar from './calendar' +import CycleDay from './cycle-day/cycle-day-overview' +import symptomViews from './cycle-day/symptoms' +import Chart from './chart/chart' +import SettingsMenu from './settings/settings-menu' +import settingsViews from './settings' +import Stats from './stats' + +export const pagesList = { + Home, + Calendar, + CycleDay, + Chart, + SettingsMenu, + ...settingsViews, + Stats, + ...symptomViews +} + +export const isSymptomView = + (page) => Object.keys(symptomViews).includes(page) + +export const isSettingsView = + (page) => Object.keys(settingsViews).includes(page) \ No newline at end of file diff --git a/components/settings/settings-menu.js b/components/settings/settings-menu.js index ad4915a..a622a1d 100644 --- a/components/settings/settings-menu.js +++ b/components/settings/settings-menu.js @@ -1,10 +1,13 @@ import React from 'react' -import { - TouchableOpacity, - ScrollView, -} from 'react-native' +import { TouchableOpacity, ScrollView } from 'react-native' +import { connect } from 'react-redux' + +import { navigate } from '../../slices/navigation' + import styles from '../../styles/index' + import settingsLabels from '../../i18n/en/settings' + import AppText from '../app-text' const labels = settingsLabels.menuTitles @@ -18,7 +21,7 @@ const menu = [ {title: labels.license, component: 'License'} ] -export default function SettingsMenu(props) { +const SettingsMenu = (props) => { return ( { menu.map(menuItem)} @@ -36,4 +39,15 @@ export default function SettingsMenu(props) { ) } -} \ No newline at end of file +} + +const mapDispatchToProps = (dispatch) => { + return({ + navigate: (page) => dispatch(navigate(page)), + }) +} + +export default connect( + null, + mapDispatchToProps +)(SettingsMenu) \ No newline at end of file diff --git a/slices/navigation.js b/slices/navigation.js new file mode 100644 index 0000000..5de20f1 --- /dev/null +++ b/slices/navigation.js @@ -0,0 +1,26 @@ +import { createSlice } from 'redux-starter-kit' + +const navigationSlice = createSlice({ + slice: 'navigation', + initialState: { + current: 'Home', + prev: null, + }, + reducers: { + navigate: (state, action) => { + return { + current: action.payload, + prev: state.current + } + } + } +}) + +// Extract the action creators object and the reducer +const { actions, reducer, selectors } = navigationSlice +// Extract and export each action creator by name +export const { navigate } = actions + +export const { getNavigation } = selectors + +export default reducer diff --git a/store.js b/store.js index 7aa713d..7887652 100644 --- a/store.js +++ b/store.js @@ -1,9 +1,11 @@ import { combineReducers, createStore } from "redux" import date from "./slices/date" +import navigation from "./slices/navigation" const reducer = combineReducers({ - date + date, + navigation }) const store = createStore(reducer)