Skip to content

Commit

Permalink
Merge pull request #302 from odziem/feat-scheduling
Browse files Browse the repository at this point in the history
Scheduling of activities
  • Loading branch information
binarybottle authored Nov 21, 2019
2 parents b2eacd6 + dc52811 commit e739b49
Show file tree
Hide file tree
Showing 27 changed files with 352 additions and 337 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]
=======
## [0.9.1] - 2019-11-20
### Fixed
- Scheduling of activities

## [0.8.10] - 2019-11-13
### Updated
- :lock: :apple: :books: User privacy descriptions
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# MindLogger 0.8.10
# MindLogger 0.9.1

_Note: v0.1 is deprecated as of June 12, 2019._

Expand Down
5 changes: 3 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ android {
applicationId "lab.childmindinstitute.data"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 93
versionName "0.8.10"
versionCode 94
versionName "0.9.1"
ndk {
abiFilters "arm64-v8a", "x86_64", "armeabi-v7a", "x86"
}
Expand Down Expand Up @@ -138,6 +138,7 @@ android {
}

dependencies {
implementation project(':react-native-localize')
configurations.all {
resolutionStrategy {
dependencySubstitution {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.app.Application;

import com.facebook.react.ReactApplication;
import com.reactcommunity.rnlocalize.RNLocalizePackage;
import com.oblador.vectoricons.VectorIconsPackage;
import com.reactnativecommunity.slider.ReactSliderPackage;
import com.reactnativecommunity.netinfo.NetInfoPackage;
Expand Down Expand Up @@ -36,6 +37,7 @@ public boolean getUseDeveloperSupport() {
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new RNLocalizePackage(),
new VectorIconsPackage(),
new ReactSliderPackage(),
new NetInfoPackage(),
Expand Down
2 changes: 2 additions & 0 deletions android/settings.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
rootProject.name = 'MDCApp'
include ':react-native-localize'
project(':react-native-localize').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-localize/android')
include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
include ':@react-native-community_slider'
Expand Down
16 changes: 4 additions & 12 deletions app/components/ActivityList/ActivityDueDate.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { StyleSheet } from 'react-native';
import moment from 'moment';
import { colors } from '../../theme';
import { LittleText } from '../core';

const formatTime = (timestamp) => {
const time = moment(timestamp);
if (moment().isSame(time, 'day')) {
return moment(timestamp).format('[Today at] h:mm A');
}
return moment(timestamp).format('MMMM D');
};
import { formatTime } from '../../services/time';

const styles = StyleSheet.create({
textStyles: {
Expand All @@ -20,17 +12,17 @@ const styles = StyleSheet.create({
});

const ActivityDueDate = ({ activity }) => {
if (activity.lastResponseTimestamp < activity.lastScheduledTimestamp) {
if (activity.status === 'overdue') {
return (
<LittleText style={{ ...styles.textStyles, color: colors.alert }}>
{formatTime(activity.lastScheduledTimestamp)}
Due on: {formatTime(activity.lastScheduledTimestamp)}
</LittleText>
);
}
if (activity.status === 'scheduled') {
return (
<LittleText style={styles.textStyles}>
{formatTime(activity.nextScheduledTimestamp)}
Scheduled for: {formatTime(activity.nextScheduledTimestamp)}
</LittleText>
);
}
Expand Down
9 changes: 1 addition & 8 deletions app/components/ActivityList/ActivityListItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ const styles = StyleSheet.create({
marginLeft: 16,
fontFamily: theme.fontFamily,
},
notification: {
position: 'absolute',
top: 4,
right: 12,
},
sectionHeading: {
marginTop: 20,
marginBottom: 0,
Expand Down Expand Up @@ -93,9 +88,7 @@ const ActivityRow = ({ activity, onPress }) => {
</View>
</TouchBox>
{activity.isOverdue && (
<View style={styles.notification}>
<NotificationDot />
</View>
<NotificationDot />
)}
</View>
);
Expand Down
3 changes: 0 additions & 3 deletions app/components/ActivityList/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@ import PropTypes from 'prop-types';
import { View } from 'react-native';
import sortActivities from './sortActivities';
import ActivityListItem from './ActivityListItem';
import { Heading } from '../core';

const ActivityList = ({ applet, inProgress, onPressActivity }) => {
const activities = sortActivities(applet.activities, inProgress);
return (
<View style={{ paddingBottom: 30 }}>
{/* <Heading style={{ paddingLeft: 20, marginTop: 30 }}>Activities</Heading> */}

{activities.map(activity => (
<ActivityListItem
onPress={() => onPressActivity(activity)}
Expand Down
28 changes: 20 additions & 8 deletions app/components/ActivityList/sortActivities.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as R from 'ramda';
import moment from 'moment';

const sortActivitiesAlpha = (a, b) => {
const compareByNameAlpha = (a, b) => {
const nameA = a.name.en.toUpperCase(); // ignore upper and lowercase
const nameB = b.name.en.toUpperCase(); // ignore upper and lowercase
if (nameA < nameB) {
Expand All @@ -12,7 +13,7 @@ const sortActivitiesAlpha = (a, b) => {
return 0;
};

const sortBy = propName => (a, b) => a[propName] - b[propName];
const compareByTimestamp = propName => (a, b) => moment(a[propName]) - moment(b[propName]);

export const getUnscheduled = activityList => activityList.filter(
activity => activity.nextScheduledTimestamp === null
Expand All @@ -28,11 +29,15 @@ export const getCompleted = activityList => activityList.filter(

export const getScheduled = activityList => activityList.filter(
activity => activity.nextScheduledTimestamp !== null
&& activity.lastResponseTimestamp >= activity.lastScheduledTimestamp,
&& (
moment(activity.lastResponseTimestamp) >= moment(activity.lastScheduledTimestamp)
|| activity.lastScheduledTimestamp === null
|| activity.lastResponseTimestamp === null
),
);

export const getOverdue = activityList => activityList.filter(
activity => activity.lastResponseTimestamp < activity.lastScheduledTimestamp,
activity => moment(activity.lastResponseTimestamp) < moment(activity.lastScheduledTimestamp),
);

const addSectionHeader = (array, headerText) => (array.length > 0
Expand All @@ -41,20 +46,27 @@ const addSectionHeader = (array, headerText) => (array.length > 0

const addProp = (key, val, arr) => arr.map(obj => R.assoc(key, val, obj));

// Sort the activities into buckets of "in-progress", "overdue", "scheduled", "unscheduled",
// and "completed". Inject header labels, e.g. "In Progress", before the activities that fit
// into that bucket.
export default (activityList, inProgress) => {
const inProgressKeys = Object.keys(inProgress);
const inProgressActivities = activityList.filter(
activity => inProgressKeys.includes(activity.id),
);

const notInProgress = activityList.filter(activity => !inProgressKeys.includes(activity.id));
const overdue = getOverdue(notInProgress).sort(sortBy('lastScheduledTimestamp')).reverse();
const scheduled = getScheduled(notInProgress).sort(sortBy('nextScheduledTimestamp'));
const unscheduled = getUnscheduled(notInProgress).sort(sortActivitiesAlpha);

// Activities that are scheduled for that time.
const overdue = getOverdue(notInProgress).sort(compareByTimestamp('lastScheduledTimestamp')).reverse();
// Should tell the user when it will be activated.
const scheduled = getScheduled(notInProgress).sort(compareByTimestamp('nextScheduledTimestamp'));
const unscheduled = getUnscheduled(notInProgress).sort(compareByNameAlpha);
const completed = getCompleted(notInProgress).reverse();

return [
...addSectionHeader(addProp('status', 'in-progress', inProgressActivities), 'In Progress'),
...addSectionHeader(addProp('status', 'overdue', overdue), 'Overdue'),
...addSectionHeader(addProp('status', 'overdue', overdue), 'Due'),
...addSectionHeader(addProp('status', 'scheduled', scheduled), 'Scheduled'),
...addSectionHeader(addProp('status', 'unscheduled', unscheduled), 'Unscheduled'),
...addSectionHeader(addProp('status', 'completed', completed), 'Completed'),
Expand Down
12 changes: 2 additions & 10 deletions app/components/ActivitySummary.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { View, StyleSheet } from 'react-native';
import { Button, Text, Icon } from 'native-base';
import {
Expand All @@ -9,6 +8,7 @@ import {
} from './core';
import { colors } from '../themes/colors';
import theme from '../themes/base-theme';
import { formatTime } from '../services/time';

const styles = StyleSheet.create({
box: {
Expand All @@ -35,14 +35,6 @@ const styles = StyleSheet.create({
},
});

const formatTime = (timestamp) => {
const time = moment(timestamp);
if (moment().isSame(time, 'day')) {
return moment(timestamp).format('[today at] h:mm A');
}
return moment(timestamp).format('MMMM D');
};

const lastCompletedString = (timestamp) => {
if (!timestamp) {
return 'Not yet completed';
Expand All @@ -55,7 +47,7 @@ const nextScheduledString = (activity) => {
return 'Unscheduled';
}
if (activity.isOverdue) {
return `Was due ${formatTime(activity.nextScheduledTimestamp)}`;
return `Due on ${formatTime(activity.lastScheduledTimestamp)}`;
}
return `Scheduled for ${formatTime(activity.nextScheduledTimestamp)}`;
};
Expand Down
6 changes: 3 additions & 3 deletions app/components/core/NotificationDot.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { colors } from '../../theme';
const styles = StyleSheet.create({
container: {
backgroundColor: colors.alert,
width: 20,
height: 20,
width: 15,
height: 15,
// justifyContent: 'center',
// alignItems: 'center',
marginLeft: 12,
marginTop: 6,
borderRadius: 10,
elevation: 2,
elevation: 1,
position: 'absolute',
},
});
Expand Down
1 change: 1 addition & 0 deletions app/scenes/ActivityDetails/ActivityDetailsComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const ActivityDetailsComponent = ({
primaryColor,
}) => {
const responses = responseHistory.filter((response) => {
// TODO: Update below or remove (currently unused) ActivityDetails from navigator.
const responseActivityId = R.path(['meta', 'activity', '@id'], response);
const formattedId = `activity/${responseActivityId}`;
return activity.id === formattedId;
Expand Down
6 changes: 6 additions & 0 deletions app/services/network.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ export const getResponses = (authToken, applet) => get(
{ applet },
);

export const getSchedule = (authToken, timezone) => get(
'schedule',
authToken,
{ timezone },
);

export const getApplets = (authToken, userId) => get(
`user/applets`,
authToken,
Expand Down
Loading

0 comments on commit e739b49

Please sign in to comment.