text: import React, {Component} from 'react';
  import {
    Text,
    View,
    Dimensions,
    Animated,
    ViewPropTypes,
  } from 'react-native';
  import PropTypes from 'prop-types';
  import XDate from 'xdate';
  
  import {parseDate, xdateToData} from '../interface';
  import dateutils from '../dateutils';
  import CalendarList from '../calendar-list';
  import ReservationsList from './reservation-list';
  import styleConstructor from './style';
  import { VelocityTracker } from '../input';
  
  const HEADER_HEIGHT = 104;
  const KNOB_HEIGHT = 24;
  
  //Fallback when RN version is < 0.44
  const viewPropTypes = ViewPropTypes || View.propTypes;
  
  export default class AgendaView extends Component {
    static propTypes = {
      // Specify theme properties to override specific styles for calendar parts. Default = {}
      theme: PropTypes.object,
  
      // agenda container style
      style: viewPropTypes.style,
  
      // the list of items that have to be displayed in agenda. If you want to render item as empty date
      // the value of date key has to be an empty array []. If there exists no value for date key it is
      // considered that the date in question is not yet loaded
      items: PropTypes.object,
  
      // callback that gets called when items for a certain month should be loaded (month became visible)
      loadItemsForMonth: PropTypes.func,
      // callback that fires when the calendar is opened or closed
      onCalendarToggled: PropTypes.func,
      // callback that gets called on day press
      onDayPress: PropTypes.func,
      // callback that gets called when day changes while scrolling agenda list
      onDaychange: PropTypes.func,
      // specify how each item should be rendered in agenda
      renderItem: PropTypes.func,
      // specify how each date should be rendered. day can be undefined if the item is not first in that day.
      renderDay: PropTypes.func,
      // specify how agenda knob should look like
      renderKnob: PropTypes.func,
      // specify how empty date content with no items should be rendered
      renderEmptyDay: PropTypes.func,
      // specify what should be rendered instead of ActivityIndicator
      renderEmptyData: PropTypes.func,
      // specify your item comparison function for increased performance
      rowHasChanged: PropTypes.func,
  
      // 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,
  
      // initially selected day
      selected: PropTypes.any,
      // Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined
      minDate: PropTypes.any,
      // Maximum date that can be selected, dates after maxDate will be grayed out. Default = undefined
      maxDate: PropTypes.any,
  
      // If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
      firstDay: PropTypes.number,
  
      // Collection of dates that have to be marked. Default = items
      markedDates: PropTypes.object,
      // Optional marking type if custom markedDates are provided
      markingType: PropTypes.string,
  
      // Hide knob button. Default = false
      hideKnob: PropTypes.bool,
      // Month format in calendar title. Formatting values: http://arshaw.com/xdate/#Formatting
      monthFormat: PropTypes.string,
      // A RefreshControl component, used to provide pull-to-refresh functionality for the ScrollView.
      refreshControl: PropTypes.element,
      // If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make sure to also set the refreshing prop correctly.
      onRefresh: PropTypes.func,
      // Set this true while waiting for new data from a refresh.
      refreshing: PropTypes.bool,
      // Display loading indicador. Default = false
      displayLoadingIndicator: PropTypes.bool,
    };
  
    constructor(props) {
      super(props);
      this.styles = styleConstructor(props.theme);
      const windowSize = Dimensions.get('window');
      this.viewHeight = windowSize.height;
      this.viewWidth = windowSize.width;
      this.scrollTimeout = undefined;
      this.headerState = 'idle';
      this.state = {
        scrollY: new Animated.Value(0),
        calendarIsReady: false,
        calendarScrollable: false,
        firstResevationLoad: false,
        selectedDay: parseDate(this.props.selected) || XDate(true),
        topDay: parseDate(this.props.selected) || XDate(true),
      };
      this.currentMonth = this.state.selectedDay.clone();
      this.onLayout = this.onLayout.bind(this);
      this.onScrollPadLayout = this.onScrollPadLayout.bind(this);
      this.onTouchStart = this.onTouchStart.bind(this);
      this.onTouchEnd = this.onTouchEnd.bind(this);
      this.onStartDrag = this.onStartDrag.bind(this);
      this.onSnapAfterDrag = this.onSnapAfterDrag.bind(this);
      this.generateMarkings = this.generateMarkings.bind(this);
      this.knobTracker = new VelocityTracker();
      this.state.scrollY.addListener(({value}) => this.knobTracker.add(value));
    }
  
    calendarOffset() {
      return 90 - (this.viewHeight / 2);
    }
  
    initialScrollPadPosition() {
      return Math.max(0, this.viewHeight - HEADER_HEIGHT);
    }
  
    setScrollPadPosition(y, animated) {
      this.scrollPad._component.scrollTo({x: 0, y, animated});
    }
  
    onScrollPadLayout() {
      // When user touches knob, the actual component that receives touch events is a ScrollView.
      // It needs to be scrolled to the bottom, so that when user moves finger downwards,
      // scroll position actually changes (it would stay at 0, when scrolled to the top).
      this.setScrollPadPosition(this.initialScrollPadPosition(), false);
      // delay rendering calendar in full height because otherwise it still flickers sometimes
      setTimeout(() => this.setState({calendarIsReady: true}), 0);
    }
  
    onLayout(event) {
      this.viewHeight = event.nativeEvent.layout.height;
      this.viewWidth = event.nativeEvent.layout.width;
      this.forceUpdate();
    }
  
    onTouchStart() {
      this.headerState = 'touched';
      if (this.knob) {
        this.knob.setNativeProps({style: { opacity: 0.5 }});
      }
    }
  
    onTouchEnd() {
      if (this.knob) {
        this.knob.setNativeProps({style: { opacity: 1 }});
      }
  
      if (this.headerState === 'touched') {
        this.setScrollPadPosition(0, true);
        this.enableCalendarScrolling();
      }
      this.headerState = 'idle';
    }
  
    onStartDrag() {
      this.headerState = 'dragged';
      this.knobTracker.reset();
    }
  
    onSnapAfterDrag(e) {
      // on Android onTouchEnd is not called if dragging was started
      this.onTouchEnd();
      const currentY = e.nativeEvent.contentOffset.y;
      this.knobTracker.add(currentY);
      const projectedY = currentY + this.knobTracker.estimateSpeed() * 250/*ms*/;
      const maxY = this.initialScrollPadPosition();
      const snapY = (projectedY > maxY / 2) ? maxY : 0;
      this.setScrollPadPosition(snapY, true);
      if (snapY === 0) {
        this.enableCalendarScrolling();
      }
    }
  
    onVisibleMonthsChange(months) {
      if (this.props.items && !this.state.firstResevationLoad) {
        clearTimeout(this.scrollTimeout);
        this.scrollTimeout = setTimeout(() => {
          if (this.props.loadItemsForMonth && this._isMounted) {
            this.props.loadItemsForMonth(months[0]);
          }
        }, 200);
      }
    }
  
    loadReservations(props) {
      if ((!props.items || !Object.keys(props.items).length) && !this.state.firstResevationLoad) {
        this.setState({
          firstResevationLoad: true
        }, () => {
          if (this.props.loadItemsForMonth) {
            this.props.loadItemsForMonth(xdateToData(this.state.selectedDay));
          }
        });
      }
    }
  
    componentWillMount() {
      this._isMounted = true;
      this.loadReservations(this.props);
    }
  
    componentWillUnmount() {
      this._isMounted = false;
    }
  
    componentWillReceiveProps(props) {
      if (props.items) {
        this.setState({
          firstResevationLoad: false
        });
      } else {
        this.loadReservations(props);
      }
    }
  
    enableCalendarScrolling() {
      this.setState({
        calendarScrollable: true
      });
      if (this.props.onCalendarToggled) {
        this.props.onCalendarToggled(true);
      }
      // Enlarge calendarOffset here as a workaround on iOS to force repaint.
      // Otherwise the month after current one or before current one remains invisible.
      // The problem is caused by overflow: 'hidden' style, which we need for dragging
      // to be performant.
      // Another working solution for this bug would be to set removeClippedSubviews={false}
      // in CalendarList listView, but that might impact performance when scrolling
      // month list in expanded CalendarList.
      // Further info https://github.com/facebook/react-native/issues/1831
      this.calendar.scrollToDay(this.state.selectedDay, this.calendarOffset() + 1, true);
    }
  
    _chooseDayFromCalendar(d) {
      this.chooseDay(d, !this.state.calendarScrollable);
    }
  
    chooseDay(d, optimisticScroll) {
      const day = parseDate(d);
      this.setState({
        calendarScrollable: false,
        selectedDay: day.clone()
      });
      if (this.props.onCalendarToggled) {
        this.props.onCalendarToggled(false);
      }
      if (!optimisticScroll) {
        this.setState({
          topDay: day.clone()
        });
      }
      this.setScrollPadPosition(this.initialScrollPadPosition(), true);
      this.calendar.scrollToDay(day, this.calendarOffset(), true);
      if (this.props.loadItemsForMonth) {
        this.props.loadItemsForMonth(xdateToData(day));
      }
      if (this.props.onDayPress) {
        this.props.onDayPress(xdateToData(day));
      }
    }
  
    renderReservations() {
      return (
        <ReservationsList
          refreshControl={this.props.refreshControl}
          refreshing={this.props.refreshing}
          onRefresh={this.props.onRefresh}
          rowHasChanged={this.props.rowHasChanged}
          renderItem={this.props.renderItem}
          renderDay={this.props.renderDay}
          renderEmptyDate={this.props.renderEmptyDate}
          reservations={this.props.items}
          selectedDay={this.state.selectedDay}
          renderEmptyData={this.props.renderEmptyData}
          topDay={this.state.topDay}
          onDayChange={this.onDayChange.bind(this)}
          onScroll={() => {}}
          ref={(c) => this.list = c}
          theme={this.props.theme}
        />
      );
    }
  
    onDayChange(day) {
      const newDate = parseDate(day);
      const withAnimation = dateutils.sameMonth(newDate, this.state.selectedDay);
      this.calendar.scrollToDay(day, this.calendarOffset(), withAnimation);
      this.setState({
        selectedDay: parseDate(day)
      });
  
      if (this.props.onDayChange) {
        this.props.onDayChange(xdateToData(newDate));
      }
    }
  
    generateMarkings() {
      let markings = this.props.markedDates;
      if (!markings) {
        markings = {};
        Object.keys(this.props.items  || {}).forEach(key => {
          if (this.props.items[key] && this.props.items[key].length) {
            markings[key] = {marked: true};
          }
        });
      }
      const key = this.state.selectedDay.toString('yyyy-MM-dd');
      return {...markings, [key]: {...(markings[key] || {}), ...{selected: true}}};
    }
  
    render() {
      const agendaHeight = Math.max(0, this.viewHeight - HEADER_HEIGHT);
      const weekDaysNames = dateutils.weekDayNames(this.props.firstDay);
      const weekdaysStyle = [this.styles.weekdays, {
        opacity: this.state.scrollY.interpolate({
          inputRange: [agendaHeight - HEADER_HEIGHT, agendaHeight],
          outputRange: [0, 1],
          extrapolate: 'clamp',
        }),
        transform: [{ translateY: this.state.scrollY.interpolate({
          inputRange: [Math.max(0, agendaHeight - HEADER_HEIGHT), agendaHeight],
          outputRange: [-HEADER_HEIGHT, 0],
          extrapolate: 'clamp',
        })}]
      }];
  
      const headerTranslate = this.state.scrollY.interpolate({
        inputRange: [0, agendaHeight],
        outputRange: [agendaHeight, 0],
        extrapolate: 'clamp',
      });
  
      const contentTranslate = this.state.scrollY.interpolate({
        inputRange: [0, agendaHeight],
        outputRange: [0, agendaHeight/2],
        extrapolate: 'clamp',
      });
  
      const headerStyle = [
        this.styles.header,
        { bottom: agendaHeight, transform: [{ translateY: headerTranslate }] },
      ];
  
      if (!this.state.calendarIsReady) {
        // limit header height until everything is setup for calendar dragging
        headerStyle.push({height: 0});
        // fill header with appStyle.calendarBackground background to reduce flickering
        weekdaysStyle.push({height: HEADER_HEIGHT});
      }
  
      const shouldAllowDragging = !this.props.hideKnob && !this.state.calendarScrollable;
      const scrollPadPosition = (shouldAllowDragging ? HEADER_HEIGHT  : 0) - KNOB_HEIGHT;
  
      const scrollPadStyle = {
        position: 'absolute',
        width: 80,
        height: KNOB_HEIGHT,
        top: scrollPadPosition,
        left: (this.viewWidth - 80) / 2,
      };
  
      let knob = (<View style={this.styles.knobContainer}/>);
  
      if (!this.props.hideKnob) {
        const knobView = this.props.renderKnob ? this.props.renderKnob() : (<View style={this.styles.knob}/>);
        knob = this.state.calendarScrollable ? null : (
          <View style={this.styles.knobContainer}>
            <View ref={(c) => this.knob = c}>{knobView}</View>
          </View>
        );
      }
  
      return (
        <View onLayout={this.onLayout} style={[this.props.style, {flex: 1, overflow: 'hidden'}]}>
          <View style={this.styles.reservations}>
            {this.renderReservations()}
          </View>
          <Animated.View style={headerStyle}>
            <Animated.View style={{flex:1, transform: [{ translateY: contentTranslate }]}}>
              <CalendarList
                onLayout={() => {
                  this.calendar.scrollToDay(this.state.selectedDay.clone(), this.calendarOffset(), false);
                }}
                theme={this.props.theme}
                onVisibleMonthsChange={this.onVisibleMonthsChange.bind(this)}
                ref={(c) => this.calendar = c}
                minDate={this.props.minDate}
                maxDate={this.props.maxDate}
                current={this.currentMonth}
                markedDates={this.generateMarkings()}
                markingType={this.props.markingType}
                removeClippedSubviews={this.props.removeClippedSubviews}
                onDayPress={this._chooseDayFromCalendar.bind(this)}
                scrollingEnabled={this.state.calendarScrollable}
                hideExtraDays={this.state.calendarScrollable}
                firstDay={this.props.firstDay}
                monthFormat={this.props.monthFormat}
                pastScrollRange={this.props.pastScrollRange}
                futureScrollRange={this.props.futureScrollRange}
                dayComponent={this.props.dayComponent}
                disabledByDefault={this.props.disabledByDefault}
                displayLoadingIndicator={this.props.displayLoadingIndicator}
                showWeekNumbers={this.props.showWeekNumbers}
              />
            </Animated.View>
            {knob}
          </Animated.View>
          <Animated.View style={weekdaysStyle}>
            {this.props.showWeekNumbers && <Text allowFontScaling={false} style={this.styles.weekday} numberOfLines={1}></Text>}
            {weekDaysNames.map((day) => (
              <Text allowFontScaling={false} key={day} style={this.styles.weekday} numberOfLines={1}>{day}</Text>
            ))}
          </Animated.View>
          <Animated.ScrollView
            ref={c => this.scrollPad = c}
            overScrollMode='never'
            showsHorizontalScrollIndicator={false}
            showsVerticalScrollIndicator={false}
            style={scrollPadStyle}
            scrollEventThrottle={1}
            scrollsToTop={false}
            onTouchStart={this.onTouchStart}
            onTouchEnd={this.onTouchEnd}
            onScrollBeginDrag={this.onStartDrag}
            onScrollEndDrag={this.onSnapAfterDrag}
            onScroll={Animated.event(
              [{ nativeEvent: { contentOffset: { y: this.state.scrollY } } }],
              { useNativeDriver: true },
            )}
          >
            <View style={{height: agendaHeight + KNOB_HEIGHT}} onLayout={this.onScrollPadLayout} />
          </Animated.ScrollView>
        </View>
      );
    }
  }
  