Moves navigation to the state
This commit is contained in:
+49
-87
@@ -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 (
|
||||
<View style={{ flex: 1 }}>
|
||||
|
||||
{ hasDefaultHeader &&
|
||||
{ !isSymptomEditView && !isCycleDayView &&
|
||||
<Header
|
||||
handleBack={isSettingsSubView ? this.handleBackButtonPress : null}
|
||||
title={title}
|
||||
@@ -119,15 +76,12 @@ class App extends Component {
|
||||
}
|
||||
|
||||
<Page
|
||||
navigate={this.navigate}
|
||||
cycleDay={cycleDay}
|
||||
date={this.props.date}
|
||||
handleBackButtonPress={this.handleBackButtonPress}
|
||||
/>
|
||||
|
||||
{!this.isSymptomView() &&
|
||||
<Menu navigate={this.navigate} currentPage={currentPage} />
|
||||
}
|
||||
{ !isSymptomEditView && <Menu /> }
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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)),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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)),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+8
-15
@@ -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 {
|
||||
</HomeElement>
|
||||
|
||||
<HomeElement
|
||||
onPress={ () => 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))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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),
|
||||
}
|
||||
]
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 (
|
||||
<ScrollView>
|
||||
{ menu.map(menuItem)}
|
||||
@@ -36,4 +39,15 @@ export default function SettingsMenu(props) {
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return({
|
||||
navigate: (page) => dispatch(navigate(page)),
|
||||
})
|
||||
}
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(SettingsMenu)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user