-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
372 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,248 @@ | ||
/** | ||
* Copyright 2016 Reza (github.com/rghorbani). | ||
* | ||
* @flow | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const React = require('react'); | ||
const PropTypes = require('prop-types'); | ||
const Moment = require('moment'); | ||
const jMoment = require('moment-jalaali'); | ||
const { FlatList, Platform, StyleSheet } = require('react-native'); | ||
|
||
const CalendarListItem = require('./item'); | ||
const Calendar = require('../calendar'); | ||
const defaultStyle = require('../style'); | ||
const dateutils = require('../dateutils'); | ||
const { xdateToData, parseDate } = require('../interface'); | ||
|
||
class CalendarList extends React.Component { | ||
static propTypes = { | ||
...Calendar.propTypes, | ||
|
||
// hight of each calendar. Default = 360 | ||
calendarHeight: PropTypes.number, | ||
|
||
// Max amount of months allowed to scroll to the past. Default = 50 | ||
pastScrollRange: PropTypes.number, | ||
|
||
// Max amount of months allowed to scroll to the future. Default = 50 | ||
futureScrollRange: PropTypes.number, | ||
|
||
// Enable or disable scrolling of calendar list | ||
scrollEnabled: PropTypes.bool, | ||
|
||
// Enable or disable vertical scroll indicator. Default = false | ||
showScrollIndicator: PropTypes.bool, | ||
|
||
// When true, the calendar list scrolls to top when the status bar is tapped. Default = false | ||
scrollsToTop: PropTypes.bool, | ||
}; | ||
|
||
static defaultProps = { | ||
type: 'gregorian', | ||
calendarHeight: 360, | ||
pastScrollRange: 50, | ||
futureScrollRange: 50, | ||
showScrollIndicator: false, | ||
scrollEnabled: true, | ||
scrollsToTop: false, | ||
}; | ||
|
||
constructor(props) { | ||
super(props); | ||
this.style = styleConstructor(props.theme); | ||
const rows = []; | ||
const texts = []; | ||
let date; | ||
if (props.type === 'jalaali') { | ||
date = parseDate(this.props.type, props.current) || jMoment.utc(); | ||
} else { | ||
date = parseDate(this.props.type, props.current) || Moment.utc(); | ||
} | ||
for (let i = 0; i <= this.props.pastScrollRange + this.props.futureScrollRange; i++) { | ||
let rangeDate; | ||
let rangeDateStr; | ||
if (props.type === 'jalaali') { | ||
rangeDate = date.clone().add(i - this.props.pastScrollRange, 'jMonths'); | ||
rangeDateStr = rangeDate.format('jMMMM jYYYY'); | ||
} else { | ||
rangeDate = date.clone().add(i - this.props.pastScrollRange, 'months'); | ||
rangeDateStr = rangeDate.format('MMM YYYY'); | ||
} | ||
texts.push(rangeDateStr); | ||
/* | ||
* This selects range around current shown month [-0, +2] or [-1, +1] month for detail calendar rendering. | ||
* If `this.props.pastScrollRange` is `undefined` it's equal to `false` or 0 in next condition. | ||
*/ | ||
if (this.props.pastScrollRange - 1 <= i && i <= this.props.pastScrollRange + 1 || !this.props.pastScrollRange && i <= this.props.pastScrollRange + 2) { | ||
rows.push(rangeDate); | ||
} else { | ||
rows.push(rangeDateStr); | ||
} | ||
} | ||
|
||
this.state = { | ||
rows, | ||
texts, | ||
openDate: date, | ||
initialized: false, | ||
}; | ||
this.lastScrollPosition = -1000; | ||
|
||
this.onViewableItemsChangedBound = this.onViewableItemsChanged.bind(this); | ||
this.renderCalendar = this.renderCalendar.bind(this); | ||
this.getItemLayout = this.getItemLayout.bind(this); | ||
} | ||
|
||
scrollToDay(d, offset, animated) { | ||
const day = parseDate(this.props.type, d); | ||
let diffMonths; | ||
if (this.props.type === 'jalaali') { | ||
diffMonths = Math.round(this.state.openDate.clone().jDate(1).diff(day.clone().jDate(1), 'jMonths')); | ||
} else { | ||
diffMonths = Math.round(this.state.openDate.clone().date(1).diff(day.clone().date(1), 'months')); | ||
} | ||
let scrollAmount = (this.props.calendarHeight * this.props.pastScrollRange) + (diffMonths * this.props.calendarHeight) + (offset || 0); | ||
let week = 0; | ||
const days = dateutils.page(this.props.type, day, this.props.firstDay); | ||
for (let i = 0; i < days.length; i++) { | ||
week = Math.floor(i / 7); | ||
if (dateutils.sameDate(this.props.type, days[i], day)) { | ||
scrollAmount += 46 * week; | ||
break; | ||
} | ||
} | ||
this.listView.scrollToOffset({offset: scrollAmount, animated}); | ||
} | ||
|
||
scrollToMonth(m) { | ||
const month = parseDate(this.props.type, m); | ||
const scrollTo = month || this.state.openDate; | ||
let diffMonths; | ||
if (this.props.type === 'jalaali') { | ||
diffMonths = Math.round(this.state.openDate.clone().jDate(1).diff(scrollTo.clone().jDate(1), 'jMonths')); | ||
} else { | ||
diffMonths = Math.round(this.state.openDate.clone().date(1).diff(scrollTo.clone().date(1), 'months')); | ||
} | ||
const scrollAmount = (this.props.calendarHeight * this.props.pastScrollRange) + (diffMonths * this.props.calendarHeight); | ||
//console.log(month, this.state.openDate); | ||
//console.log(scrollAmount, diffMonths); | ||
this.listView.scrollToOffset({offset: scrollAmount, animated: false}); | ||
} | ||
|
||
componentWillReceiveProps(props) { | ||
const current = parseDate(this.props.type, this.props.current); | ||
const nextCurrent = parseDate(this.props.type, props.current); | ||
if (nextCurrent && current && nextCurrent.valueOf() !== current.valueOf()) { | ||
this.scrollToMonth(nextCurrent); | ||
} | ||
|
||
const rowclone = this.state.rows; | ||
const newrows = []; | ||
for (let i = 0; i < rowclone.length; i++) { | ||
let val = this.state.texts[i]; | ||
if (rowclone[i].getTime) { | ||
val = rowclone[i].clone(); | ||
val.propbump = rowclone[i].propbump ? rowclone[i].propbump + 1 : 1; | ||
} | ||
newrows.push(val); | ||
} | ||
this.setState({ | ||
rows: newrows | ||
}); | ||
} | ||
|
||
onViewableItemsChanged({viewableItems}) { | ||
function rowIsCloseToViewable(index, distance) { | ||
for (let i = 0; i < viewableItems.length; i++) { | ||
if (Math.abs(index - parseInt(viewableItems[i].index)) <= distance) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
const rowclone = this.state.rows; | ||
const newrows = []; | ||
const visibleMonths = []; | ||
for (let i = 0; i < rowclone.length; i++) { | ||
let val = rowclone[i]; | ||
const rowShouldBeRendered = rowIsCloseToViewable(i, 1); | ||
if (rowShouldBeRendered && !rowclone[i].getTime) { | ||
if (this.props.type === 'jalaali') { | ||
val = this.state.openDate.clone().add(i - this.props.pastScrollRange, 'jMonths'); | ||
} else { | ||
val = this.state.openDate.clone().add(i - this.props.pastScrollRange, 'months'); | ||
} | ||
} else if (!rowShouldBeRendered) { | ||
val = this.state.texts[i]; | ||
} | ||
newrows.push(val); | ||
if (rowIsCloseToViewable(i, 0)) { | ||
visibleMonths.push(xdateToData(this.props.type, val)); | ||
} | ||
} | ||
if (this.props.onVisibleMonthsChange) { | ||
this.props.onVisibleMonthsChange(visibleMonths); | ||
} | ||
this.setState({ | ||
rows: newrows | ||
}); | ||
} | ||
|
||
renderCalendar({item}) { | ||
return (<CalendarListItem item={item} calendarHeight={this.props.calendarHeight} {...this.props} />); | ||
} | ||
|
||
getItemLayout(data, index) { | ||
return {length: this.props.calendarHeight, offset: this.props.calendarHeight * index, index}; | ||
} | ||
|
||
getMonthIndex(month) { | ||
let diffMonths; | ||
if (this.props.type === 'jalaali') { | ||
diffMonths = this.state.openDate.diff(month, 'jMonths') + this.props.pastScrollRange; | ||
} else { | ||
diffMonths = this.state.openDate.diff(month, 'months') + this.props.pastScrollRange; | ||
} | ||
return diffMonths; | ||
} | ||
|
||
render() { | ||
return ( | ||
<FlatList | ||
ref={(c) => this.listView = c} | ||
style={[this.style.container, this.props.style]} | ||
initialListSize={this.props.pastScrollRange * this.props.futureScrollRange + 1} | ||
data={this.state.rows} | ||
removeClippedSubviews={Platform.OS === 'android' ? false : true} | ||
pageSize={1} | ||
onViewableItemsChanged={this.onViewableItemsChangedBound} | ||
renderItem={this.renderCalendar} | ||
showsVerticalScrollIndicator={this.props.showScrollIndicator} | ||
scrollEnabled={this.props.scrollEnabled} | ||
keyExtractor={(item, index) => String(index)} | ||
initialScrollIndex={this.state.openDate ? this.getMonthIndex(this.state.openDate) : false} | ||
getItemLayout={this.getItemLayout} | ||
scrollsToTop={this.props.scrollsToTop} | ||
/> | ||
); | ||
} | ||
} | ||
|
||
const STYLESHEET_ID = 'stylesheet.calendar-list.main'; | ||
|
||
function styleConstructor(theme = {}) { | ||
const appStyle = {...defaultStyle, ...theme}; | ||
return StyleSheet.create({ | ||
container: { | ||
backgroundColor: appStyle.calendarBackground, | ||
}, | ||
...(theme[STYLESHEET_ID] || {}) | ||
}); | ||
} | ||
|
||
module.exports = CalendarList; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
/** | ||
* Copyright 2016 Reza (github.com/rghorbani). | ||
* | ||
* @flow | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const React = require('react'); | ||
const {StyleSheet, Text, View} = require('react-native'); | ||
|
||
const Calendar = require('../calendar'); | ||
const defaultStyle = require('../style'); | ||
|
||
class CalendarListItem extends React.Component { | ||
static defaultProps = { | ||
hideExtraDays: true, | ||
}; | ||
|
||
constructor(props) { | ||
super(props); | ||
this.style = styleConstructor(props.theme); | ||
} | ||
|
||
shouldComponentUpdate(nextProps) { | ||
const r1 = this.props.item; | ||
const r2 = nextProps.item; | ||
return (typeof r1 !== typeof r2 && typeof r1 === 'string' && r1.toString() !== r2.format('YYYY MM')) | ||
|| (typeof r1 !== typeof r2 && typeof r2 === 'string' && r2.toString() !== r1.format('YYYY MM')) | ||
|| !!(r2.propbump && r2.propbump !== r1.propbump); | ||
// return r1.format('YYYY MM') !== r2.format('YYYY MM') || !!(r2.propbump && r2.propbump !== r1.propbump); | ||
} | ||
|
||
render() { | ||
const row = this.props.item; | ||
if (row.format) { | ||
return ( | ||
<Calendar | ||
type={this.props.type} | ||
rtl={this.props.rtl} | ||
theme={this.props.theme} | ||
style={[{height: this.props.calendarHeight}, this.style.calendar]} | ||
current={row} | ||
hideArrows | ||
hideExtraDays={this.props.hideExtraDays} | ||
disableMonthChange | ||
markedDates={this.props.markedDates} | ||
markingType={this.props.markingType} | ||
hideDayNames={this.props.hideDayNames} | ||
onDayPress={this.props.onDayPress} | ||
displayLoadingIndicator={this.props.displayLoadingIndicator} | ||
minDate={this.props.minDate} | ||
maxDate={this.props.maxDate} | ||
firstDay={this.props.firstDay} | ||
monthFormat={this.props.monthFormat} | ||
dayComponent={this.props.dayComponent} | ||
disabledByDefault={this.props.disabledByDefault} | ||
showWeekNumbers={this.props.showWeekNumbers} | ||
/>); | ||
} else { | ||
const text = row.toString(); | ||
return ( | ||
<View style={[{height: this.props.calendarHeight}, this.style.placeholder]}> | ||
<Text allowFontScaling={false} style={this.style.placeholderText}>{text}</Text> | ||
</View> | ||
); | ||
} | ||
} | ||
} | ||
|
||
const STYLESHEET_ID = 'stylesheet.calendar-list.main'; | ||
|
||
function styleConstructor(theme = {}) { | ||
const appStyle = {...defaultStyle, ...theme}; | ||
return StyleSheet.create({ | ||
placeholder: { | ||
backgroundColor: appStyle.calendarBackground, | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
}, | ||
placeholderText: { | ||
fontSize: 30, | ||
fontWeight: '200', | ||
color: appStyle.dayTextColor, | ||
}, | ||
calendar: { | ||
paddingLeft: 15, | ||
paddingRight: 15, | ||
}, | ||
...(theme[STYLESHEET_ID] || {}) | ||
}); | ||
} | ||
|
||
module.exports = CalendarListItem; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.