Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: add now-line #78

Merged
merged 7 commits into from
Apr 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ const MyComponent = () => (
* `startHour` _(Number)_ - hour clicked (as integer)
* `date` _(Date)_ - date object indicating day clicked (the hour is not relevant)
* **`EventComponent`** _(React.Component)_ - Component rendered inside an event. By default, is a `Text` with the `event.description`. See below for details on the component.
* **`showNowLine`** _(Boolean)_ - If true, displays a line indicating the time right now. Defaults to false.
* **`nowLineColor`** _(String)_ - Color used for the now-line. Defaults to a red "#e53935"
* **`rightToLeft`** _(Boolean)_ - If true, render older days to the right and more recent days to the left.
* **`prependMostRecent`** _(Boolean)_ - If true, the horizontal prepending is done in the most recent dates. See [issue #39](https://github.com/hoangnm/react-native-week-view/issues/39) for more details. Default is false.

Expand Down
3 changes: 3 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Changelog

## Unreleased
### Added
* "Now line" is displayed


## 0.3.0 - 2021-02-13
### Changed
Expand Down
1 change: 1 addition & 0 deletions example/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class App extends React.Component {
formatDateHeader="MMM D"
hoursInDisplay={12}
startHour={8}
showNowLine
/>
</SafeAreaView>
</>
Expand Down
40 changes: 29 additions & 11 deletions src/Events/Events.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { View, TouchableWithoutFeedback } from 'react-native';
import moment from 'moment';
import memoizeOne from 'memoize-one';

import NowLine from '../NowLine/NowLine';
import Event from '../Event/Event';
import {
TIME_LABEL_HEIGHT,
Expand All @@ -12,9 +13,11 @@ import {
calculateDaysArray,
DATE_STR_FORMAT,
availableNumberOfDays,
minutesToYDimension,
CONTENT_OFFSET,
} from '../utils';

import styles, { CONTENT_OFFSET } from './Events.styles';
import styles from './Events.styles';

const MINUTES_IN_HOUR = 60;
const EVENT_HORIZONTAL_PADDING = 15;
Expand All @@ -30,13 +33,15 @@ const areEventsOverlapped = (event1EndDate, event2StartDate) => {

class Events extends PureComponent {
getStyleForEvent = (item) => {
const { hoursInDisplay } = this.props;

const startDate = moment(item.startDate);
const startHours = startDate.hours();
const startMinutes = startDate.minutes();
const totalStartMinutes = startHours * MINUTES_IN_HOUR + startMinutes;
const top = this.minutesToYDimension(totalStartMinutes);
const top = minutesToYDimension(hoursInDisplay, totalStartMinutes);
const deltaMinutes = moment(item.endDate).diff(item.startDate, 'minutes');
const height = this.minutesToYDimension(deltaMinutes);
const height = minutesToYDimension(hoursInDisplay, deltaMinutes);
const width = this.getEventItemWidth();

return {
Expand Down Expand Up @@ -151,21 +156,16 @@ class Events extends PureComponent {
});
};

minutesToYDimension = (minutes) => {
const { hoursInDisplay } = this.props;
const minutesInDisplay = MINUTES_IN_HOUR * hoursInDisplay;
return (minutes * CONTAINER_HEIGHT) / minutesInDisplay;
};

yToHour = (y) => {
const { hoursInDisplay } = this.props;
const hour = (y * hoursInDisplay) / CONTAINER_HEIGHT;
return hour;
};

getEventItemWidth = () => {
getEventItemWidth = (padded = true) => {
const { numberOfDays } = this.props;
return EVENTS_CONTAINER_WIDTH / numberOfDays;
const fullWidth = padded ? EVENTS_CONTAINER_WIDTH : CONTAINER_WIDTH;
return fullWidth / numberOfDays;
};

processEvents = memoizeOne(
Expand Down Expand Up @@ -196,6 +196,12 @@ class Events extends PureComponent {
onGridClick(event, hour, date);
};

isToday = (dayIndex) => {
const { initialDate } = this.props;
const today = moment();
return moment(initialDate).add(dayIndex, 'days').isSame(today, 'day');
}

render() {
const {
eventsByDate,
Expand All @@ -206,6 +212,9 @@ class Events extends PureComponent {
eventContainerStyle,
EventComponent,
rightToLeft,
hoursInDisplay,
showNowLine,
nowLineColor,
} = this.props;
const totalEvents = this.processEvents(
eventsByDate,
Expand All @@ -231,6 +240,13 @@ class Events extends PureComponent {
key={dayIndex}
>
<View style={styles.event}>
{showNowLine && this.isToday(dayIndex) && (
<NowLine
color={nowLineColor}
hoursInDisplay={hoursInDisplay}
width={this.getEventItemWidth(false)}
/>
)}
{eventsInSection.map((item) => (
<Event
key={item.data.id}
Expand Down Expand Up @@ -262,6 +278,8 @@ Events.propTypes = {
eventContainerStyle: PropTypes.object,
EventComponent: PropTypes.elementType,
rightToLeft: PropTypes.bool,
showNowLine: PropTypes.bool,
nowLineColor: PropTypes.string,
};

export default Events;
4 changes: 1 addition & 3 deletions src/Events/Events.styles.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { StyleSheet } from 'react-native';
import { CONTAINER_WIDTH } from '../utils';
import { CONTAINER_WIDTH, CONTENT_OFFSET } from '../utils';

const GREY_COLOR = '#E9EDF0';
export const CONTENT_OFFSET = 16;

const styles = StyleSheet.create({
container: {
Expand All @@ -22,7 +21,6 @@ const styles = StyleSheet.create({
},
event: {
flex: 1,
overflow: 'hidden',
borderColor: GREY_COLOR,
borderLeftWidth: 1,
},
Expand Down
87 changes: 87 additions & 0 deletions src/NowLine/NowLine.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react';
import { Text, View, Animated } from 'react-native';
import PropTypes from 'prop-types';

import {
minutesToYDimension,
CONTENT_OFFSET,
} from '../utils';
import styles from './NowLine.styles';

const UPDATE_EVERY_MILLISECONDS = 60 * 1000; // 1 minute

const getCurrentTop = (hoursInDisplay) => {
const now = new Date();
const minutes = now.getHours() * 60 + now.getMinutes();
return minutesToYDimension(hoursInDisplay, minutes) + CONTENT_OFFSET;
};

class NowLine extends React.Component {
constructor(props) {
super(props);

this.initialTop = getCurrentTop(this.props.hoursInDisplay);

this.state = {
currentTranslateY: new Animated.Value(0),
}

this.intervalCallbackId = null;
}

componentDidMount() {
this.intervalCallbackId = setInterval(() => {
hoangnm marked this conversation as resolved.
Show resolved Hide resolved
const newTop = getCurrentTop(this.props.hoursInDisplay);
Animated.timing(this.state.currentTranslateY, {
toValue: newTop - this.initialTop,
duration: 1000,
useNativeDriver: true,
isInteraction: false,
}).start();
}, UPDATE_EVERY_MILLISECONDS);
}

componentWillUnmount() {
if (this.intervalCallbackId) {
clearInterval(this.intervalCallbackId);
}
}

render() {
const { color, width } = this.props;

return (
<Animated.View
style={[
styles.container,
{
top: this.initialTop,
transform: [{ translateY: this.state.currentTranslateY }],
borderColor: color,
width,
},
]}
>
<View
style={[
styles.circle,
{
backgroundColor: color,
},
]}/>
</Animated.View>
);
}
}

NowLine.propTypes = {
width: PropTypes.number.isRequired,
hoursInDisplay: PropTypes.number.isRequired,
color: PropTypes.string,
};

NowLine.defaultProps = {
color: '#e53935',
};

export default React.memo(NowLine);
22 changes: 22 additions & 0 deletions src/NowLine/NowLine.styles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { StyleSheet } from 'react-native';

const circleSize = 8;
const lineWidth = 1.5;

const styles = StyleSheet.create({
container: {
position: 'absolute',
zIndex: 2,
borderTopWidth: lineWidth,
},
circle: {
position: 'absolute',
top: -(circleSize + lineWidth) / 2,
left: 2,
borderRadius: circleSize,
height: circleSize,
width: circleSize,
},
});

export default styles;
13 changes: 12 additions & 1 deletion src/WeekView/WeekView.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,13 @@ export default class WeekView extends Component {

calculatePagesDates = (currentMoment, numberOfDays, prependMostRecent) => {
const initialDates = [];
const centralDate = moment(currentMoment);
if (numberOfDays === 7) {
// Start week on monday
centralDate.startOf('isoWeek');
}
for (let i = -this.pageOffset; i <= this.pageOffset; i += 1) {
const initialDate = moment(currentMoment).add(numberOfDays * i, 'd');
const initialDate = moment(centralDate).add(numberOfDays * i, 'd');
initialDates.push(initialDate.format(DATE_STR_FORMAT));
}
return prependMostRecent ? initialDates.reverse() : initialDates;
Expand Down Expand Up @@ -312,6 +317,8 @@ export default class WeekView extends Component {
EventComponent,
prependMostRecent,
rightToLeft,
showNowLine,
nowLineColor,
} = this.props;
const { currentMoment, initialDates } = this.state;
const times = this.calculateTimes(hoursInDisplay);
Expand Down Expand Up @@ -382,6 +389,8 @@ export default class WeekView extends Component {
EventComponent={EventComponent}
eventContainerStyle={eventContainerStyle}
rightToLeft={rightToLeft}
showNowLine={showNowLine}
nowLineColor={nowLineColor}
/>
);
}}
Expand Down Expand Up @@ -431,6 +440,8 @@ WeekView.propTypes = {
showTitle: PropTypes.bool,
rightToLeft: PropTypes.bool,
prependMostRecent: PropTypes.bool,
showNowLine: PropTypes.bool,
nowLineColor: PropTypes.string,
};

WeekView.defaultProps = {
Expand Down
13 changes: 7 additions & 6 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ import { Dimensions } from 'react-native';
import moment from 'moment';

const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
export const CONTENT_OFFSET = 16;
export const TIME_LABELS_IN_DISPLAY = 12;
export const CONTAINER_HEIGHT = SCREEN_HEIGHT - 60;
export const CONTAINER_WIDTH = SCREEN_WIDTH - 60;
export const TIME_LABEL_HEIGHT = CONTAINER_HEIGHT / TIME_LABELS_IN_DISPLAY;
export const DATE_STR_FORMAT = 'YYYY-MM-DD';
export const availableNumberOfDays = [1, 3, 5, 7];

export const minutesToYDimension = (hoursInDisplay, minutes) => {
const minutesInDisplay = 60 * hoursInDisplay;
return (minutes * CONTAINER_HEIGHT) / minutesInDisplay;
};

export const getFormattedDate = (date, format) => {
return moment(date).format(format);
};
Expand All @@ -29,12 +35,7 @@ export const getCurrentMonth = (date) => {

export const calculateDaysArray = (date, numberOfDays, rightToLeft) => {
const dates = [];
let initial = 0;
if (numberOfDays === 7) {
initial = 1;
initial -= moment(date).isoWeekday();
}
for (let i = initial; i < numberOfDays + initial; i += 1) {
for (let i = 0; i < numberOfDays; i += 1) {
const currentDate = moment(date).add(i, 'd');
dates.push(currentDate);
}
Expand Down