diff --git a/index.js b/index.js index b546b82..4333fb3 100644 --- a/index.js +++ b/index.js @@ -1,274 +1,377 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +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'; + 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_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', + 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); - } + static propTypes = { + id: PropTypes.string, + digitStyle: PropTypes.object, + digitTxtStyle: PropTypes.object, + timeLabelStyle: PropTypes.object, + doubleDigitslStyle: PropTypes.object, + separatorStyle: PropTypes.object, + timeToHiddenWhenEqualToZero: PropTypes.object, + timeToShow: PropTypes.array, + showSeparator: PropTypes.bool, + size: PropTypes.number, + until: PropTypes.number, + onChange: PropTypes.func, + onPress: PropTypes.func, + onFinish: PropTypes.func, + }; - componentDidMount() { - AppState.addEventListener('change', this._handleAppStateChange); - } + state = { + until: Math.max(this.props.until, 0), + lastUntil: null, + wentBackgroundAt: null, + }; - componentWillUnmount() { - clearInterval(this.timer); - AppState.removeEventListener('change', this._handleAppStateChange); - } + constructor(props) { + super(props); + this.timer = setInterval(this.updateTimer, 1000); + this.myListener = null; + } - 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) - }); + componentDidMount() { + this.myListener = AppState.addEventListener( + "change", + this._handleAppStateChange + ); } - } - // 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) - }); + componentWillUnmount() { + clearInterval(this.timer); + this.myListener && this.myListener.remove(); } - if (currentAppState === 'background') { - this.setState({wentBackgroundAt: Date.now()}); + + 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) + // }); + // } + // } - 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), + _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() }); + } }; - }; - updateTimer = () => { - // Don't fetch these values here, because their value might be changed - // in another thread - // const {lastUntil, until} = this.state; + 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), + }; + }; - 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); - } - } + updateTimer = () => { + // Don't fetch these values here, because their value might be changed + // in another thread + // const {lastUntil, until} = this.state; - 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) - }); - } - }; + 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); + } + } - renderDigit = (d) => { - const {digitStyle, digitTxtStyle, size} = this.props; - return ( - - - {d} - - - ); - }; + 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), + }); + } + }; - renderLabel = label => { - const {timeLabelStyle, size} = this.props; - if (label) { - return ( - - {label} - - ); - } - }; + renderDigit = (d) => { + const { digitStyle, digitTxtStyle, size } = this.props; + return ( + + + {d} + + + ); + }; - renderDoubleDigits = (label, digits) => { - return ( - - - {this.renderDigit(digits)} - - {this.renderLabel(label)} - - ); - }; + renderLabel = (label) => { + const { timeLabelStyle, size } = this.props; + if (label) { + return ( + + {label} + + ); + } + }; + + // Kiểm tra xem co hiển thị timeType (days, hours, minutes, seconds) hay không ? + checkTimeToHideen = (digits, timeType, newTime) => { + const { timeToHiddenWhenEqualToZero } = this.props; - renderSeparator = () => { - const {separatorStyle, size} = this.props; - return ( - - - {':'} - - - ); - }; + // nếu không có timeToHiddenWhenEqualToZero khỏi cần check + if (!timeToHiddenWhenEqualToZero) return false; + const days = Number(newTime[0]), + hours = Number(newTime[1]), + minutes = Number(newTime[2]); - 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; + const objectTime = { + D: + timeToHiddenWhenEqualToZero?.includes(timeType) && + Number(digits) === 0, + H: + timeToHiddenWhenEqualToZero?.includes(timeType) && + Number(digits) === 0 && + days === 0, // kiểm tra xem days === 0 thì giờ là lớn nhất có thể ẩn 00:30:30:30 + M: + timeToHiddenWhenEqualToZero?.includes(timeType) && + Number(digits) === 0 && + days === 0 && + hours === 0, // kiểm tra xem days === 0 và hours === 0 thì phút là lớn nhất có thể ẩn 00:00:30:30 + S: + timeToHiddenWhenEqualToZero?.includes(timeType) && + Number(digits) === 0 && + days === 0 && + hours === 0 && + minutes === 0, // kiểm tra xem days === 0 và hours === 0 và minutes === 0 thì giây là lớn nhất có thể ẩn 00:00:00:30 + }; + return objectTime[timeType] || false; + }; - return ( - - {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} - - ); - }; + renderDoubleDigits = (label, digits, timeType, newTime) => { + const { doubleDigitslStyle } = this.props; - render() { - return ( - - {this.renderCountDown()} - - ); - } + if (!this.checkTimeToHideen(digits, timeType, newTime)) { + return ( + + + {this.renderDigit(digits)} + + {this.renderLabel(label)} + + ); + } + return ; + }; + + renderSeparator = (digits, timeType, newTime) => { + const { separatorStyle, size } = this.props; + + if (!this.checkTimeToHideen(digits, timeType, newTime)) { + return ( + + + {":"} + + + ); + } + return ; + }; + + renderCountDown = () => { + const { timeToShow, timeLabels, showSeparator } = this.props; + 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 ( + + {timeToShow.includes("D") + ? this.renderDoubleDigits( + timeLabels.d, + newTime[0], + "D", + newTime + ) + : null} + {showSeparator && + timeToShow.includes("D") && + timeToShow.includes("H") + ? this.renderSeparator(newTime[0], "D", newTime) + : null} + {timeToShow.includes("H") + ? this.renderDoubleDigits( + timeLabels.h, + newTime[1], + "H", + newTime + ) + : null} + {showSeparator && + timeToShow.includes("H") && + timeToShow.includes("M") + ? this.renderSeparator(newTime[1], "H", newTime) + : null} + {timeToShow.includes("M") + ? this.renderDoubleDigits( + timeLabels.m, + newTime[2], + "M", + newTime + ) + : null} + {showSeparator && + timeToShow.includes("M") && + timeToShow.includes("S") + ? this.renderSeparator(newTime[2], "M", newTime) + : null} + {timeToShow.includes("S") + ? this.renderDoubleDigits( + timeLabels.s, + newTime[3], + "S", + newTime + ) + : null} + + ); + }; + + render() { + return {this.renderCountDown()}; + } } 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, + 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', - }, + 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;