Advertisement
Guest User

calendar.js

a guest
Sep 18th, 2019
163
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. //@flow
  2.  
  3. import React, {Component} from 'react';
  4. import {ScrollView, StyleSheet, Dimensions} from 'react-native';
  5. import {Text, View, Header, Left} from 'native-base';
  6. import moment from 'moment';
  7. import Dates from './Dates';
  8. import type Moment from 'moment';
  9.  
  10. var plLocale = require('moment/locale/pl');
  11. moment.locale('pl', plLocale);
  12.  
  13. type Props = {
  14.     //optional prop to pass custom date instead of today
  15.     currentDate?: string | Moment,
  16.     //callback executed when user taps on a date
  17.     onSelectDate: (date: Moment) => any,
  18.     //number of days to show before today/current chosen date
  19.     showDaysBeforeCurrent?: number,
  20.     //number of days to show after
  21.     showDaysAfterCurrent?: number,
  22. };
  23.  
  24. type State = {
  25.     //true when all dates have been rendered
  26.     allDatesHaveRendered: boolean,
  27.     //current chosen date index
  28.     currentDateIndex: ?number,
  29.     //Store months and years visible on screen
  30.     //for rendering months and years above the dates
  31.     visibleMonths: ?Array<string>,
  32.     visibleYears: ?Array<string>,
  33.     // array of dates to show
  34.     dates: Array<Moment>,
  35.     //store each day width to help with scrolling to specific days
  36.     //and calculating which days are visible on the screen
  37.     dayWidths: ?{|[index: number]: number|},
  38.     // store current scroll position
  39.     scrollPositionX: number,
  40. };
  41.  
  42. const {width: screenWidth} = Dimensions.get('window');
  43.  
  44. const formatMonth = (date: Moment): string => date.format('MMMM');
  45.  
  46. const formatYear = (date: Moment): string => date.format('YYYY');
  47.  
  48. export default class Calendar extends Component {
  49.     props: Props;
  50.  
  51.     state: State;
  52.  
  53.     static defaultProps = {
  54.         //show 5 days before current date
  55.         showDaysBeforeCurrent: 5,
  56.         // ^ but after
  57.         showDaysAfterCurrent: 5,
  58.     };
  59.  
  60.     _scrollView;
  61.  
  62.     //initialize state with default values
  63.     constructor(props: Props) {
  64.         super(props);
  65.         this.state = {
  66.             allDatesHaveRendered: false,
  67.             currentDateIndex: props.showDaysBeforeCurrent,
  68.             dates: this.getDates(),
  69.             dayWidths: undefined,
  70.             scrollPositionX: 0,
  71.             visibleMonths: undefined,
  72.             visibleYears: undefined,
  73.         };
  74.     }
  75.  
  76.     //get an array of dates for showing in horizontal screen view
  77.     getDates = (): Array<Moment> => {
  78.         const {
  79.             currentDate,
  80.             showDaysBeforeCurrent,
  81.             showDaysAfterCurrent,
  82.         } = this.props;
  83.  
  84.         // 'showDaysBeforeCurrent' ago before current chosen or todays date
  85.         const startDay = moment(currentDate || undefined).subtract(
  86.             showDaysBeforeCurrent + 1,
  87.             'days',
  88.         );
  89.         //number of days in total
  90.         const totalDaysCount = showDaysBeforeCurrent + showDaysAfterCurrent + 1;
  91.         // and return array of 'totalDaysCount' dates
  92.         //console.log(totalDaysCount)
  93.         return [...Array(totalDaysCount)].map(_ => startDay.add(1, 'day').clone());
  94.     };
  95.  
  96.     //return a subset of days currently visible on screen
  97.     getVisibleDates = (): ?Array<Moment> => {
  98.         const {dates, dayWidths, scrollPositionX} = this.state;
  99.  
  100.         if (!dayWidths) {
  101.             return;
  102.         }
  103.  
  104.         let datePositionX = 0;
  105.         let firstVisibleDateIndex = undefined;
  106.         let lastVisibleDateIndex = undefined;
  107.  
  108.         //iterate through daywidths to $FlowFixMe
  109.         Object.values(dayWidths).some((width: number, index: number) => {
  110.             if (
  111.                 firstVisibleDateIndex === undefined &&
  112.                 datePositionX >= scrollPositionX
  113.             ) {
  114.                 firstVisibleDateIndex = index > 0 ? index - 1 : index;
  115.             }
  116.  
  117.             if (
  118.                 lastVisibleDateIndex === undefined &&
  119.                 datePositionX >= scrollPositionX + screenWidth
  120.             ) {
  121.                 lastVisibleDateIndex = index;
  122.             }
  123.  
  124.             datePositionX += width;
  125.  
  126.             return !!(firstVisibleDateIndex && lastVisibleDateIndex);
  127.         });
  128.  
  129.         return dates.slice(firstVisibleDateIndex, lastVisibleDateIndex);
  130.     };
  131.  
  132.     getVisibleMonthAndYear = (): ?string => {
  133.         const {dates, visibleMonths, visibleYears} = this.state;
  134.  
  135.         //no visible month or year yet
  136.         if (!visibleMonths || !visibleYears) {
  137.             //return the month and year of the very first date
  138.             if (dates) {
  139.                 const firstDate = dates[0];
  140.                 return `${formatMonth(firstDate)}, ${formatYear(firstDate)}`;
  141.             }
  142.             return undefined;
  143.         }
  144.  
  145.         //one or two months within the same year
  146.         if (visibleYears.length === 1) {
  147.             return `${visibleMonths.join(' - ')}, ${visibleYears[0]}`;
  148.         }
  149.  
  150.         //two months within the same year
  151.         return visibleMonths
  152.             .map((month, index) => `${month}, ${visibleYears[index]}`)
  153.             .join(' - ');
  154.     };
  155.  
  156.     updateVisibleMonthAndYear = () => {
  157.         const {allDatesHaveRendered} = this.state;
  158.  
  159.         if (!allDatesHaveRendered) {
  160.             return;
  161.         }
  162.  
  163.         const visibleDates = this.getVisibleDates();
  164.  
  165.         if (!visibleDates) {
  166.             return;
  167.         }
  168.  
  169.         let visibleMonths = [];
  170.         let visibleYears = [];
  171.  
  172.         visibleDates.forEach((date: Moment) => {
  173.             const month = formatMonth(date);
  174.             const year = formatYear(date);
  175.             if (!visibleMonths.includes(month)) {
  176.                 visibleMonths.push(month);
  177.             }
  178.             if (!visibleYears.includes(year)) {
  179.                 visibleYears.push(year);
  180.             }
  181.         });
  182.  
  183.         this.setState({
  184.             visibleMonths,
  185.             visibleYears,
  186.         });
  187.     };
  188.  
  189.     scrollToCurrentDay = () => {
  190.         const {allDatesHaveRendered, currentDateIndex, dayWidths} = this.state;
  191.  
  192.         //making sure values are correct
  193.         if (
  194.             !allDatesHaveRendered ||
  195.             currentDateIndex === undefined ||
  196.             currentDateIndex === null
  197.         ) {
  198.             return;
  199.         }
  200.  
  201.         //put all widths value into simple array $FlowFixMe
  202.         const dayWidthsArray: Array<number> = Object.values(dayWidths);
  203.         // total width all days take
  204.         const allDaysWidths = dayWidthsArray.reduce(
  205.             (total, width) => width + total,
  206.             0,
  207.         );
  208.         //current day button width
  209.         const currentDayWidth = dayWidthsArray[currentDateIndex];
  210.         //minimal x value to prevent scrolling before first day
  211.         const minX = 0;
  212.         //maximum x value to prevent scrolling after last day
  213.         const maxX = allDaysWidths > screenWidth ? allDaysWidths - screenWidth : 0; // no scrolling if nowhere to scroll
  214.  
  215.         let scrollToX;
  216.  
  217.         scrollToX =
  218.             dayWidthsArray
  219.                 //get all days before target one
  220.                 .slice(0, currentDateIndex + 1)
  221.                 // and calculate total width
  222.                 .reduce((total, width) => width + total, 0) -
  223.             //subtract half of the screen so target day is centered
  224.             screenWidth / 2 -
  225.             currentDayWidth / 2;
  226.  
  227.         //do not scroll over left ledge
  228.         if (scrollToX < minX) {
  229.             scrollToX = 0;
  230.         } else if (scrollToX > maxX) {
  231.             scrollToX = maxX;
  232.         }
  233.  
  234.         this._scrollView.scrollTo({x: scrollToX});
  235.     };
  236.  
  237.     onSelectDay = (index: number) => {
  238.         const {dates} = this.state;
  239.         const {onSelectDate} = this.props;
  240.         this.setState({currentDateIndex: index}, this.scrollToCurrentDay);
  241.         onSelectDate(dates[index]);
  242.     };
  243.  
  244.     onRenderDay = (index: number, width: number) => {
  245.         const {dayWidths} = this.state;
  246.         const {showDaysBeforeCurrent, showDaysAfterCurrent} = this.props;
  247.  
  248.         //check whether all dates were rendered correctly
  249.         const allDatesHaveRendered =
  250.             dayWidths &&
  251.             Object.keys(dayWidths).length >=
  252.                 showDaysBeforeCurrent + showDaysAfterCurrent;
  253.  
  254.         this.setState(
  255.             prevState => ({
  256.                 allDatesHaveRendered,
  257.                 dayWidths: {
  258.                     //keep all existing widths added previously
  259.                     ...prevState.dayWidths,
  260.                     //keep index for calculating scrolling position for each day
  261.                     [index]: width,
  262.                 },
  263.             }),
  264.             () => {
  265.                 if (allDatesHaveRendered) {
  266.                     this.scrollToCurrentDay();
  267.                     this.updateVisibleMonthAndYear();
  268.                 }
  269.             },
  270.         );
  271.     };
  272.  
  273.     onScroll = (event: {
  274.         nativeEvent: {contentOffset: {x: number, y: number}},
  275.     }) => {
  276.         const {
  277.             nativeEvent: {
  278.                 contentOffset: {x},
  279.             },
  280.         } = event;
  281.         this.setState({scrollPositionX: x}, this.updateVisibleMonthAndYear);
  282.     };
  283.  
  284.     render() {
  285.         const {dates, currentDateIndex} = this.state;
  286.  
  287.         const visibleMonthAndYear = this.getVisibleMonthAndYear();
  288.  
  289.         return (
  290.             <View>
  291.                 <View>
  292.                     <Text style={styles.visibleMonthAndYear}>{visibleMonthAndYear}</Text>
  293.                 </View>
  294.  
  295.                 <ScrollView
  296.                     ref={scrollView => {
  297.                         this._scrollView = scrollView;
  298.                     }}
  299.                     horizontal={true}
  300.                     showHorizontalScrollIndicator={false}
  301.                     automaticallyAdjustContentInsets={false} //disables automatic content adjusting
  302.                     scrollEventThrottle={100}
  303.                     onScroll={this.onScroll}>
  304.                     <Dates
  305.                         dates={dates}
  306.                         currentDateIndex={currentDateIndex}
  307.                         onSelectDay={this.onSelectDay}
  308.                         onRenderDay={this.onRenderDay}
  309.                     />
  310.                 </ScrollView>
  311.             </View>
  312.         );
  313.     }
  314. }
  315.  
  316. const styles = StyleSheet.create({
  317.     visibleMonthAndYear: {
  318.         color: 'black', // '#ff8000',//'rgba(255, 255, 255, 0.5)',
  319.         fontSize: 20,
  320.         left: 15,
  321.         bottom: 15,
  322.         top: 1,
  323.         //paddingHorizontal: 25,
  324.         //position: 'absolute',
  325.         textAlign: 'left',
  326.     },
  327. });
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement