diff --git a/src/Events/Events.js b/src/Events/Events.js index 55b62cbe..b6e1c92c 100644 --- a/src/Events/Events.js +++ b/src/Events/Events.js @@ -8,7 +8,6 @@ import NowLine from '../NowLine/NowLine'; import Event from '../Event/Event'; import { CONTAINER_HEIGHT, - CONTAINER_WIDTH, calculateDaysArray, DATE_STR_FORMAT, availableNumberOfDays, @@ -20,11 +19,15 @@ import { import styles from './Events.styles'; const MINUTES_IN_HOUR = 60; -const EVENT_HORIZONTAL_PADDING = 15; -const EVENTS_CONTAINER_WIDTH = CONTAINER_WIDTH - EVENT_HORIZONTAL_PADDING; +const EVENT_HORIZONTAL_PADDING = 8; // percentage const MIN_ITEM_WIDTH = 4; const ALLOW_OVERLAP_SECONDS = 2; +const padItemWidth = ( + width, + paddingPercentage = EVENT_HORIZONTAL_PADDING, +) => paddingPercentage > 0 ? width - Math.max(2, width * paddingPercentage / 100) : width; + const areEventsOverlapped = (event1EndDate, event2StartDate) => { const endDate = moment(event1EndDate); endDate.subtract(ALLOW_OVERLAP_SECONDS, 'seconds'); @@ -65,11 +68,9 @@ const addOverlappedToArray = (baseArr, overlappedArr, itemWidth) => { } let nLanes; - let horizontalPadding; let indexToLane; if (nOverlapped === 2) { nLanes = nOverlapped; - horizontalPadding = 3; indexToLane = (index) => index; } else { // Distribute events in multiple lanes @@ -98,11 +99,10 @@ const addOverlappedToArray = (baseArr, overlappedArr, itemWidth) => { }); nLanes = Object.keys(latestByLane).length; - horizontalPadding = 2; indexToLane = (index) => laneByEvent[index]; } const dividedWidth = itemWidth / nLanes; - const width = Math.max(dividedWidth - horizontalPadding, MIN_ITEM_WIDTH); + const width = Math.max(padItemWidth(dividedWidth, EVENT_HORIZONTAL_PADDING / nLanes), MIN_ITEM_WIDTH); overlappedArr.forEach((eventWithStyle, index) => { const { data, style } = eventWithStyle; @@ -118,13 +118,14 @@ const addOverlappedToArray = (baseArr, overlappedArr, itemWidth) => { }; const getEventsWithPosition = ( - totalEvents, regularItemWidth, hoursInDisplay, beginAgendaAt, + totalEvents, dayWidth, hoursInDisplay, beginAgendaAt, ) => { + const paddedDayWidth = padItemWidth(dayWidth); return totalEvents.map((events) => { let overlappedSoFar = []; // Store events overlapped until now let lastDate = null; const eventsWithStyle = events.reduce((eventsAcc, event) => { - const style = getStyleForEvent(event, regularItemWidth, hoursInDisplay, beginAgendaAt); + const style = getStyleForEvent(event, paddedDayWidth, hoursInDisplay, beginAgendaAt); const eventWithStyle = { data: event, style, @@ -138,7 +139,7 @@ const getEventsWithPosition = ( addOverlappedToArray( eventsAcc, overlappedSoFar, - regularItemWidth, + dayWidth, ); overlappedSoFar = [eventWithStyle]; lastDate = moment(event.endDate); @@ -148,12 +149,34 @@ const getEventsWithPosition = ( addOverlappedToArray( eventsWithStyle, overlappedSoFar, - regularItemWidth, + dayWidth, ); return eventsWithStyle; }); }; +const processEvents = ( + eventsByDate, initialDate, numberOfDays, dayWidth, hoursInDisplay, + rightToLeft, beginAgendaAt, +) => { + // totalEvents stores events in each day of numberOfDays + // example: [[event1, event2], [event3, event4], [event5]], each child array + // is events for specific day in range + const dates = calculateDaysArray(initialDate, numberOfDays, rightToLeft); + const totalEvents = dates.map((date) => { + const dateStr = date.format(DATE_STR_FORMAT); + return eventsByDate[dateStr] || []; + }); + + const totalEventsWithPosition = getEventsWithPosition( + totalEvents, + dayWidth, + hoursInDisplay, + beginAgendaAt, + ); + return totalEventsWithPosition; +}; + class Events extends PureComponent { yToHour = (y) => { const { hoursInDisplay, beginAgendaAt } = this.props; @@ -168,30 +191,7 @@ class Events extends PureComponent { return fullWidth / numberOfDays; }; - processEvents = memoizeOne( - (eventsByDate, initialDate, numberOfDays, hoursInDisplay, rightToLeft, - beginAgendaAt, - ) => { - // totalEvents stores events in each day of numberOfDays - // example: [[event1, event2], [event3, event4], [event5]], each child array - // is events for specific day in range - const dates = calculateDaysArray(initialDate, numberOfDays, rightToLeft); - const totalEvents = dates.map((date) => { - const dateStr = date.format(DATE_STR_FORMAT); - return eventsByDate[dateStr] || []; - }); - - const regularItemWidth = this.getEventItemWidth(); - - const totalEventsWithPosition = getEventsWithPosition( - totalEvents, - regularItemWidth, - hoursInDisplay, - beginAgendaAt, - ); - return totalEventsWithPosition; - }, - ); + processEvents = memoizeOne(processEvents); onGridTouch = (event, dayIndex, longPress) => { const { initialDate, onGridClick, onGridLongPress } = this.props; @@ -220,12 +220,13 @@ class Events extends PureComponent { }; onDragEvent = (event, newX, newY) => { - const { onDragEvent } = this.props; + const { onDragEvent, dayWidth } = this.props; if (!onDragEvent) { return; } - const movedDays = Math.floor(newX / this.getEventItemWidth()); + // NOTE: newX is in the eventsColumn coordinates + const movedDays = Math.floor(newX / dayWidth); const startTime = event.startDate.getTime(); const newStartDate = new Date(startTime); @@ -268,11 +269,14 @@ class Events extends PureComponent { showNowLine, nowLineColor, onDragEvent, + dayWidth, + pageWidth, } = this.props; const totalEvents = this.processEvents( eventsByDate, initialDate, numberOfDays, + dayWidth, hoursInDisplay, rightToLeft, beginAgendaAt, @@ -280,7 +284,7 @@ class Events extends PureComponent { const timeSlotHeight = getTimeLabelHeight(hoursInDisplay, timeStep); return ( - + {times.map((time) => ( )} @@ -359,6 +363,8 @@ Events.propTypes = { showNowLine: PropTypes.bool, nowLineColor: PropTypes.string, onDragEvent: PropTypes.func, + pageWidth: PropTypes.number.isRequired, + dayWidth: PropTypes.number.isRequired, }; export default Events; diff --git a/src/Events/Events.styles.js b/src/Events/Events.styles.js index 2fe0ac1f..831c2514 100644 --- a/src/Events/Events.styles.js +++ b/src/Events/Events.styles.js @@ -1,5 +1,5 @@ import { StyleSheet } from 'react-native'; -import { CONTAINER_WIDTH, CONTENT_OFFSET } from '../utils'; +import { CONTENT_OFFSET } from '../utils'; const GREY_COLOR = '#E9EDF0'; @@ -7,7 +7,6 @@ const styles = StyleSheet.create({ container: { flex: 1, paddingTop: CONTENT_OFFSET, - width: CONTAINER_WIDTH, }, timeRow: { flex: 0, diff --git a/src/Times/Times.js b/src/Times/Times.js index 3ce50e0b..a8c12348 100644 --- a/src/Times/Times.js +++ b/src/Times/Times.js @@ -4,10 +4,17 @@ import { View, Text } from 'react-native'; import styles from './Times.styles'; import { getTimeLabelHeight } from '../utils'; -const Times = ({ times, hoursInDisplay, timeStep, textStyle }) => { +const Times = ({ + times, hoursInDisplay, timeStep, textStyle, width, +}) => { const height = getTimeLabelHeight(hoursInDisplay, timeStep); return ( - + {times.map((time) => ( {time} @@ -22,6 +29,7 @@ Times.propTypes = { hoursInDisplay: PropTypes.number.isRequired, timeStep: PropTypes.number.isRequired, textStyle: Text.propTypes.style, + width: PropTypes.number.isRequired, }; export default React.memo(Times); diff --git a/src/Times/Times.styles.js b/src/Times/Times.styles.js index 5503b974..792b9f8a 100644 --- a/src/Times/Times.styles.js +++ b/src/Times/Times.styles.js @@ -3,7 +3,6 @@ import { StyleSheet } from 'react-native'; const styles = StyleSheet.create({ columnContainer: { paddingTop: 10, - width: 60, }, label: { flex: -1, diff --git a/src/Title/Title.js b/src/Title/Title.js index 89392ac2..1bf60385 100644 --- a/src/Title/Title.js +++ b/src/Title/Title.js @@ -13,7 +13,7 @@ const getFontSizeHeader = (numberOfDays) => { }; const Title = ({ - style, showTitle, numberOfDays, selectedDate, textStyle, onMonthPress, + style, showTitle, numberOfDays, selectedDate, textStyle, onMonthPress, width, }) => { if (!showTitle) { return @@ -21,7 +21,7 @@ const Title = ({ const formattedMonth = getCurrentMonth(selectedDate); return ( onMonthPress && onMonthPress(selectedDate, formattedMonth)} disabled={!onMonthPress} > @@ -47,6 +47,7 @@ Title.propTypes = { style: PropTypes.object, textStyle: PropTypes.object, onMonthPress: PropTypes.func, + width: PropTypes.number.isRequired, }; export default React.memo(Title); diff --git a/src/Title/Title.styles.js b/src/Title/Title.styles.js index 208414ff..35aca351 100644 --- a/src/Title/Title.styles.js +++ b/src/Title/Title.styles.js @@ -4,7 +4,6 @@ const styles = StyleSheet.create({ title: { justifyContent: 'center', alignItems: 'center', - width: 60, borderTopWidth: 1, }, }); diff --git a/src/WeekView/WeekView.js b/src/WeekView/WeekView.js index 6f63a581..24012201 100644 --- a/src/WeekView/WeekView.js +++ b/src/WeekView/WeekView.js @@ -21,8 +21,8 @@ import { DATE_STR_FORMAT, availableNumberOfDays, setLocale, - CONTAINER_WIDTH, minutesToYDimension, + computeWeekViewDimensions, } from '../utils'; const MINUTES_IN_DAY = 60 * 24; @@ -366,11 +366,16 @@ export default class WeekView extends Component { return sortedEvents; }); - getListItemLayout = (index) => ({ - length: CONTAINER_WIDTH, - offset: CONTAINER_WIDTH * index, - index, - }); + updateDimensions = memoizeOne(computeWeekViewDimensions); + + getListItemLayout = (item, index) => { + const pageWidth = this.dimensions.pageWidth || 0; + return { + length: pageWidth, + offset: pageWidth * index, + index, + }; + }; render() { const { @@ -414,6 +419,13 @@ export default class WeekView extends Component { (prependMostRecent && !rightToLeft) || (!prependMostRecent && rightToLeft); + this.dimensions = this.updateDimensions(numberOfDays); + const { + pageWidth, + dayWidth, + timeLabelsWidth, + } = this.dimensions; + return ( @@ -424,6 +436,7 @@ export default class WeekView extends Component { numberOfDays={numberOfDays} selectedDate={currentMoment} onMonthPress={onMonthPress} + width={timeLabelsWidth} /> data[index]} getItemCount={(data) => data.length} - getItemLayout={(_, index) => this.getListItemLayout(index)} + getItemLayout={this.getListItemLayout} keyExtractor={(item) => item} initialScrollIndex={this.pageOffset} renderItem={({ item }) => { return ( - +
{isRefreshing && RefreshComponent && ( - + )} false} @@ -471,12 +486,13 @@ export default class WeekView extends Component { textStyle={hourTextStyle} hoursInDisplay={hoursInDisplay} timeStep={timeStep} + width={timeLabelsWidth} /> data[index]} getItemCount={(data) => data.length} - getItemLayout={(_, index) => this.getListItemLayout(index)} + getItemLayout={this.getListItemLayout} keyExtractor={(item) => item} initialScrollIndex={this.pageOffset} scrollEnabled={!fixedHorizontally} @@ -505,6 +521,8 @@ export default class WeekView extends Component { showNowLine={showNowLine} nowLineColor={nowLineColor} onDragEvent={onDragEvent} + pageWidth={pageWidth} + dayWidth={dayWidth} /> ); }} diff --git a/src/WeekView/WeekView.styles.js b/src/WeekView/WeekView.styles.js index 06592e96..ed100578 100644 --- a/src/WeekView/WeekView.styles.js +++ b/src/WeekView/WeekView.styles.js @@ -1,5 +1,5 @@ import { StyleSheet } from 'react-native'; -import { CONTAINER_WIDTH, CONTAINER_HEIGHT } from '../utils'; +import { CONTAINER_HEIGHT } from '../utils'; const styles = StyleSheet.create({ container: { @@ -16,12 +16,10 @@ const styles = StyleSheet.create({ height: 50, justifyContent: 'center', alignItems: 'center', - width: CONTAINER_WIDTH, }, loadingSpinner: { position: 'absolute', top: CONTAINER_HEIGHT / 2, - right: CONTAINER_WIDTH / 2, zIndex: 2, }, }); diff --git a/src/utils.js b/src/utils.js index 19d840f3..e35391a0 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,13 +1,33 @@ import { Dimensions } from 'react-native'; import moment from 'moment'; -const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window'); +const { height: SCREEN_HEIGHT } = Dimensions.get('window'); export const CONTENT_OFFSET = 16; export const CONTAINER_HEIGHT = SCREEN_HEIGHT - 60; -export const CONTAINER_WIDTH = SCREEN_WIDTH - 60; export const DATE_STR_FORMAT = 'YYYY-MM-DD'; export const availableNumberOfDays = [1, 3, 5, 7]; +const TIMES_WIDTH_PERCENTAGE = 18; +const PAGE_WIDTH_PERCENTAGE = (100 - TIMES_WIDTH_PERCENTAGE) / 100; + +export const computeWeekViewDimensions = (numberOfDays) => { + const { width: screenWidth } = Dimensions.get('window'); + + // Each day must have an equal width (integer pixels) + const dayWidth = Math.floor(screenWidth * PAGE_WIDTH_PERCENTAGE / numberOfDays); + const pageWidth = numberOfDays * dayWidth; + + // Fill the full screen + const timeLabelsWidth = screenWidth - pageWidth; + + const dimensions = { + pageWidth, + timeLabelsWidth, + dayWidth, + }; + return dimensions; +} + export const minutesToYDimension = (hoursInDisplay, minutes) => { const minutesInDisplay = 60 * hoursInDisplay; return (minutes * CONTAINER_HEIGHT) / minutesInDisplay;