Moves navigation to the state

This commit is contained in:
Sofiya Tepikin
2019-09-15 18:59:11 +02:00
parent f444de0b87
commit e479b93638
13 changed files with 254 additions and 202 deletions
+49 -87
View File
@@ -1,117 +1,74 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { View, BackHandler } from 'react-native' import { View, BackHandler } from 'react-native'
import PropTypes from 'prop-types'
import { LocalDate } from 'js-joda'
import { connect } from 'react-redux' 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 Header from './header'
import Menu from './menu' import Menu from './menu'
import Home from './home' import { pagesList, isSymptomView, isSettingsView } from './pages'
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'
const HOME_PAGE = 'Home' import { headerTitles } from '../i18n/en/labels'
const CYCLE_DAY_PAGE = 'CycleDay' import setupNotifications from '../lib/notifications'
const SETTINGS_MENU_PAGE = 'SettingsMenu' import { closeDb, getCycleDay } from '../db'
class App extends Component { class App extends Component {
static propTypes = {
date: PropTypes.string,
navigation: PropTypes.object.isRequired,
}
constructor(props) { constructor(props) {
super(props) super(props)
this.todayDateString = LocalDate.now().toString()
props.setDate(this.todayDateString)
this.state = { this.state = {
currentPage: HOME_PAGE, cycleDay: getCycleDay(this.todayDateString),
cycleDay: {},
} }
this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.handleBackButtonPress)
setupNotifications(this.navigate) this.backHandler = BackHandler.addEventListener(
'hardwareBackPress',
this.handleBackButtonPress
)
setupNotifications(this.props.navigate)
} }
componentWillUnmount() { componentWillUnmount() {
this.backHandler.remove() 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 = () => { handleBackButtonPress = () => {
const { currentPage } = this.state const { current, prev } = this.props.navigation
if (currentPage === HOME_PAGE) { if (current === 'Home') {
closeDb() closeDb()
return false return false
} }
if (this.isSymptomView()) { this.props.navigate(prev)
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)
}
return true 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() { render() {
const { currentPage, cycleDay } = this.state const { cycleDay } = this.state
const pages = { const currentPage = this.props.navigation.current
Home,
Calendar, const Page = pagesList[currentPage]
CycleDay,
Chart,
SettingsMenu,
...settingsViews,
Stats,
...symptomViews
}
const Page = pages[currentPage]
const title = headerTitles[currentPage] const title = headerTitles[currentPage]
const hasDefaultHeader = const isSymptomEditView = isSymptomView(currentPage)
!this.isSymptomView() && const isSettingsSubView = isSettingsView(currentPage)
currentPage !== CYCLE_DAY_PAGE const isCycleDayView = currentPage === 'CycleDay'
const isSettingsSubView = this.isSettingsView()
return ( return (
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
{ !isSymptomEditView && !isCycleDayView &&
{ hasDefaultHeader &&
<Header <Header
handleBack={isSettingsSubView ? this.handleBackButtonPress : null} handleBack={isSettingsSubView ? this.handleBackButtonPress : null}
title={title} title={title}
@@ -119,15 +76,12 @@ class App extends Component {
} }
<Page <Page
navigate={this.navigate}
cycleDay={cycleDay} cycleDay={cycleDay}
date={this.props.date} date={this.props.date}
handleBackButtonPress={this.handleBackButtonPress} handleBackButtonPress={this.handleBackButtonPress}
/> />
{!this.isSymptomView() && { !isSymptomEditView && <Menu /> }
<Menu navigate={this.navigate} currentPage={currentPage} />
}
</View> </View>
) )
} }
@@ -135,11 +89,19 @@ class App extends Component {
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
return({ 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( export default connect(
mapStateToProps, mapStateToProps,
null mapDispatchToProps
)(App) )(App)
+2
View File
@@ -3,6 +3,7 @@ import { CalendarList } from 'react-native-calendars'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { setDate } from '../slices/date' import { setDate } from '../slices/date'
import { navigate } from '../slices/navigation'
import { LocalDate } from 'js-joda' import { LocalDate } from 'js-joda'
import { getBleedingDaysSortedByDate } from '../db' import { getBleedingDaysSortedByDate } from '../db'
@@ -68,6 +69,7 @@ class CalendarView extends Component {
const mapDispatchToProps = (dispatch) => { const mapDispatchToProps = (dispatch) => {
return({ return({
setDate: (date) => dispatch(setDate(date)), setDate: (date) => dispatch(setDate(date)),
navigate: (page) => dispatch(navigate(page)),
}) })
} }
+2
View File
@@ -3,6 +3,7 @@ import { TouchableOpacity } from 'react-native'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { setDate } from '../../slices/date' import { setDate } from '../../slices/date'
import { navigate } from '../../slices/navigation'
import { getCycleDay } from '../../db' import { getCycleDay } from '../../db'
@@ -109,6 +110,7 @@ class DayColumn extends Component {
const mapDispatchToProps = (dispatch) => { const mapDispatchToProps = (dispatch) => {
return({ return({
setDate: (date) => dispatch(setDate(date)), setDate: (date) => dispatch(setDate(date)),
navigate: (page) => dispatch(navigate(page)),
}) })
} }
+3 -6
View File
@@ -3,6 +3,7 @@ import { ScrollView, View } from 'react-native'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { getDate, setDate } from '../../slices/date' import { getDate, setDate } from '../../slices/date'
import { navigate } from '../../slices/navigation'
import { LocalDate } from 'js-joda' import { LocalDate } from 'js-joda'
import Header from '../header' import Header from '../header'
@@ -41,11 +42,6 @@ class CycleDayOverView extends Component {
this.updateCycleDay(nextDate) this.updateCycleDay(nextDate)
} }
navigate(symptom) {
const { cycleDay } = this.state
this.props.navigate(symptom, cycleDay)
}
render() { render() {
const { cycleDay } = this.state const { cycleDay } = this.state
const { date } = this.props const { date } = this.props
@@ -89,7 +85,7 @@ class CycleDayOverView extends Component {
key={symptom} key={symptom}
symptom={symptom} symptom={symptom}
symptomData={symptomData} symptomData={symptomData}
onPress={() => this.navigate(symptomEditView, symptomData)} onPress={() => this.props.navigate(symptomEditView)}
disabled={dateInFuture} disabled={dateInFuture}
/>) />)
}) })
@@ -115,6 +111,7 @@ const mapStateToProps = (state) => {
const mapDispatchToProps = (dispatch) => { const mapDispatchToProps = (dispatch) => {
return({ return({
setDate: (date) => dispatch(setDate(date)), setDate: (date) => dispatch(setDate(date)),
navigate: (page) => dispatch(navigate(page)),
}) })
} }
+8 -15
View File
@@ -3,7 +3,7 @@ import React, { Component } from 'react'
import { ScrollView, View } from 'react-native' import { ScrollView, View } from 'react-native'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { setDate } from '../slices/date' import { navigate } from '../slices/navigation'
import DripHomeIcon from '../assets/drip-home-icons' import DripHomeIcon from '../assets/drip-home-icons'
import { import {
@@ -17,7 +17,6 @@ import styles, { cycleDayColor, periodColor, secondaryColor } from '../styles'
import AppText from './app-text' import AppText from './app-text'
import Button from './button' import Button from './button'
import { formatDateForShortText } from './helpers/format-date' import { formatDateForShortText } from './helpers/format-date'
import { getCycleDay } from '../db'
const IconText = ({ children, wrapperStyles }) => { const IconText = ({ children, wrapperStyles }) => {
return ( return (
@@ -71,26 +70,20 @@ class Home extends Component {
} }
} }
setTodayDate = () => {
this.props.setDate(this.todayDateString)
}
navigateToCycleDayView = () => { navigateToCycleDayView = () => {
this.setTodayDate()
this.props.navigate('CycleDay') this.props.navigate('CycleDay')
} }
navigateToBleedingEditView = () => { navigateToBleedingEditView = () => {
this.setTodayDate() this.props.navigate('BleedingEditView')
this.props.navigate( }
'BleedingEditView',
getCycleDay(this.todayDateString) navigateToChart = () => {
) this.props.navigate('Chart')
} }
render() { render() {
const { cycleDayNumber, phase, status } = this.state const { cycleDayNumber, phase, status } = this.state
const { navigate } = this.props
const cycleDayMoreText = cycleDayNumber ? const cycleDayMoreText = cycleDayNumber ?
labels.cycleDayKnown(cycleDayNumber) : labels.cycleDayKnown(cycleDayNumber) :
labels.cycleDayNotEnoughInfo labels.cycleDayNotEnoughInfo
@@ -136,7 +129,7 @@ class Home extends Component {
</HomeElement> </HomeElement>
<HomeElement <HomeElement
onPress={ () => navigate('Chart') } onPress={this.navigateToChart}
buttonColor={ secondaryColor } buttonColor={ secondaryColor }
buttonLabel={ labels.checkFertility } buttonLabel={ labels.checkFertility }
> >
@@ -164,7 +157,7 @@ class Home extends Component {
const mapDispatchToProps = (dispatch) => { const mapDispatchToProps = (dispatch) => {
return({ return({
setDate: (date) => dispatch(setDate(date)), navigate: (page) => dispatch(navigate(page))
}) })
} }
-87
View File
@@ -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 (
<TouchableOpacity
style={styles.menuItem}
onPress={onPress}
>
<Icon name={icon} {...iconStyles.menuIcon} {...styleActive} />
<Text
testID={active ? 'activeMenuItem' : `menuItem${labelKey}`}
style={[styles.menuText, styleActive]}
>
{menuTitlesLowerCase[labelKey]}
</Text>
</TouchableOpacity>
)
}
const Menu = ({ currentPage, navigate }) => {
return (
<View style={styles.menu}>
{ menuItems.map(({ icon, labelKey, component, children }) => {
const isActive = (component === currentPage) ||
(children && children.indexOf(currentPage) !== -1)
return (
<MenuItem
key={labelKey}
labelKey={labelKey}
icon={icon}
active={isActive}
onPress={() => navigate(component)}
/>
)}
)}
</View >
)
}
export default Menu
+54
View File
@@ -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 (
<View style={styles.menu}>
{ menuItems.map(({ icon, labelKey, component, children }) => {
const isActive = (component === navigation.current) ||
(children && children.indexOf(navigation.current) !== -1)
return (
<MenuItem
key={labelKey}
labelKey={labelKey}
icon={icon}
active={isActive}
onPress={() => navigate(component)}
/>
)}
)}
</View >
)
}
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)
+30
View File
@@ -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),
}
]
+32
View File
@@ -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 (
<TouchableOpacity
style={styles.menuItem}
onPress={onPress}
>
<Icon name={icon} {...iconStyles.menuIcon} {...styleActive} />
<Text
testID={active ? 'activeMenuItem' : `menuItem${labelKey}`}
style={[styles.menuText, styleActive]}
>
{menuTitlesLowerCase[labelKey]}
</Text>
</TouchableOpacity>
)
}
export default MenuItem
+25
View File
@@ -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)
+19 -5
View File
@@ -1,10 +1,13 @@
import React from 'react' import React from 'react'
import { import { TouchableOpacity, ScrollView } from 'react-native'
TouchableOpacity, import { connect } from 'react-redux'
ScrollView,
} from 'react-native' import { navigate } from '../../slices/navigation'
import styles from '../../styles/index' import styles from '../../styles/index'
import settingsLabels from '../../i18n/en/settings' import settingsLabels from '../../i18n/en/settings'
import AppText from '../app-text' import AppText from '../app-text'
const labels = settingsLabels.menuTitles const labels = settingsLabels.menuTitles
@@ -18,7 +21,7 @@ const menu = [
{title: labels.license, component: 'License'} {title: labels.license, component: 'License'}
] ]
export default function SettingsMenu(props) { const SettingsMenu = (props) => {
return ( return (
<ScrollView> <ScrollView>
{ menu.map(menuItem)} { menu.map(menuItem)}
@@ -37,3 +40,14 @@ export default function SettingsMenu(props) {
) )
} }
} }
const mapDispatchToProps = (dispatch) => {
return({
navigate: (page) => dispatch(navigate(page)),
})
}
export default connect(
null,
mapDispatchToProps
)(SettingsMenu)
+26
View File
@@ -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
+3 -1
View File
@@ -1,9 +1,11 @@
import { combineReducers, createStore } from "redux" import { combineReducers, createStore } from "redux"
import date from "./slices/date" import date from "./slices/date"
import navigation from "./slices/navigation"
const reducer = combineReducers({ const reducer = combineReducers({
date date,
navigation
}) })
const store = createStore(reducer) const store = createStore(reducer)