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

Add back-to-back feature (#56) #60

Merged
merged 5 commits into from
Aug 13, 2020
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
4 changes: 3 additions & 1 deletion src/common/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export const DEMO_TOKEN = 'DEMO_TOKEN';
export const SKIP_MODE = 'SKIP_MODE';
export const QUICK_MODE = 'QUICK_MODE';
export const DARK_MODE = 'DARK_MODE';
export const BACK_TO_BACK_MODE = 'BACK_TO_BACK_MODE';
export const MEANING_FIRST = 'MEANING_FIRST';

export const TERMINOLOGY = {

Expand All @@ -36,4 +38,4 @@ export const TERMINOLOGY = {
[MASTER]: 'Master',
[ENLIGHTENED]: 'Enlightened',
[BURNED]: 'Burned',
};
};
50 changes: 43 additions & 7 deletions src/features/reviews/useReviewSession.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import { MEANING, RADICAL } from 'src/common/constants';
import listToDict from 'src/utils/listToDict';
import queueReviews from 'src/features/reviews/utils/queueReviews';

import { BACK_TO_BACK_MODE, MEANING_FIRST} from 'src/common/constants';
import { useStoreState } from 'easy-peasy';
import adjustQueue from 'src/features/reviews/utils/adjustQueue';

export default (reviews, subjects) => {

const [ queue, setQueue ] = useState([]);
Expand All @@ -28,6 +32,18 @@ export default (reviews, subjects) => {
const [ completedCards, setCompletedCards ] = useState({});
const [ incorrectCards, setIncorrectCards ] = useState({});

// Sort reading-meaning card pairs back-to-back
const userSettings = useStoreState(state => state.session.userSettings);
const backToBackMode = _.get(userSettings, BACK_TO_BACK_MODE);
const meaningFirst = _.get(userSettings, MEANING_FIRST);
// Re-sort queue when the relevant user settings change
useEffect(() => {
setQueue(adjustQueue(queue, backToBackMode, meaningFirst))
}, [
backToBackMode,
meaningFirst,
]);

// refresh &
// reviews and subjects loaded
useEffect(() => {
Expand All @@ -48,8 +64,7 @@ export default (reviews, subjects) => {

// create queue
const _queue = queueReviews(reviews);

setQueue(_queue);
setQueue(adjustQueue(_queue, backToBackMode, meaningFirst));
setTotalCards(_queue.length);

}, [
Expand Down Expand Up @@ -171,11 +186,15 @@ export default (reviews, subjects) => {
// we can't assume the reviewed item is at zero index. we
// need to find the reviewed item in the queue

// find index of the removed item
const newQueue = queue.filter(i => !(
// find index of the current item
// useful information for maintaining back-to-back invariant
const currentIndex = queue.findIndex(i => (
i.id === id &&
i.reviewType === reviewType
));
// remove the current item
const newQueue = queue.slice();
newQueue.splice(currentIndex, 1);

// if answer was incorrect, put the item back
// into the queue randomly
Expand All @@ -187,12 +206,29 @@ export default (reviews, subjects) => {
// is -1) or if it has less than 2 items, splice
// statement will put the item to the end of the
// array, so we don't have to check for overflows
const requeueIndex = _.random(2, 8);
let requeueIndex = _.random(2, 8);

// adjust requeue index
if (backToBackMode) {
const pairIndex = newQueue.findIndex(item => (
_.get(item, 'review.id') === reviewId
));
// if paired card is in deck and not at top, put them together
// else if item would be requeued between a pair, adjust index
if ((pairIndex !== -1) && (pairIndex !== currentIndex)) {
requeueIndex = pairIndex;
requeueIndex += ((reviewType === MEANING) === meaningFirst) ? 0 : 1;
} else if ((0 < requeueIndex) && (requeueIndex < newQueue.length)) {
const prevId = _.get(newQueue[requeueIndex-1], 'review.id');
const nextId = _.get(newQueue[requeueIndex], 'review.id');
if (prevId === nextId) requeueIndex += 1;
}
}

// put the item back into the queue
newQueue.splice(requeueIndex, 0, queueItem);
}

// set the new queue
setQueue(newQueue);
}
Expand All @@ -212,4 +248,4 @@ export default (reviews, subjects) => {
incorrectMeanings,
incorrectReadings,
}
}
}
34 changes: 26 additions & 8 deletions src/features/reviews/utils/adjustQueue.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@
*
* Adjust distances between subject pairs items to
* make sure that they are not too far apart
* Also used to implement back-to-back sorting
*
*/

import _ from 'lodash';

import { MEANING, RADICAL } from 'src/common/constants';

const ALLOWED_MAX_DISTANCE = 10;
const ALLOWED_MIN_DISTANCE = 3;

const adjustSubjectPairDistances = (queue, currentIndex) => {
const adjustSubjectPairDistances = (queue,
currentIndex,
backToBackMode,
meaningFirst) => {
const newQueue = queue.slice();

// base case - return when queue is empty
Expand All @@ -22,7 +29,13 @@ const adjustSubjectPairDistances = (queue, currentIndex) => {
// get current item
const currentItem = newQueue[currentIndex];
const currentReviewId = _.get(currentItem, 'review.id');


// skip this item if it's a radical
if (currentItem.reviewType === RADICAL) {
return adjustSubjectPairDistances(newQueue, currentIndex + 1,
backToBackMode, meaningFirst);
}

// get current reviews pair in the queue after it
// Note: findIndex transformation is supported by metro-react-native-babel-preset (expo sdk37)
// https://docs.expo.io/versions/latest/react-native/javascript-environment/#polyfills
Expand All @@ -35,7 +48,8 @@ const adjustSubjectPairDistances = (queue, currentIndex) => {
// pair), or it's pair was already adjusted priorly. either case,
// it's safe to skip this item
if (pairIndex === -1) {
return adjustSubjectPairDistances(newQueue, currentIndex + 1);
return adjustSubjectPairDistances(newQueue, currentIndex + 1,
backToBackMode, meaningFirst);
}

// we sliced the first half of the array to reduce the search iterations
Expand All @@ -50,19 +64,23 @@ const adjustSubjectPairDistances = (queue, currentIndex) => {
ALLOWED_MIN_DISTANCE,
ALLOWED_MAX_DISTANCE
);
const backToBackDistance = ((currentItem.reviewType === MEANING) ===
meaningFirst) ? 1 : 0;
const pairDistance = backToBackMode ? backToBackDistance : randomDistance;

// remove the pair from it's current position
const tmp = newQueue.splice(pairIndex, 1)[0];

// place the pair to it's new location closer to the
// current review. if the new location overflows, splice
// will place it to the end, which works in our case
newQueue.splice(currentIndex + randomDistance, 0, tmp);
newQueue.splice(currentIndex + pairDistance, 0, tmp);

// recurse from the next item
return adjustSubjectPairDistances(newQueue, currentIndex + 1);
return adjustSubjectPairDistances(newQueue, currentIndex + 1,
backToBackMode, meaningFirst);
}

export default queue => {
return adjustSubjectPairDistances(queue, 0);
}
export default adjustQueue = (queue, backToBackMode = false, meaningFirst = false) => {
return adjustSubjectPairDistances(queue, 0, backToBackMode, meaningFirst);
}
31 changes: 30 additions & 1 deletion src/screens/Review/ReviewMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import List from 'src/components/List/List';
import Modal, { DURATION_SAFE } from 'src/components/Modal/Modal';
import dialog from 'src/utils/dialog';
import { isWeb } from 'src/utils/device';
import { SKIP_MODE, QUICK_MODE, DARK_MODE } from 'src/common/constants';
import { SKIP_MODE, QUICK_MODE, DARK_MODE, BACK_TO_BACK_MODE, MEANING_FIRST}
from 'src/common/constants';

const ReviewMenu = ({
demo,
Expand All @@ -30,6 +31,8 @@ const ReviewMenu = ({
const skipMode = _.get(userSettings, SKIP_MODE);
const quickMode = _.get(userSettings, QUICK_MODE);
const darkMode = _.get(userSettings, DARK_MODE);
const backToBackMode = _.get(userSettings, BACK_TO_BACK_MODE);
const meaningFirst = _.get(userSettings, MEANING_FIRST);

return (
<Modal
Expand Down Expand Up @@ -96,6 +99,30 @@ const ReviewMenu = ({
},
}
},
{
id: 'ses-back-to-back',
title: 'Back To Back',
subtitle: 'Reorder cards so reading and meaning are back-to-back',
leftIcon: <SimpleLineIcons name="layers" size={18} color={iconcolor} />,
switch: {
value: backToBackMode,
onValueChange: () => {
saveSetting({ key: BACK_TO_BACK_MODE, value: !backToBackMode });
},
}
},
{
id: 'ses-meaning-first',
title: 'Meaning First',
subtitle: 'Show meaning first when back-to-back is enabled',
leftIcon: <SimpleLineIcons name="direction" size={18} color={iconcolor} />,
switch: {
value: meaningFirst,
onValueChange: () => {
saveSetting({ key: MEANING_FIRST, value: !meaningFirst });
},
}
},
{
id: 'ses-dark',
title: 'Dark Mode',
Expand Down Expand Up @@ -182,6 +209,8 @@ ReviewMenu.propTypes = {
setQuickMode: PropTypes.func,
skipMode: PropTypes.bool,
setSkipMode: PropTypes.func,
backToBackMode: PropTypes.bool,
setBackToBackMode: PropTypes.func,
};

const styles = StyleSheet.create({
Expand Down