Merge branch 'navigation-state' into 'master'
Navigation state See merge request bloodyhealth/drip!247
This commit is contained in:
+66
-107
@@ -1,140 +1,90 @@
|
|||||||
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, goBack } from '../slices/navigation'
|
||||||
|
|
||||||
import Header from './header'
|
import Header from './header'
|
||||||
import Menu from './menu'
|
import Menu from './menu'
|
||||||
import Home from './home'
|
import { viewsList } from './views'
|
||||||
import Calendar from './calendar'
|
import { isSymptomView, isSettingsView } from './pages'
|
||||||
import CycleDay from './cycle-day/cycle-day-overview'
|
|
||||||
import symptomViews from './cycle-day/symptoms'
|
import { headerTitles } from '../i18n/en/labels'
|
||||||
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 setupNotifications from '../lib/notifications'
|
||||||
import { closeDb } from '../db'
|
import { getCycleDay } 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'
|
|
||||||
|
|
||||||
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',
|
||||||
|
props.goBack
|
||||||
|
)
|
||||||
|
|
||||||
|
setupNotifications(this.props.navigate)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.backHandler.remove()
|
this.backHandler.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
navigate = (pageName, cycleDay) => {
|
render() {
|
||||||
const { currentPage } = this.state
|
const { date, navigation, goBack } = this.props
|
||||||
// for the back button to work properly, we want to
|
const { currentPage } = navigation
|
||||||
// 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 = () => {
|
if (!currentPage) {
|
||||||
const { currentPage } = this.state
|
|
||||||
if (currentPage === HOME_PAGE) {
|
|
||||||
closeDb()
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (this.isSymptomView()) {
|
|
||||||
this.navigate(this.originForSymptomView)
|
const { cycleDay } = this.state
|
||||||
} else if (this.isSettingsView()) {
|
|
||||||
this.navigate(SETTINGS_MENU_PAGE)
|
const Page = viewsList[currentPage]
|
||||||
} else if (currentPage === CYCLE_DAY_PAGE) {
|
const title = headerTitles[currentPage]
|
||||||
this.navigate(this.menuOrigin)
|
|
||||||
} else {
|
const isSymptomEditView = isSymptomView(currentPage)
|
||||||
this.navigate(HOME_PAGE)
|
const isSettingsSubView = isSettingsView(currentPage)
|
||||||
}
|
const isCycleDayView = currentPage === 'CycleDay'
|
||||||
return true
|
|
||||||
|
const headerProps = {
|
||||||
|
title,
|
||||||
|
handleBack: isSettingsSubView ? goBack : null,
|
||||||
}
|
}
|
||||||
|
|
||||||
isMenuItem() {
|
const pageProps = {
|
||||||
return Object.keys(menuTitles).includes(this.state.currentPage)
|
cycleDay,
|
||||||
|
date,
|
||||||
|
handleBackButtonPress: goBack,
|
||||||
}
|
}
|
||||||
|
|
||||||
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 title = headerTitlesLowerCase[currentPage]
|
|
||||||
|
|
||||||
const hasDefaultHeader =
|
|
||||||
!this.isSymptomView() &&
|
|
||||||
currentPage !== CYCLE_DAY_PAGE
|
|
||||||
|
|
||||||
const isSettingsSubView = this.isSettingsView()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
|
{
|
||||||
{ hasDefaultHeader &&
|
!isSymptomEditView &&
|
||||||
<Header
|
!isCycleDayView &&
|
||||||
handleBack={isSettingsSubView ? this.handleBackButtonPress : null}
|
<Header { ...headerProps } />
|
||||||
title={title}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<Page
|
<Page { ...pageProps } />
|
||||||
navigate={this.navigate}
|
|
||||||
cycleDay={cycleDay}
|
|
||||||
date={this.props.date}
|
|
||||||
handleBackButtonPress={this.handleBackButtonPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!this.isSymptomView() &&
|
{ !isSymptomEditView && <Menu /> }
|
||||||
<Menu navigate={this.navigate} currentPage={currentPage} />
|
|
||||||
}
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -142,11 +92,20 @@ 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)),
|
||||||
|
goBack: () => dispatch(goBack()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
null
|
mapDispatchToProps
|
||||||
)(App)
|
)(App)
|
||||||
@@ -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)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 +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
|
||||||
@@ -66,8 +62,7 @@ class CycleDayOverView extends Component {
|
|||||||
|
|
||||||
const { getCycleDayNumber } = cycleModule()
|
const { getCycleDayNumber } = cycleModule()
|
||||||
const cycleDayNumber = getCycleDayNumber(date)
|
const cycleDayNumber = getCycleDayNumber(date)
|
||||||
const headerSubtitle =
|
const headerSubtitle = cycleDayNumber && `Cycle day ${cycleDayNumber}`
|
||||||
cycleDayNumber && `Cycle day ${cycleDayNumber}`.toLowerCase()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
@@ -90,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}
|
||||||
/>)
|
/>)
|
||||||
})
|
})
|
||||||
@@ -116,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)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class SymptomView extends Component {
|
|||||||
return (
|
return (
|
||||||
<View style={{flex: 1}}>
|
<View style={{flex: 1}}>
|
||||||
<Header
|
<Header
|
||||||
title={headerTitles[symptom].toLowerCase()}
|
title={headerTitles[symptom]}
|
||||||
subtitle={formatDate(this.date)}
|
subtitle={formatDate(this.date)}
|
||||||
handleBack={this.props.handleBackButtonPress}
|
handleBack={this.props.handleBackButtonPress}
|
||||||
handleDelete={
|
handleDelete={
|
||||||
|
|||||||
@@ -10,18 +10,24 @@ export default function Title({ title, subtitle }) {
|
|||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<Text style={styles.dateHeader} testID='headerTitle'>
|
<Text style={styles.dateHeader} testID='headerTitle'>
|
||||||
{title}
|
{ // design wants everyhting lowercased, but we don't
|
||||||
|
// have CSS pseudo properties
|
||||||
|
title.toLowerCase()}
|
||||||
</Text>
|
</Text>
|
||||||
{ subtitle &&
|
{ subtitle &&
|
||||||
<Text style={styles.cycleDayNumber} testID='headerSubtitle'>
|
<Text style={styles.cycleDayNumber} testID='headerSubtitle'>
|
||||||
{subtitle}
|
{subtitle.toLowerCase()}
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Text testID='headerTitle' style={styles.headerText}>{title}</Text>
|
return (
|
||||||
|
<Text testID='headerTitle' style={styles.headerText}>
|
||||||
|
{title.toLowerCase()}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Title.propTypes = {
|
Title.propTypes = {
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import moment from 'moment'
|
|||||||
export default function (date) {
|
export default function (date) {
|
||||||
const today = LocalDate.now()
|
const today = LocalDate.now()
|
||||||
const dateToDisplay = LocalDate.parse(date)
|
const dateToDisplay = LocalDate.parse(date)
|
||||||
const formattedDate = today.equals(dateToDisplay) ? 'today' : moment(date).format('MMMM Do YYYY')
|
return today.equals(dateToDisplay) ?
|
||||||
return formattedDate.toLowerCase()
|
'today' :
|
||||||
|
moment(date).format('MMMM Do YYYY')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatDateForShortText (date) {
|
export function formatDateForShortText (date) {
|
||||||
|
|||||||
+8
-15
@@ -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))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,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 (
|
||||||
|
<View style={styles.menu}>
|
||||||
|
{ menuItems.map(({ icon, label, component, children }) => {
|
||||||
|
const isActive = (component === navigation.currentPage) ||
|
||||||
|
(children && children.indexOf(navigation.currentPage) !== -1)
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
key={label}
|
||||||
|
label={label}
|
||||||
|
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,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 (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.menuItem}
|
||||||
|
onPress={onPress}
|
||||||
|
>
|
||||||
|
<Icon name={icon} {...iconStyles.menuIcon} {...styleActive} />
|
||||||
|
<Text
|
||||||
|
testID={active ? 'activeMenuItem' : `menuItem${label}`}
|
||||||
|
style={[styles.menuText, styleActive]}
|
||||||
|
>
|
||||||
|
{menuTitlesLowerCase[label]}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MenuItem
|
||||||
@@ -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
|
||||||
|
]
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user