Feature: add stats explainer take2

This commit is contained in:
Maria Zadnepryanets
2022-09-26 17:19:23 +00:00
parent db1388c3c4
commit 7b9293f7a2
8 changed files with 224 additions and 181 deletions
+1 -1
View File
@@ -71,7 +71,7 @@ const Home = ({ navigate, setDate }) => {
<Button isCTA isSmall={false} onPress={navigateToCycleDayView}>
{t('labels.home.addDataForToday')}
</Button>
{phase && <Footnote>{statusText}</Footnote>}
{phase && <Footnote colorLabel="greyLight">{statusText}</Footnote>}
</ScrollView>
)
}
+12 -5
View File
@@ -7,13 +7,16 @@ import Asterisk from '../common/Asterisk'
import { Colors, Spacing } from '../../styles'
const Footnote = ({ children }) => {
const Footnote = ({ children, colorLabel }) => {
if (!children) return false
return (
<View style={styles.container}>
<Asterisk />
<AppText linkStyle={styles.link} style={styles.text}>
<AppText
linkStyle={styles.link}
style={{ ...styles.text, color: Colors[colorLabel] }}
>
{children}
</AppText>
</View>
@@ -22,6 +25,11 @@ const Footnote = ({ children }) => {
Footnote.propTypes = {
children: PropTypes.node,
colorLabel: PropTypes.string,
}
Footnote.defaultProps = {
colorLabel: 'greyDark',
}
const styles = StyleSheet.create({
@@ -29,14 +37,13 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignContent: 'flex-start',
marginBottom: Spacing.tiny,
marginTop: Spacing.small,
marginTop: Spacing.base,
},
link: {
color: 'white',
},
text: {
color: Colors.greyLight,
paddingLeft: Spacing.base,
paddingLeft: Spacing.small,
},
})
+48 -13
View File
@@ -1,20 +1,33 @@
import React from 'react'
import { Modal, StyleSheet, TouchableOpacity } from 'react-native'
import {
Dimensions,
Modal,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native'
import PropTypes from 'prop-types'
const AppModal = ({ children, onClose }) => {
return (
<Modal
animationType="fade"
onRequestClose={onClose}
transparent={true}
visible={true}
>
<TouchableOpacity onPress={onClose} style={styles.blackBackground} />
import CloseIcon from './close-icon'
import { Sizes, Spacing } from '../../styles'
const AppModal = ({ children, onClose }) => (
<Modal
animationType="fade"
onRequestClose={onClose}
transparent={true}
visible={true}
>
<TouchableOpacity onPress={onClose} style={styles.blackBackground} />
<View style={styles.modalWindow}>
<View style={styles.headerContainer}>
<CloseIcon onClose={onClose} />
</View>
{children}
</Modal>
)
}
</View>
</Modal>
)
AppModal.propTypes = {
children: PropTypes.node,
@@ -27,6 +40,28 @@ const styles = StyleSheet.create({
flex: 1,
opacity: 0.5,
},
headerContainer: {
flexDirection: 'row',
justifyContent: 'flex-end',
paddingTop: Spacing.base,
paddingHorizontal: Spacing.base,
position: 'absolute',
width: '100%',
zIndex: 3, // works on ios
elevation: 3, // works on android
},
modalWindow: {
alignSelf: 'center',
backgroundColor: 'white',
borderRadius: 10,
marginTop: Sizes.huge * 2,
paddingVertical: Spacing.large * 2,
position: 'absolute',
maxHeight: Dimensions.get('window').height * 0.7,
zIndex: 2, // works on ios
elevation: 2, // works on android
minWidth: '80%',
},
})
export default AppModal
+87 -113
View File
@@ -1,13 +1,12 @@
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import { Dimensions, ScrollView, StyleSheet, View } from 'react-native'
import { ScrollView, StyleSheet, View } from 'react-native'
import AppModal from '../common/app-modal'
import AppSwitch from '../common/app-switch'
import AppText from '../common/app-text'
import AppTextInput from '../common/app-text-input'
import Button from '../common/button'
import CloseIcon from '../common/close-icon'
import Segment from '../common/segment'
import SelectBoxGroup from './select-box-group'
import SelectTabGroup from './select-tab-group'
@@ -119,99 +118,94 @@ const SymptomEditView = ({ date, onClose, symptom, symptomData }) => {
return (
<AppModal onClose={onSave}>
<View style={styles.modalWindow}>
<View style={styles.headerContainer}>
<CloseIcon onClose={onSave} />
</View>
<ScrollView
contentContainerStyle={styles.modalContainer}
keyboardDismissMode="on-drag"
>
{symptom === 'temperature' && (
<Temperature
date={date}
data={data}
save={(value, field) => onSaveTemperature(value, field)}
/>
)}
{shouldShow(symptomConfig.selectTabGroups) &&
symtomPage[symptom].selectTabGroups.map((group) => {
return (
<Segment key={group.key} style={styles.segmentBorder}>
<AppText style={styles.title}>{group.title}</AppText>
<SelectTabGroup
activeButton={data[group.key]}
buttons={group.options}
onSelect={(value) => onSelectTab(group, value)}
/>
</Segment>
)
})}
{shouldShow(symptomConfig.selectBoxGroups) &&
symtomPage[symptom].selectBoxGroups.map((group) => {
const isOtherSelected =
data['other'] !== null &&
data['other'] !== false &&
Object.keys(group.options).includes('other')
<ScrollView
contentContainerStyle={styles.modalContainer}
keyboardDismissMode="on-drag"
>
{symptom === 'temperature' && (
<Temperature
date={date}
data={data}
save={(value, field) => onSaveTemperature(value, field)}
/>
)}
{shouldShow(symptomConfig.selectTabGroups) &&
symtomPage[symptom].selectTabGroups.map((group) => {
return (
<Segment key={group.key} style={styles.segmentBorder}>
<AppText style={styles.title}>{group.title}</AppText>
<SelectTabGroup
activeButton={data[group.key]}
buttons={group.options}
onSelect={(value) => onSelectTab(group, value)}
/>
</Segment>
)
})}
{shouldShow(symptomConfig.selectBoxGroups) &&
symtomPage[symptom].selectBoxGroups.map((group) => {
const isOtherSelected =
data['other'] !== null &&
data['other'] !== false &&
Object.keys(group.options).includes('other')
return (
<Segment key={group.key} style={styles.segmentBorder}>
<AppText style={styles.title}>{group.title}</AppText>
<SelectBoxGroup
labels={group.options}
onSelect={(value) => onSelectBox(value)}
optionsState={data}
return (
<Segment key={group.key} style={styles.segmentBorder}>
<AppText style={styles.title}>{group.title}</AppText>
<SelectBoxGroup
labels={group.options}
onSelect={(value) => onSelectBox(value)}
optionsState={data}
/>
{isOtherSelected && (
<AppTextInput
{...inputProps}
placeholder={sharedLabels.enter}
value={data.note}
onChangeText={(value) => onSelectBoxNote(value)}
/>
{isOtherSelected && (
<AppTextInput
{...inputProps}
placeholder={sharedLabels.enter}
value={data.note}
onChangeText={(value) => onSelectBoxNote(value)}
/>
)}
</Segment>
)
})}
{shouldShow(symptomConfig.excludeText) && (
<Segment style={styles.segmentBorder}>
<AppSwitch
onToggle={onExcludeToggle}
text={symtomPage[symptom].excludeText}
value={data.exclude}
/>
</Segment>
)}
{shouldShow(symptomConfig.note) && (
<Segment style={styles.segmentBorder}>
<AppText>{symtomPage[symptom].note}</AppText>
<AppTextInput
{...inputProps}
onChangeText={onEditNote}
placeholder={sharedLabels.enter}
testID="noteInput"
value={noteText !== null ? noteText : ''}
/>
</Segment>
)}
<View style={styles.buttonsContainer}>
<Button iconName={iconName} isSmall onPress={onPressLearnMore}>
{sharedLabels.learnMore}
</Button>
<Button isSmall onPress={onRemove}>
{sharedLabels.remove}
</Button>
<Button isCTA isSmall onPress={onSave}>
{sharedLabels.save}
</Button>
</View>
{shouldShowInfo && (
<Segment last style={styles.segmentBorder}>
<AppText>{info[symptom].text}</AppText>
</Segment>
)}
</ScrollView>
</View>
)}
</Segment>
)
})}
{shouldShow(symptomConfig.excludeText) && (
<Segment style={styles.segmentBorder}>
<AppSwitch
onToggle={onExcludeToggle}
text={symtomPage[symptom].excludeText}
value={data.exclude}
/>
</Segment>
)}
{shouldShow(symptomConfig.note) && (
<Segment style={styles.segmentBorder}>
<AppText>{symtomPage[symptom].note}</AppText>
<AppTextInput
{...inputProps}
onChangeText={onEditNote}
placeholder={sharedLabels.enter}
testID="noteInput"
value={noteText !== null ? noteText : ''}
/>
</Segment>
)}
<View style={styles.buttonsContainer}>
<Button iconName={iconName} isSmall onPress={onPressLearnMore}>
{sharedLabels.learnMore}
</Button>
<Button isSmall onPress={onRemove}>
{sharedLabels.remove}
</Button>
<Button isCTA isSmall onPress={onSave}>
{sharedLabels.save}
</Button>
</View>
{shouldShowInfo && (
<Segment last style={styles.segmentBorder}>
<AppText>{info[symptom].text}</AppText>
</Segment>
)}
</ScrollView>
</AppModal>
)
}
@@ -229,32 +223,12 @@ const styles = StyleSheet.create({
paddingHorizontal: Spacing.base,
paddingBottom: Spacing.base,
},
headerContainer: {
flexDirection: 'row',
justifyContent: 'flex-end',
paddingTop: Spacing.base,
paddingHorizontal: Spacing.base,
position: 'absolute',
width: '100%',
zIndex: 3, // works on ios
elevation: 3, // works on android
},
input: {
height: Sizes.base * 5,
},
modalContainer: {
paddingHorizontal: Spacing.base,
},
modalWindow: {
alignSelf: 'center',
backgroundColor: 'white',
borderRadius: 10,
marginTop: Sizes.huge * 2,
paddingTop: Spacing.large * 2,
position: 'absolute',
minHeight: '40%',
maxHeight: Dimensions.get('window').height * 0.7,
},
segmentBorder: {
borderBottomColor: Colors.greyLight,
},
@@ -3,6 +3,7 @@ import { FlatList, StyleSheet, View } from 'react-native'
import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next'
import AppModal from '../common/app-modal'
import AppText from '../common/app-text'
import cycleModule from '../../lib/cycle'
@@ -35,26 +36,34 @@ Item.propTypes = {
data: PropTypes.object.isRequired,
}
const StatsTable = () => {
const PeriodDetailsModal = ({ onClose }) => {
const renderItem = ({ item }) => <Item data={item} />
const data = cycleModule().getStats()
if (!data || data.length === 0) return false
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item.date}
ItemSeparatorComponent={ItemDivider}
ListHeaderComponent={FlatListHeader}
ListHeaderComponentStyle={styles.headerDivider}
stickyHeaderIndices={[0]}
contentContainerStyle={styles.container}
/>
<AppModal onClose={onClose}>
<View>
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item.date}
ItemSeparatorComponent={ItemDivider}
ListHeaderComponent={FlatListHeader}
ListHeaderComponentStyle={styles.headerDivider}
stickyHeaderIndices={[0]}
contentContainerStyle={styles.container}
/>
</View>
</AppModal>
)
}
PeriodDetailsModal.propTypes = {
onClose: PropTypes.func,
}
const ItemDivider = () => <View style={styles.divider} />
const FlatListHeader = () => (
@@ -89,7 +98,7 @@ const styles = StyleSheet.create({
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: Spacing.tiny,
backgroundColor: Colors.turquoiseLight,
backgroundColor: 'white',
},
cell: {
flex: 2,
@@ -100,8 +109,10 @@ const styles = StyleSheet.create({
justifyContent: 'center',
},
container: {
minHeight: '40%',
minWidth: '95%',
paddingHorizontal: Spacing.base,
},
})
export default StatsTable
export default PeriodDetailsModal
+6 -2
View File
@@ -15,10 +15,12 @@ StatsOverview.propTypes = {
}
const Row = ({ rowContent }) => {
const isStandardDeviation = rowContent[1].includes('deviation')
return (
<View style={styles.row}>
<Cell content={rowContent[0]} isLeft />
<Cell content={rowContent[1]} />
<Cell content={rowContent[1]} hasAsterisk={isStandardDeviation} />
</View>
)
}
@@ -27,7 +29,7 @@ Row.propTypes = {
rowContent: PropTypes.array.isRequired,
}
const Cell = ({ content, isLeft }) => {
const Cell = ({ content, isLeft, hasAsterisk }) => {
const styleContainer = isLeft ? styles.cellLeft : styles.cellRight
const styleText = isLeft ? styles.accentPurpleBig : styles.accentOrange
const numberOfLines = isLeft ? 1 : 2
@@ -41,6 +43,7 @@ const Cell = ({ content, isLeft }) => {
style={styleText}
>
{content}
{hasAsterisk && <AppText style={styles.accentOrange}>*</AppText>}
</AppText>
</View>
)
@@ -49,6 +52,7 @@ const Cell = ({ content, isLeft }) => {
Cell.propTypes = {
content: PropTypes.node.isRequired,
isLeft: PropTypes.bool,
hasAsterisk: PropTypes.bool,
}
const styles = StyleSheet.create({
+43 -31
View File
@@ -1,11 +1,13 @@
import React from 'react'
import { ImageBackground, View } from 'react-native'
import React, { useState } from 'react'
import { ImageBackground, SafeAreaView, ScrollView, View } from 'react-native'
import { ScaledSheet } from 'react-native-size-matters'
import { useTranslation } from 'react-i18next'
import AppText from '../common/app-text'
import Button from '../common/button'
import Footnote from '../common/Footnote'
import StatsOverview from './StatsOverview'
import StatsTable from './StatsTable'
import PeriodDetailsModal from './PeriodDetailsModal'
import cycleModule from '../../lib/cycle'
import { getCycleLengthStats as getCycleInfo } from '../../lib/cycle-length'
@@ -15,6 +17,8 @@ import { Containers, Sizes, Spacing, Typography } from '../../styles'
const image = require('../../assets/cycle-icon.png')
const Stats = () => {
const [isStatsVisible, setIsStatsVisible] = useState(false)
const { t } = useTranslation(null, { keyPrefix: 'stats' })
const cycleLengths = cycleModule().getAllCycleLengths()
@@ -34,42 +38,50 @@ const Stats = () => {
]
return (
<View style={styles.pageContainer}>
<View style={styles.overviewContainer}>
<SafeAreaView style={styles.pageContainer}>
<ScrollView contentContainerStyle={styles.overviewContainer}>
<AppText>{t('intro')}</AppText>
{numberOfCycles === 0 ? (
<AppText>{t('noData')}</AppText>
) : (
<View style={styles.container}>
<View style={styles.columnLeft}>
<ImageBackground
source={image}
imageStyle={styles.image}
style={styles.imageContainter}
>
<AppText
numberOfLines={1}
ellipsizeMode="clip"
style={styles.accentPurpleGiant}
<>
<View style={styles.container}>
<View style={styles.columnLeft}>
<ImageBackground
source={image}
imageStyle={styles.image}
style={styles.imageContainter}
>
{cycleData.mean}
<AppText
numberOfLines={1}
ellipsizeMode="clip"
style={styles.accentPurpleGiant}
>
{cycleData.mean}
</AppText>
<AppText style={styles.accentPurpleHuge}>
{t('overview.days')}
</AppText>
</ImageBackground>
<AppText style={styles.accentOrange}>
{t('overview.average')}
</AppText>
<AppText style={styles.accentPurpleHuge}>
{t('overview.days')}
</AppText>
</ImageBackground>
<AppText style={styles.accentOrange}>
{t('overview.average')}
</AppText>
</View>
<View style={styles.columnRight}>
<StatsOverview data={statsData} />
</View>
</View>
<View style={styles.columnRight}>
<StatsOverview data={statsData} />
</View>
</View>
<Button isCTA onPress={() => setIsStatsVisible(true)}>
{t('showStats')}
</Button>
{isStatsVisible && (
<PeriodDetailsModal onClose={() => setIsStatsVisible(false)} />
)}
<Footnote>{t('footnote')}</Footnote>
</>
)}
</View>
<StatsTable />
</View>
</ScrollView>
</SafeAreaView>
)
}
@@ -7,7 +7,7 @@ exports[`Footnote component when children are present, renders them 1`] = `
"alignContent": "flex-start",
"flexDirection": "row",
"marginBottom": 8.571428571428571,
"marginTop": 21.428571428571427,
"marginTop": 34.285714285714285,
}
}
>
@@ -41,8 +41,8 @@ exports[`Footnote component when children are present, renders them 1`] = `
"fontSize": 34.285714285714285,
},
Object {
"color": "#CCC",
"paddingLeft": 34.285714285714285,
"color": "#555",
"paddingLeft": 21.428571428571427,
},
]
}