diff --git a/package.json b/package.json index 6f65d84..7331cb2 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "react-native-easy-toast": "^1.2.0", "react-native-elements": "^3.0.0", "react-native-modal": "^11.5.6", + "react-native-picker-select": "^8.0.4", "react-native-safe-area-context": "3.1.9", "react-native-web": "~0.13.12", "react-redux": "^7.2.0", diff --git a/src/common/constants.js b/src/common/constants.js index 09919ff..0dac708 100644 --- a/src/common/constants.js +++ b/src/common/constants.js @@ -21,6 +21,15 @@ export const DARK_MODE = 'DARK_MODE'; export const BACK_TO_BACK_MODE = 'BACK_TO_BACK_MODE'; export const MEANING_FIRST = 'MEANING_FIRST'; +// review orders +export const REVIEW_ORDER = 'REVIEW_ORDER' +export const RANDOM_ORDER = 'RANDOM_ORDER' +export const LOWEST_LEVEL_FIRST = 'LOWEST_LEVEL_FIRST' +export const CURRENT_LEVEL_FIRST = 'CURRENT_LEVEL_FIRST' +export const ASCENDING_SRS_STAGE = 'ASCENDING_SRS_STAGE' +export const DESCENDING_SRS_STAGE = 'DESCENDING_SRS_STAGE' + + export const TERMINOLOGY = { // subject types diff --git a/src/components/List/ListItem.js b/src/components/List/ListItem.js index 3d4203b..0d3be99 100644 --- a/src/components/List/ListItem.js +++ b/src/components/List/ListItem.js @@ -6,6 +6,7 @@ import { ListItem, Icon } from 'react-native-elements'; import device, {isWeb} from 'src/utils/device'; import useColorScheme from 'src/hooks/useColorScheme';; import theme from 'src/common/theme'; +import RNPickerSelect from 'react-native-picker-select'; const ListItemComponent = props => { const colorScheme = useColorScheme(); @@ -27,6 +28,7 @@ const ListItemComponent = props => { {props.rightIcon && {props.rightIcon}} {!isWeb() && props.switch && } {isWeb() && props.switch && {props?.switch?.value ? "On" : "Off"}} + {props.picker && } ) }; @@ -63,4 +65,12 @@ const styles = StyleSheet.create({ } }) +const pickerStyles = StyleSheet.create({ + inputWeb: { + borderWidth: 0, + textAlign: 'right', + fontSize: 14, + }, +}) + export default ListItemComponent; diff --git a/src/features/reviews/useReviewSession.js b/src/features/reviews/useReviewSession.js index ba2fa78..ee1a615 100644 --- a/src/features/reviews/useReviewSession.js +++ b/src/features/reviews/useReviewSession.js @@ -8,7 +8,7 @@ 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 { BACK_TO_BACK_MODE, MEANING_FIRST, REVIEW_ORDER } from 'src/common/constants'; import { useStoreState } from 'easy-peasy'; import adjustQueue from 'src/features/reviews/utils/adjustQueue'; @@ -32,16 +32,18 @@ export default (reviews, subjects) => { const [ completedCards, setCompletedCards ] = useState({}); const [ incorrectCards, setIncorrectCards ] = useState({}); - // Sort reading-meaning card pairs back-to-back + // Sort reading-meaning card pairs back-to-back according to the review order const userSettings = useStoreState(state => state.session.userSettings); const backToBackMode = _.get(userSettings, BACK_TO_BACK_MODE); const meaningFirst = _.get(userSettings, MEANING_FIRST); + const reviewOrder = _.get(userSettings, REVIEW_ORDER); // Re-sort queue when the relevant user settings change useEffect(() => { - setQueue(adjustQueue(queue, backToBackMode, meaningFirst)) + setQueue(adjustQueue(queue, backToBackMode, meaningFirst, reviewOrder)) }, [ - backToBackMode, - meaningFirst, + backToBackMode, + meaningFirst, + reviewOrder, ]); // refresh & @@ -63,8 +65,8 @@ export default (reviews, subjects) => { setTotalReviews(reviews ? reviews.length : 0); // create queue - const _queue = queueReviews(reviews); - setQueue(adjustQueue(_queue, backToBackMode, meaningFirst)); + const _queue = queueReviews(reviews, subjects); + setQueue(adjustQueue(_queue, backToBackMode, meaningFirst, reviewOrder)); setTotalCards(_queue.length); }, [ diff --git a/src/features/reviews/utils/adjustQueue.js b/src/features/reviews/utils/adjustQueue.js index d88becb..3145224 100644 --- a/src/features/reviews/utils/adjustQueue.js +++ b/src/features/reviews/utils/adjustQueue.js @@ -9,15 +9,35 @@ import _ from 'lodash'; -import { MEANING, RADICAL } from 'src/common/constants'; +import { MEANING, RADICAL, RANDOM_ORDER, LOWEST_LEVEL_FIRST, CURRENT_LEVEL_FIRST, ASCENDING_SRS_STAGE, DESCENDING_SRS_STAGE } from 'src/common/constants'; const ALLOWED_MAX_DISTANCE = 10; const ALLOWED_MIN_DISTANCE = 3; -export default (queue, backToBackMode = false, meaningFirst = false) => { - const newQueue = queue.slice(); +export default (queue, backToBackMode = false, meaningFirst = false, reviewOrder = RANDOM_ORDER) => { + + let newQueue = queue.slice(); let currentIndex = 0; + // sort new queue based on the selected review order + switch (reviewOrder) { + case RANDOM_ORDER: + newQueue = _.shuffle(newQueue); + break; + case LOWEST_LEVEL_FIRST: + newQueue = _.orderBy(newQueue, 'subjectLevel', 'asc') + break; + case CURRENT_LEVEL_FIRST: + newQueue = _.orderBy(newQueue, 'subjectLevel', 'desc') + break; + case ASCENDING_SRS_STAGE: + newQueue = _.orderBy(newQueue, 'review.data.srs_stage', 'asc') + break; + case DESCENDING_SRS_STAGE: + newQueue = _.orderBy(newQueue, 'review.data.srs_stage', 'desc') + break; + } + // avoid recursion to avoid exceeding maximum call stack size while (currentIndex < queue.length - 1) { diff --git a/src/features/reviews/utils/queueReviews.js b/src/features/reviews/utils/queueReviews.js index 3dfbef6..a72fbeb 100644 --- a/src/features/reviews/utils/queueReviews.js +++ b/src/features/reviews/utils/queueReviews.js @@ -6,8 +6,7 @@ import adjustQueue from 'src/features/reviews/utils/adjustQueue'; // pick multiple reviews from the list, respecting the // removed items from the list from the previous loop -const queueReviews = (reviews, reviewTypesHistory, queue) => { - +const queueReviews = (reviews, reviewTypesHistory, queue, subjects) => { let _reviews = reviews; let _reviewTypesHistory = reviewTypesHistory; @@ -22,11 +21,16 @@ const queueReviews = (reviews, reviewTypesHistory, queue) => { newReviewTypeHistory, ] = pickReviewAndType(_reviews, _reviewTypesHistory); + // find the review's subject + const subject = subjects.find(subject => subject.id === _.get(review, 'data.subject_id')); + const subjectLevel = _.get(subject, "data.level"); + // add current pick to the queue queue.push({ id: randomId(), review, reviewType, + subjectLevel, }); _reviews = newReviews; @@ -37,7 +41,7 @@ const queueReviews = (reviews, reviewTypesHistory, queue) => { return queue; } -export default reviews => { - const queued = queueReviews(reviews, {}, []); +export default (reviews, subjects) => { + const queued = queueReviews(reviews, {}, [], subjects); return adjustQueue(queued); }; diff --git a/src/screens/Review/ReviewMenu.js b/src/screens/Review/ReviewMenu.js index 1a9d886..f2478ea 100644 --- a/src/screens/Review/ReviewMenu.js +++ b/src/screens/Review/ReviewMenu.js @@ -10,7 +10,7 @@ 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, BACK_TO_BACK_MODE, MEANING_FIRST} +import { SKIP_MODE, QUICK_MODE, DARK_MODE, BACK_TO_BACK_MODE, MEANING_FIRST, REVIEW_ORDER, RANDOM_ORDER, LOWEST_LEVEL_FIRST, CURRENT_LEVEL_FIRST, ASCENDING_SRS_STAGE, DESCENDING_SRS_STAGE } from 'src/common/constants'; const ReviewMenu = ({ @@ -33,6 +33,7 @@ const ReviewMenu = ({ const darkMode = _.get(userSettings, DARK_MODE); const backToBackMode = _.get(userSettings, BACK_TO_BACK_MODE); const meaningFirst = _.get(userSettings, MEANING_FIRST); + const reviewOrder = _.get(userSettings, REVIEW_ORDER, RANDOM_ORDER); return ( , + picker: { + onValueChange: (value) => { + saveSetting({ key: REVIEW_ORDER, value: value }); + }, + placeholder: {}, + value: reviewOrder, + items: + [ + { value: RANDOM_ORDER, label: "Random Order" }, + { value: LOWEST_LEVEL_FIRST, label: "Lowest Level First" }, + { value: CURRENT_LEVEL_FIRST, label: "Current Level First" }, + { value: ASCENDING_SRS_STAGE, label: "Ascending SRS Stage" }, + { value: DESCENDING_SRS_STAGE, label: "Descending SRS Stage" }, + ], + }, + }, { id: 'ses-dark', title: 'Dark Mode', diff --git a/yarn.lock b/yarn.lock index 61c9b90..3126ec4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2795,6 +2795,11 @@ resolved "https://registry.yarnpkg.com/@react-native-community/netinfo/-/netinfo-5.9.7.tgz#769c6b306ea6bbaa1c4a3fffdd183d0cfc30ed7e" integrity sha512-NAkkT68oF+M9o6El2xeUqZK7magPjG/tAcEbvCbqyhlh3yElKWnI1e1vpbVvFXzTefy67FwYFWOJqBN6U7Mnkg== +"@react-native-picker/picker@^1.8.3": + version "1.16.3" + resolved "https://registry.yarnpkg.com/@react-native-picker/picker/-/picker-1.16.3.tgz#22750f8d44465432845fa59ce20e69761029b6ef" + integrity sha512-Qt40DBg7IEhBN5x2DYPV3B26j5fNhVZcpCestr1IuirneMNtOaIyA9+nPYniL/OBzd9YbpUKbS7XSy/MM9bP3w== + "@reduxjs/toolkit@^1.3.5": version "1.4.0" resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.4.0.tgz#ee2e2384cc3d1d76780d844b9c2da3580d32710d" @@ -14201,6 +14206,14 @@ react-native-modal@^11.5.6: prop-types "^15.6.2" react-native-animatable "1.3.3" +react-native-picker-select@^8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/react-native-picker-select/-/react-native-picker-select-8.0.4.tgz#3f7f1f42df69b06e7d2c10338288332a6c40fd10" + integrity sha512-orBjPIwBkV5oipyVw263YNMI56f6Kj3p/ejabZoCYYNSG3AiLVVhC2RqsxMgDA7IayyURAW+AlV+mDJyVqLBkg== + dependencies: + "@react-native-picker/picker" "^1.8.3" + lodash.isequal "^4.5.0" + react-native-ratings@^7.3.0: version "7.3.0" resolved "https://registry.yarnpkg.com/react-native-ratings/-/react-native-ratings-7.3.0.tgz#32e11e4ed944ba8adbbc995d601df3f131619b6f"