Skip to content

Commit

Permalink
Reduxify the state
Browse files Browse the repository at this point in the history
  • Loading branch information
dsernst committed Jan 14, 2020
1 parent b6858ca commit 6bdecf2
Show file tree
Hide file tree
Showing 10 changed files with 226 additions and 118 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module.exports = {
root: true,
extends: '@react-native-community',
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'sort-keys-fix', 'typescript-sort-keys'],
plugins: ['@typescript-eslint', 'sort-keys-fix', 'typescript-sort-keys', 'sort-destructure-keys'],
rules: {
'lines-between-class-members': ['warn'],
'no-alert': ['off'],
Expand All @@ -13,5 +13,6 @@ module.exports = {
'typescript-sort-keys/interface': 2,
'typescript-sort-keys/string-enum': 2,
'react-native/no-inline-styles': ['off'],
'sort-destructure-keys/sort-destructure-keys': 2,
},
}
170 changes: 62 additions & 108 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,178 +3,125 @@ import { StatusBar, Text, View } from 'react-native'
import _ from 'lodash'
import InitScreen from './InitScreen'
import CountdownScreen from './CountdownScreen'
import HistoryScreen, { SitProps } from './HistoryScreen'
import { Sound, clips as c } from './clips'
import HistoryScreen from './HistoryScreen'
import { clips as c } from './clips'
import { connect } from 'react-redux'
import { ScreenNames, State } from './reducer'

// Shared vars
const bodyTextColor = '#f1f1f1'

type ScreenNames = 'InitScreen' | 'CountdownScreen' | 'HistoryScreen'
const screens: { [screen in ScreenNames]: any } = {
CountdownScreen,
HistoryScreen,
InitScreen,
}

type State = {
[index: string]: any
duration: number
finished: boolean
hasChanting: boolean
hasExtendedMetta: boolean
history: SitProps[]
isEnoughTime: boolean
latestTrack: Sound | null
screen: ScreenNames
}

let timeouts: ReturnType<typeof setTimeout>[] = []

class App extends Component<object, State> {
state: State = {
duration: 60,
finished: false,
hasChanting: false,
hasExtendedMetta: false,
history: [
{
date: new Date('Sat Jan 13 2020 9:14'),
duration: 15,
elapsed: 15,
},
{
date: new Date('Sun Jan 12 2020 22:58'),
duration: 45,
elapsed: 45,
},
{
date: new Date('Sun Jan 12 2020 12:50'),
duration: 5,
elapsed: 5,
},
{
date: new Date('Sat Jan 11 2020 11:57'),
duration: 60,
elapsed: 60,
},
{
date: new Date('Fri Jan 10 2020 22:30'),
duration: 10,
elapsed: 10,
},
{
date: new Date('Fri Jan 10 2020 8:25'),
duration: 35,
elapsed: 35,
},
],
isEnoughTime: true,
latestTrack: null,
screen: 'InitScreen',
}
interface Props extends State {
setState: (payload: object) => void
}

componentDidUpdate(_prevProps: any, prevState: State) {
class App extends Component<Props> {
componentDidUpdate(prevProps: Props) {
const { latestTrack } = this.props
// New track to play, nothing playing previously
if (!prevState.latestTrack && this.state.latestTrack) {
this.state.latestTrack.play()
if (latestTrack && !prevProps.latestTrack) {
latestTrack.play()
}

// New track to play, another track already playing
if (
prevState.latestTrack &&
this.state.latestTrack &&
prevState.latestTrack !== this.state.latestTrack
) {
prevState.latestTrack.stop()
this.state.latestTrack.play()
if (latestTrack && prevProps.latestTrack && prevProps.latestTrack !== latestTrack) {
prevProps.latestTrack.stop()
latestTrack.play()
}

// If timing settings changed, check if duration is enough
if (
['duration', 'hasChanting', 'hasExtendedMetta'].some(
key => prevState[key] !== this.state[key],
)
prevProps.duration !== this.props.duration ||
prevProps.hasChanting !== this.props.hasChanting ||
prevProps.hasExtendedMetta !== this.props.hasExtendedMetta
) {
this.checkIfDurationIsEnough()
}
}

checkIfDurationIsEnough() {
const { duration, hasChanting, hasExtendedMetta, setState } = this.props
const delay = (seconds: number) => ({ getDuration: () => seconds })
const queue: { getDuration: () => number }[] = [c.introInstructions, c.closingMetta]
if (this.state.hasChanting) {
if (hasChanting) {
queue.push(c.introChanting, delay(5), c.closingChanting, delay(2))
}
if (this.state.hasExtendedMetta) {
if (hasExtendedMetta) {
queue.push(c.mettaIntro, delay(3 * 60))
}
const durations = queue.map(clip => clip.getDuration())
this.setState({ isEnoughTime: _.sum(durations) < this.state.duration * 60 })
setState({ isEnoughTime: _.sum(durations) < duration * 60 })
}

pressStart() {
this.setState({ screen: 'CountdownScreen' })
const { duration, hasChanting, hasExtendedMetta, history, setState } = this.props

setState({ screen: 'CountdownScreen' })

// Add to history
this.setState({
history: [
{ date: new Date(), duration: this.state.duration, elapsed: 0 },
...this.state.history,
],
})

if (this.state.hasChanting) {
setState({ history: [{ date: new Date(), duration: duration, elapsed: 0 }, ...history] })

if (hasChanting) {
// Begin introChanting
this.setState({ latestTrack: c.introChanting })
setState({ latestTrack: c.introChanting })

// Setup a timeout to begin introInstructions a few
// seconds after introChanting finishes.
timeouts.push(
setTimeout(() => {
this.setState({ latestTrack: c.introInstructions })
setState({ latestTrack: c.introInstructions })
}, Math.ceil(c.introChanting.getDuration() + 5) * 1000),
)
} else {
this.setState({ latestTrack: c.introInstructions })
setState({ latestTrack: c.introInstructions })
}

// Calculate closing time
const closingMettaTime =
(this.state.duration * 60 - Math.floor(c.closingMetta.getDuration())) * 1000
const closingMettaTime = (duration * 60 - Math.floor(c.closingMetta.getDuration())) * 1000

let extendedMettaTime = closingMettaTime
if (this.state.hasExtendedMetta) {
if (hasExtendedMetta) {
// Begin mettaIntro 3 minutes before closingMetta
extendedMettaTime -= (Math.floor(c.mettaIntro.getDuration()) + 3 * 60) * 1000

timeouts.push(
setTimeout(() => {
this.setState({ latestTrack: c.mettaIntro })
setState({ latestTrack: c.mettaIntro })
}, extendedMettaTime),
)
}

if (this.state.hasChanting) {
if (hasChanting) {
// Begin closingChanting so it ends just before metta starts.
timeouts.push(
setTimeout(() => {
this.setState({ latestTrack: c.closingChanting })
setState({ latestTrack: c.closingChanting })
}, extendedMettaTime - (Math.floor(c.closingChanting.getDuration()) + 2) * 1000),
)
}

// Begin closingMetta so it ends when countdown hits zero.
timeouts.push(
setTimeout(() => {
this.setState({ latestTrack: c.closingMetta })
setState({ latestTrack: c.closingMetta })
}, closingMettaTime),
)
}

pressStop() {
const { latestTrack, setState } = this.props

// Stop audio
if (this.state.latestTrack) {
this.state.latestTrack.stop()
if (latestTrack) {
latestTrack.stop()
}

// Clear all of the setTimeouts
Expand All @@ -185,11 +132,12 @@ class App extends Component<object, State> {
}

// Go back to InitScreen
this.setState({ finished: false, latestTrack: null, screen: 'InitScreen' })
setState({ finished: false, latestTrack: null, screen: 'InitScreen' })
}

render() {
const Screen = screens[this.state.screen]
const { screen, setState } = this.props
const Screen = screens[screen]

return (
<>
Expand All @@ -202,7 +150,7 @@ class App extends Component<object, State> {
paddingTop: 18,
}}
>
{this.state.screen !== 'HistoryScreen' && (
{screen !== 'HistoryScreen' && (
<Text
style={{
alignSelf: 'center',
Expand All @@ -216,23 +164,22 @@ class App extends Component<object, State> {
</Text>
)}
<Screen
{...this.state}
openHistory={() => this.setState({ screen: 'HistoryScreen' })}
{...this.props}
openHistory={() => setState({ screen: 'HistoryScreen' })}
pressStart={this.pressStart.bind(this)}
pressStop={this.pressStop.bind(this)}
removeSit={(index: number) => () => {
const history = [...this.state.history]
const history = [...this.props.history]
history.splice(index, 1)
this.setState({ history })
}}
setDuration={(duration: number) => this.setState({ duration })}
toggle={(key: string) => () => {
this.setState({ [key]: !this.state[key] })
setState({ history })
}}
setDuration={(duration: number) => setState({ duration })}
toggle={(key: string) => () =>
setState({ [key]: !_.pickBy(this.props, _.isBoolean)[key] })}
updateElapsed={(elapsed: number) => {
const history = [...this.state.history]
const history = [...this.props.history]
history[0].elapsed = elapsed
this.setState({ history })
setState({ history })
}}
/>
</View>
Expand All @@ -241,4 +188,11 @@ class App extends Component<object, State> {
}
}

export default App
export default connect(
(s: State) => s,
dispatch => ({
setState: (payload: object) => {
dispatch({ payload, type: 'setState' })
},
}),
)(App)
2 changes: 1 addition & 1 deletion CountdownScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ type CountdownScreenProps = {

const CountdownScreen = ({
duration,
finished,
pressStop,
toggle,
finished,
updateElapsed,
}: CountdownScreenProps) => (
<>
Expand Down
4 changes: 2 additions & 2 deletions HistoryScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export type SitProps = {
}

export default ({
pressStop,
history,
pressStop,
removeSit,
}: {
history: SitProps[]
Expand Down Expand Up @@ -117,7 +117,7 @@ export default ({
You don't have any sits recorded yet.
</Text>
)}
renderItem={({ item: i, index }) => (
renderItem={({ index, item: i }) => (
<TouchableOpacity
onPress={() =>
Alert.alert(
Expand Down
2 changes: 1 addition & 1 deletion InitScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ const InitScreen = ({
duration,
hasChanting,
hasExtendedMetta,
openHistory,
isEnoughTime,
openHistory,
pressStart,
setDuration,
toggle,
Expand Down
14 changes: 13 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,20 @@
* @format
*/

import React from 'react'
import { AppRegistry } from 'react-native'
import App from './App'
import { name as appName } from './app.json'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import reducer from './reducer'

AppRegistry.registerComponent(appName, () => App)
const store = createStore(reducer)

const Reduxed = () => (
<Provider store={store}>
<App />
</Provider>
)

AppRegistry.registerComponent(appName, () => Reduxed)
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
},
"dependencies": {
"@types/react-redux": "^7.1.5",
"dayjs": "^1.8.19",
"lodash": "^4.17.15",
"react": "16.9.0",
"react-native": "0.61.5",
"react-native-keep-awake": "^4.0.0",
"react-native-sound": "^0.11.0",
"react-native-vector-icons": "^6.6.0"
"react-native-vector-icons": "^6.6.0",
"react-redux": "^7.1.3",
"redux": "^4.0.5"
},
"devDependencies": {
"@babel/core": "^7.6.2",
Expand All @@ -33,6 +36,7 @@
"babel-jest": "^24.9.0",
"eslint": "^6.5.1",
"eslint-plugin-react": "^7.17.0",
"eslint-plugin-sort-destructure-keys": "^1.3.3",
"eslint-plugin-sort-keys-fix": "^1.1.0",
"eslint-plugin-typescript-sort-keys": "^0.5.0",
"jest": "^24.9.0",
Expand Down
Loading

0 comments on commit 6bdecf2

Please sign in to comment.