diff --git a/components/app.js b/components/app.js
index 1d0151d..a351513 100644
--- a/components/app.js
+++ b/components/app.js
@@ -1,140 +1,90 @@
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, goBack } 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 { viewsList } from './views'
+import { isSymptomView, isSettingsView } from './pages'
+
+import { headerTitles } from '../i18n/en/labels'
import setupNotifications from '../lib/notifications'
-import { closeDb } from '../db'
-
-// design wants everyhting lowercased, but we don't
-// have CSS pseudo properties
-const headerTitlesLowerCase = Object.keys(headerTitles).reduce((acc, curr) => {
- acc[curr] = headerTitles[curr].toLowerCase()
- return acc
-}, {})
-
-const HOME_PAGE = 'Home'
-const CYCLE_DAY_PAGE = 'CycleDay'
-const SETTINGS_MENU_PAGE = 'SettingsMenu'
+import { 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',
+ props.goBack
+ )
+
+ 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 })
- }
+ render() {
+ const { date, navigation, goBack } = this.props
+ const { currentPage } = navigation
- handleBackButtonPress = () => {
- const { currentPage } = this.state
- if (currentPage === HOME_PAGE) {
- closeDb()
+ if (!currentPage) {
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)
+
+ const { cycleDay } = this.state
+
+ const Page = viewsList[currentPage]
+ const title = headerTitles[currentPage]
+
+ const isSymptomEditView = isSymptomView(currentPage)
+ const isSettingsSubView = isSettingsView(currentPage)
+ const isCycleDayView = currentPage === 'CycleDay'
+
+ const headerProps = {
+ title,
+ handleBack: isSettingsSubView ? goBack : null,
}
- 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 pageProps = {
+ cycleDay,
+ date,
+ handleBackButtonPress: goBack,
}
- const Page = pages[currentPage]
- const title = headerTitlesLowerCase[currentPage]
-
- const hasDefaultHeader =
- !this.isSymptomView() &&
- currentPage !== CYCLE_DAY_PAGE
-
- const isSettingsSubView = this.isSettingsView()
return (
-
- { hasDefaultHeader &&
-
+ {
+ !isSymptomEditView &&
+ !isCycleDayView &&
+
}
-
+
- {!this.isSymptomView() &&
-
- }
+ { !isSymptomEditView && }
)
}
@@ -142,11 +92,20 @@ 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)),
+ goBack: () => dispatch(goBack()),
})
}
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 5a39306..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
@@ -66,8 +62,7 @@ class CycleDayOverView extends Component {
const { getCycleDayNumber } = cycleModule()
const cycleDayNumber = getCycleDayNumber(date)
- const headerSubtitle =
- cycleDayNumber && `Cycle day ${cycleDayNumber}`.toLowerCase()
+ const headerSubtitle = cycleDayNumber && `Cycle day ${cycleDayNumber}`
return (
@@ -90,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}
/>)
})
@@ -116,6 +111,7 @@ const mapStateToProps = (state) => {
const mapDispatchToProps = (dispatch) => {
return({
setDate: (date) => dispatch(setDate(date)),
+ navigate: (page) => dispatch(navigate(page)),
})
}
diff --git a/components/cycle-day/symptoms/symptom-view.js b/components/cycle-day/symptoms/symptom-view.js
index 801fbdb..6b0398e 100644
--- a/components/cycle-day/symptoms/symptom-view.js
+++ b/components/cycle-day/symptoms/symptom-view.js
@@ -106,7 +106,7 @@ class SymptomView extends Component {
return (
- {title}
+ { // design wants everyhting lowercased, but we don't
+ // have CSS pseudo properties
+ title.toLowerCase()}
{ subtitle &&
- {subtitle}
+ {subtitle.toLowerCase()}
}
)
}
- return {title}
+ return (
+
+ {title.toLowerCase()}
+
+ )
}
Title.propTypes = {
diff --git a/components/helpers/format-date.js b/components/helpers/format-date.js
index cf63f70..d353dc6 100644
--- a/components/helpers/format-date.js
+++ b/components/helpers/format-date.js
@@ -4,8 +4,9 @@ import moment from 'moment'
export default function (date) {
const today = LocalDate.now()
const dateToDisplay = LocalDate.parse(date)
- const formattedDate = today.equals(dateToDisplay) ? 'today' : moment(date).format('MMMM Do YYYY')
- return formattedDate.toLowerCase()
+ return today.equals(dateToDisplay) ?
+ 'today' :
+ moment(date).format('MMMM Do YYYY')
}
export function formatDateForShortText (date) {
diff --git a/components/home.js b/components/home.js
index da0fdc8..497007c 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 (
-
- )
-}
-
-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..969082e
--- /dev/null
+++ b/components/menu/index.js
@@ -0,0 +1,55 @@
+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 { pages } from '../pages'
+
+import styles from '../../styles'
+
+const Menu = ({ navigate, navigation }) => {
+ const menuItems = pages.filter(page => page.isInMenu)
+ return (
+
+ { menuItems.map(({ icon, label, component, children }) => {
+ const isActive = (component === navigation.currentPage) ||
+ (children && children.indexOf(navigation.currentPage) !== -1)
+ return (
+
+ )
+}
+
+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-item.js b/components/menu/menu-item.js
new file mode 100644
index 0000000..cf59c0e
--- /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, label, active, onPress }) => {
+ const styleActive = active ? { color: secondaryColor } : null
+ return (
+
+
+
+ {menuTitlesLowerCase[label]}
+
+
+ )
+}
+
+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..943a6a9
--- /dev/null
+++ b/components/pages.js
@@ -0,0 +1,89 @@
+import symptomViews from './cycle-day/symptoms'
+import settingsViews from './settings'
+
+import settingsLabels from '../i18n/en/settings'
+const labels = settingsLabels.menuTitles
+
+const symptomsPages = Object.keys(symptomViews).map(symptomView => ({
+ component: symptomView,
+ parent: 'CycleDay',
+}))
+
+export const isSymptomView =
+ (page) => Object.keys(symptomViews).includes(page)
+
+export const isSettingsView =
+ (page) => Object.keys(settingsViews).includes(page)
+
+export const pages = [
+ {
+ component: 'Home',
+ icon: 'home',
+ isInMenu: true,
+ label: 'Home',
+ },
+ {
+ component: 'Calendar',
+ icon: 'calendar-range',
+ isInMenu: true,
+ label: 'Calendar',
+ parent: 'Home',
+ },
+ {
+ component: 'Chart',
+ icon: 'chart-line',
+ isInMenu: true,
+ label: 'Chart',
+ parent: 'Home',
+ },
+ {
+ component: 'Stats',
+ icon: 'chart-pie',
+ isInMenu: true,
+ label: 'Stats',
+ parent: 'Home',
+ },
+ {
+ children: Object.keys(settingsViews),
+ component: 'SettingsMenu',
+ icon: 'settings',
+ isInMenu: true,
+ label: 'Settings',
+ parent: 'Home',
+ },
+ {
+ component: 'Reminders',
+ label: labels.reminders,
+ parent: 'SettingsMenu',
+ },
+ {
+ component: 'NfpSettings',
+ label: labels.nfpSettings,
+ parent: 'SettingsMenu',
+ },
+ {
+ component: 'DataManagement',
+ label: labels.dataManagement,
+ parent: 'SettingsMenu',
+ },
+ {
+ component: 'Password',
+ label: labels.password,
+ parent: 'SettingsMenu',
+ },
+ {
+ component: 'About',
+ label: labels.about,
+ parent: 'SettingsMenu',
+ },
+ {
+ component: 'License',
+ label: labels.license,
+ parent: 'SettingsMenu',
+ },
+ {
+ component: 'CycleDay',
+ parent: 'Home',
+ },
+ ...symptomsPages
+]
\ 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/components/views.js b/components/views.js
new file mode 100644
index 0000000..1cb8545
--- /dev/null
+++ b/components/views.js
@@ -0,0 +1,19 @@
+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 viewsList = {
+ Home,
+ Calendar,
+ CycleDay,
+ Chart,
+ SettingsMenu,
+ ...settingsViews,
+ Stats,
+ ...symptomViews
+}
diff --git a/slices/navigation.js b/slices/navigation.js
new file mode 100644
index 0000000..24991df
--- /dev/null
+++ b/slices/navigation.js
@@ -0,0 +1,49 @@
+import { createSlice } from 'redux-starter-kit'
+import { pages, isSymptomView } from '../components/pages'
+import { closeDb } from '../db'
+import { BackHandler } from 'react-native'
+
+const navigationSlice = createSlice({
+ slice: 'navigation',
+ initialState: {
+ currentPage: 'Home',
+ },
+ reducers: {
+ navigate: (state, action) => {
+ return {
+ currentPage: action.payload,
+ previousPage: state.currentPage,
+ }
+ },
+ goBack: ({ currentPage, previousPage }) => {
+
+ if (currentPage === 'Home') {
+ closeDb()
+ BackHandler.exitApp()
+ return { currentPage }
+ }
+
+ if (currentPage === 'CycleDay' || isSymptomView(currentPage)) {
+ if (previousPage) {
+ return {
+ currentPage: previousPage
+ }
+ }
+ }
+
+ const page = pages.find(p => p.component === currentPage)
+ return {
+ currentPage: page.parent
+ }
+ }
+ }
+})
+
+// Extract the action creators object and the reducer
+const { actions, reducer, selectors } = navigationSlice
+// Extract and export each action creator by name
+export const { navigate, goBack } = 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)