react-native-countdown-component
Version:
React Native CountDown Component
276 lines (252 loc) • 7.23 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import {
StyleSheet,
View,
Text,
TouchableOpacity,
AppState
} from 'react-native';
import _ from 'lodash';
import {sprintf} from 'sprintf-js';
const DEFAULT_DIGIT_STYLE = {backgroundColor: '#FAB913'};
const DEFAULT_DIGIT_TXT_STYLE = {color: '#000'};
const DEFAULT_TIME_LABEL_STYLE = {color: '#000'};
const DEFAULT_SEPARATOR_STYLE = {color: '#000'};
const DEFAULT_TIME_TO_SHOW = ['D', 'H', 'M', 'S'];
const DEFAULT_TIME_LABELS = {
d: 'Days',
h: 'Hours',
m: 'Minutes',
s: 'Seconds',
};
class CountDown extends React.Component {
static propTypes = {
id: PropTypes.string,
digitStyle: PropTypes.object,
digitTxtStyle: PropTypes.object,
timeLabelStyle: PropTypes.object,
separatorStyle: PropTypes.object,
timeToShow: PropTypes.array,
showSeparator: PropTypes.bool,
size: PropTypes.number,
until: PropTypes.number,
onChange: PropTypes.func,
onPress: PropTypes.func,
onFinish: PropTypes.func,
};
state = {
until: Math.max(this.props.until, 0),
lastUntil: null,
wentBackgroundAt: null,
};
constructor(props) {
super(props);
this.timer = setInterval(this.updateTimer, 1000);
}
componentDidMount() {
AppState.addEventListener('change', this._handleAppStateChange);
}
componentWillUnmount() {
clearInterval(this.timer);
AppState.removeEventListener('change', this._handleAppStateChange);
}
componentDidUpdate(prevProps, prevState) {
if (this.props.until !== prevProps.until || this.props.id !== prevProps.id) {
this.setState({
lastUntil: prevState.until,
until: Math.max(prevProps.until, 0)
});
}
}
// componentWillReceiveProps(nextProps) {
// if (this.props.until !== nextProps.until || this.props.id !== nextProps.id) {
// this.setState({
// lastUntil: this.state.until,
// until: Math.max(nextProps.until, 0)
// });
// }
// }
_handleAppStateChange = currentAppState => {
const {until, wentBackgroundAt} = this.state;
if (currentAppState === 'active' && wentBackgroundAt && this.props.running) {
const diff = (Date.now() - wentBackgroundAt) / 1000.0;
this.setState({
lastUntil: until,
until: Math.max(0, until - diff)
});
}
if (currentAppState === 'background') {
this.setState({wentBackgroundAt: Date.now()});
}
}
getTimeLeft = () => {
const {until} = this.state;
return {
seconds: until % 60,
minutes: parseInt(until / 60, 10) % 60,
hours: parseInt(until / (60 * 60), 10) % 24,
days: parseInt(until / (60 * 60 * 24), 10),
};
};
updateTimer = () => {
// Don't fetch these values here, because their value might be changed
// in another thread
// const {lastUntil, until} = this.state;
if (this.state.lastUntil === this.state.until || !this.props.running) {
return;
}
if (this.state.until === 1 || (this.state.until === 0 && this.state.lastUntil !== 1)) {
if (this.props.onFinish) {
this.props.onFinish();
}
if (this.props.onChange) {
this.props.onChange(this.state.until);
}
}
if (this.state.until === 0) {
this.setState({lastUntil: 0, until: 0});
} else {
if (this.props.onChange) {
this.props.onChange(this.state.until);
}
this.setState({
lastUntil: this.state.until,
until: Math.max(0, this.state.until - 1)
});
}
};
renderDigit = (d) => {
const {digitStyle, digitTxtStyle, size} = this.props;
return (
<View style={[
styles.digitCont,
{width: size * 2.3, height: size * 2.6},
digitStyle,
]}>
<Text style={[
styles.digitTxt,
{fontSize: size},
digitTxtStyle,
]}>
{d}
</Text>
</View>
);
};
renderLabel = label => {
const {timeLabelStyle, size} = this.props;
if (label) {
return (
<Text style={[
styles.timeTxt,
{fontSize: size / 1.8},
timeLabelStyle,
]}>
{label}
</Text>
);
}
};
renderDoubleDigits = (label, digits) => {
return (
<View style={styles.doubleDigitCont}>
<View style={styles.timeInnerCont}>
{this.renderDigit(digits)}
</View>
{this.renderLabel(label)}
</View>
);
};
renderSeparator = () => {
const {separatorStyle, size} = this.props;
return (
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={[
styles.separatorTxt,
{fontSize: size * 1.2},
separatorStyle,
]}>
{':'}
</Text>
</View>
);
};
renderCountDown = () => {
const {timeToShow, timeLabels, showSeparator} = this.props;
const {until} = this.state;
const {days, hours, minutes, seconds} = this.getTimeLeft();
const newTime = sprintf('%02d:%02d:%02d:%02d', days, hours, minutes, seconds).split(':');
const Component = this.props.onPress ? TouchableOpacity : View;
return (
<Component
style={styles.timeCont}
onPress={this.props.onPress}
>
{timeToShow.includes('D') ? this.renderDoubleDigits(timeLabels.d, newTime[0]) : null}
{showSeparator && timeToShow.includes('D') && timeToShow.includes('H') ? this.renderSeparator() : null}
{timeToShow.includes('H') ? this.renderDoubleDigits(timeLabels.h, newTime[1]) : null}
{showSeparator && timeToShow.includes('H') && timeToShow.includes('M') ? this.renderSeparator() : null}
{timeToShow.includes('M') ? this.renderDoubleDigits(timeLabels.m, newTime[2]) : null}
{showSeparator && timeToShow.includes('M') && timeToShow.includes('S') ? this.renderSeparator() : null}
{timeToShow.includes('S') ? this.renderDoubleDigits(timeLabels.s, newTime[3]) : null}
</Component>
);
};
render() {
return (
<View style={this.props.style}>
{this.renderCountDown()}
</View>
);
}
}
CountDown.defaultProps = {
digitStyle: DEFAULT_DIGIT_STYLE,
digitTxtStyle: DEFAULT_DIGIT_TXT_STYLE,
timeLabelStyle: DEFAULT_TIME_LABEL_STYLE,
timeLabels: DEFAULT_TIME_LABELS,
separatorStyle: DEFAULT_SEPARATOR_STYLE,
timeToShow: DEFAULT_TIME_TO_SHOW,
showSeparator: false,
until: 0,
size: 15,
running: true,
};
const styles = StyleSheet.create({
timeCont: {
flexDirection: 'row',
justifyContent: 'center',
},
timeTxt: {
color: 'white',
marginVertical: 2,
backgroundColor: 'transparent',
},
timeInnerCont: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
digitCont: {
borderRadius: 5,
marginHorizontal: 2,
alignItems: 'center',
justifyContent: 'center',
},
doubleDigitCont: {
justifyContent: 'center',
alignItems: 'center',
},
digitTxt: {
color: 'white',
fontWeight: 'bold',
fontVariant: ['tabular-nums']
},
separatorTxt: {
backgroundColor: 'transparent',
fontWeight: 'bold',
},
});
export default CountDown;
export { CountDown };