diff --git a/api/.yarn/install-state.gz b/api/.yarn/install-state.gz index 995763d17..0630d4782 100644 Binary files a/api/.yarn/install-state.gz and b/api/.yarn/install-state.gz differ diff --git a/api/.yarn/versions/a865d729.yml b/api/.yarn/versions/a865d729.yml new file mode 100644 index 000000000..e69de29bb diff --git a/api/package.json b/api/package.json index 1b228a878..0d3ed941d 100644 --- a/api/package.json +++ b/api/package.json @@ -3,7 +3,7 @@ "license": "MIT", "homepage": "https://openarabic.io", "repository": "https://github.com/edenmind/OpenArabic", - "version": "1444.12.229", + "version": "1444.12.231", "authors": [ "Yunus Andreasson (https://github.com/YunusAndreasson)" ], diff --git a/api/schemas/texts.js b/api/schemas/texts.js index 4f1aa8690..a27a725b1 100644 --- a/api/schemas/texts.js +++ b/api/schemas/texts.js @@ -36,6 +36,7 @@ const getTextsOptions = { handler: listTexts } +// used for categories const getTextsWithIdOptions = { schema: { response: { @@ -80,6 +81,7 @@ const getTextOptions = { slug: { type: 'string' }, image: { type: 'string' }, introduction: { type: 'string' }, + explanation: { type: 'string' }, views: { type: 'string' }, timeAgo: { type: 'string' }, readingTime: { type: 'string' }, @@ -147,7 +149,8 @@ const updateTextOptions = { properties: { quiz: { type: 'boolean' }, arabic: { type: 'string', minLength: 1, maxLength: 50 }, - english: { type: 'string', minLength: 1, maxLength: 50 } + english: { type: 'string', minLength: 1, maxLength: 50 }, + explanation: { type: 'string' } } } } @@ -208,7 +211,8 @@ const postTextOptions = { properties: { quiz: { type: 'boolean' }, arabic: { type: 'string', maxLength: 50 }, - english: { type: 'string', maxLength: 50 } + english: { type: 'string', maxLength: 50 }, + explanation: { type: 'string' } } } } diff --git a/api/schemas/words.js b/api/schemas/words.js index 4045885c7..f37483e50 100644 --- a/api/schemas/words.js +++ b/api/schemas/words.js @@ -102,6 +102,7 @@ const getWordsOptions = { grammar: { type: 'string' }, filename: { type: 'string' }, date: { type: 'string' }, + explanation: { type: 'string' }, publishDate: { type: 'string' }, arabicSentenceFilename: { type: 'string' } } diff --git a/api/services/texts.js b/api/services/texts.js index b3a5a3e91..995614e5b 100644 --- a/api/services/texts.js +++ b/api/services/texts.js @@ -287,6 +287,7 @@ async function getAllWordsFromTexts(textsCollection) { arabicSentence: sentence.arabic, englishSentence: sentence.english, audioSentence: sentence.filename, + explanation: sentence.explanation, author: text.author, source: text.source, arabicSentenceFilename: sentence.filename diff --git a/mobile/.yarn/install-state.gz b/mobile/.yarn/install-state.gz index 39da09312..a4e39b104 100644 Binary files a/mobile/.yarn/install-state.gz and b/mobile/.yarn/install-state.gz differ diff --git a/mobile/.yarn/versions/6bee3a1e.yml b/mobile/.yarn/versions/6bee3a1e.yml new file mode 100644 index 000000000..e69de29bb diff --git a/mobile/components/modal-scroll-view.js b/mobile/components/modal-scroll-view.js index 818eb227d..7252af1fd 100644 --- a/mobile/components/modal-scroll-view.js +++ b/mobile/components/modal-scroll-view.js @@ -33,6 +33,7 @@ const ModalScrollView = ({ ...sharedStyled.arabicHeading, alignSelf: 'center', marginHorizontal: 10, + paddingBottom: 15, textAlign: 'center', writingDirection: 'rtl' }, diff --git a/mobile/components/play-sound.js b/mobile/components/play-sound.js index aeb3ee5b0..72234ff31 100644 --- a/mobile/components/play-sound.js +++ b/mobile/components/play-sound.js @@ -5,18 +5,20 @@ import PropTypes from 'prop-types' import { StyleSheet } from 'react-native' // This is more of a component than a server and might be better placed in the components folder -export default function PlaySound({ audioFileNames, buttonText }) { +export default function PlaySound({ audioFileNames, buttonText, mode = 'elevated', margin = 10 }) { const [sound, setSound] = React.useState() const styles = StyleSheet.create({ button: { - marginBottom: 10, - marginTop: 10 + marginBottom: margin, + marginTop: margin } }) // function that loops over audioFileName that is an array and calls playSound with the should that should be played const playAllSounds = async () => { + console.log(audioFileNames) + if (Array.isArray(audioFileNames)) { for (const audioFileName of audioFileNames) { await playSound(audioFileName) @@ -69,13 +71,15 @@ export default function PlaySound({ audioFileNames, buttonText }) { }, [sound]) return ( - ) } PlaySound.propTypes = { + mode: PropTypes.string, audioFileNames: PropTypes.any.isRequired, - buttonText: PropTypes.string.isRequired + buttonText: PropTypes.string.isRequired, + margin: PropTypes.number } diff --git a/mobile/package.json b/mobile/package.json index dfa98acc4..737a406e3 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -4,7 +4,7 @@ "license": "MIT", "homepage": "https://openarabic.io", "repository": "https://github.com/edenmind/OpenArabic", - "version": "1444.12.329", + "version": "1444.12.331", "authors": [ "Yunus Andreasson (https://github.com/YunusAndreasson)" ], diff --git a/mobile/screens/text-bilingual-heading.js b/mobile/screens/text-bilingual-heading.js index 280d7647b..80219d556 100644 --- a/mobile/screens/text-bilingual-heading.js +++ b/mobile/screens/text-bilingual-heading.js @@ -75,6 +75,6 @@ TextBilingualHeading.propTypes = { image: PropTypes.string.isRequired, author: PropTypes.string.isRequired, source: PropTypes.string.isRequired, - introduction: PropTypes.string.isOptional + introduction: PropTypes.string }) } diff --git a/mobile/screens/text-bilingual-sentences-word-pair.js b/mobile/screens/text-bilingual-sentences-word-pair.js index 03047e84c..a56acb8c3 100644 --- a/mobile/screens/text-bilingual-sentences-word-pair.js +++ b/mobile/screens/text-bilingual-sentences-word-pair.js @@ -2,7 +2,7 @@ import React, { Fragment } from 'react' import PropTypes from 'prop-types' import { StyleSheet, View } from 'react-native' -import { Text, useTheme } from 'react-native-paper' +import { Text, useTheme, Button } from 'react-native-paper' import PlaySound from '../components/play-sound.js' import { useSharedStyles } from '../styles/common.js' import ModalScrollView from '../components/modal-scroll-view.js' @@ -25,6 +25,7 @@ function TextBilingualSentencesWords({ word }) { const sharedStyle = useSharedStyles(theme) const [visible, setVisible] = React.useState(false) const hideModal = () => setVisible(false) + const showModal = () => setVisible(true) const explanation = formatGrammar(word.grammar, sharedStyle) @@ -39,6 +40,9 @@ function TextBilingualSentencesWords({ word }) { + { getListOfWordPairs() showModal() diff --git a/mobile/screens/text-list-card-quote.js b/mobile/screens/text-list-card-quote.js index c470d790f..51b8e43c6 100644 --- a/mobile/screens/text-list-card-quote.js +++ b/mobile/screens/text-list-card-quote.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types' import React, { useState, useCallback } from 'react' import { StyleSheet, Animated, Share } from 'react-native' import { useSharedStyles } from '../styles/common.js' +import PlaySound from '../components/play-sound.js' export default function TextListCardQuote({ text }) { const [scaleValue] = useState(new Animated.Value(1)) @@ -87,6 +88,10 @@ export default function TextListCardQuote({ text }) { New ā˜€ļø )} + diff --git a/mobile/screens/text-practice.js b/mobile/screens/text-practice.js index c5fe895db..fea04c432 100644 --- a/mobile/screens/text-practice.js +++ b/mobile/screens/text-practice.js @@ -1,5 +1,5 @@ import { View, ScrollView, Alert } from 'react-native' -import { Text, Surface, Divider, useTheme } from 'react-native-paper' +import { Text, Surface, Divider, useTheme, Button } from 'react-native-paper' import { useSelector } from 'react-redux' import React, { useState, useEffect, useCallback } from 'react' import { useSharedStyles } from '../styles/common.js' @@ -8,6 +8,9 @@ import WordsContextHighLighted from '../components/context-highlighted.js' import TextPracticeArabicWords from './text-practice-arabic-words.js' import { getThreeRandomWords, vibrateBetweenTwoColors } from '../services/utility-service.js' import Spinner from '../components/spinner.js' +import ModalScrollView from '../components/modal-scroll-view.js' +import { formatGrammar } from '../services/ui-services.js' +import PlaySound from '../components/play-sound.js' const selector = (state) => state.text const textLoadSelector = (state) => state.textLoading @@ -22,6 +25,9 @@ const TextPractice = () => { const [currentArabicWordsInSentence, setCurrentArabicWordsInSentence] = useState([]) const [color, setColor] = useState(theme.colors.elevation.level2) const [currentEnglishWord, setCurrentEnglishWord] = useState(0) + const hideModal = () => setVisible(false) + const [visible, setVisible] = React.useState(false) + const [explanation, setExplanation] = useState('') // update the state for currentArabicWordsInSentence with the arabic words in the current sentence (sentencesInText[currentSentence].arabicWords) when the component loads useEffect(() => { @@ -64,16 +70,17 @@ const TextPractice = () => { const wordsInSentence = sentence.words.map((word, wordIndex) => { return { arabicWord: { arabic: word.arabic, id: wordIndex }, - englishWord: { english: word.english, id: wordIndex } + englishWord: { english: word.english, id: wordIndex }, + explanation: word.explanation } }) const arabicWords = wordsInSentence.map((word) => word.arabicWord).sort(() => Math.random() - 0.5) const englishWords = wordsInSentence.map((word) => word.englishWord) + const explanations = wordsInSentence.map((word) => word.explanation) + const filename = sentence.filename - const explanation = sentence.explanation - - return { arabicWords, englishWords, explanation } + return { arabicWords, englishWords, explanations, filename } }) }, [text]) @@ -147,9 +154,48 @@ const TextPractice = () => { englishWord={currentEnglishWord} /> + + + + + - + {explanation}} + title={'Explain'} + hideModal={hideModal} + /> + state.words const WordsContent = ({ @@ -31,8 +28,6 @@ const WordsContent = ({ const [color, setColor] = useState(theme.colors.elevation.level2) const [buttonPositions, setButtonPositions] = useState(generateRandomPositions()) const [timeoutId, setTimeoutId] = useState() - const hideModal = () => setVisible(false) - const [visible, setVisible] = React.useState(false) const sharedStyle = useSharedStyles(theme) const [wrongAnswers, setWrongAnswers] = useState(0) const [wrongAnswerAlreadyAdded, setWrongAnswerAlreadyAdded] = useState([]) @@ -51,7 +46,7 @@ const WordsContent = ({ marginBottom: 10, marginVertical: 10, - minHeight: 350 + minHeight: 320 }, text: { color: theme.colors.primary, @@ -157,14 +152,6 @@ const WordsContent = ({ { button: button3, position: buttonPositions[2] } ].sort((a, b) => a.position - b.position) - const details = ( - - {words[currentWord]?.grammar - ? formatGrammar(words[currentWord].grammar, sharedStyle) - : 'No explanation available'} - - ) - const renderItem = ({ item }) => {item.button} return ( @@ -181,35 +168,45 @@ const WordsContent = ({ style={{ height: 7, borderRadius: 10, backgroundColor: theme.colors.elevation.level2 }} /> - + + {words[currentWord]?.arabic.trim()} + + + + {/* - {words[currentWord]?.arabic.trim()} - - - {words[currentWord]?.arabicSentence} - + */} - + {/* {words[currentWord]?.source} - + */} - + - } /> diff --git a/mobile/services/ui-services.js b/mobile/services/ui-services.js index 0165d8293..464f4a497 100644 --- a/mobile/services/ui-services.js +++ b/mobile/services/ui-services.js @@ -56,19 +56,21 @@ export function generateWordError(text) { } export function moonPhaseEmoji(day) { - if (day < 1 || day > 30) { + const dayInt = Number.parseInt(day, 10) + + if (dayInt < 1 || dayInt > 30) { throw new Error('Day must be between 1 and 30.') } - if (day === 1) { + if (dayInt === 1) { return 'šŸŒ‘' // New moon - } else if (day > 1 && day < 7) { + } else if (dayInt > 1 && dayInt < 7) { return 'šŸŒ’' // Waxing crescent - } else if (day >= 7 && day < 14) { + } else if (dayInt >= 7 && dayInt < 14) { return 'šŸŒ“' // First quarter - } else if (day >= 14 && day < 22) { + } else if (dayInt >= 14 && dayInt < 22) { return 'šŸŒ•' // Full moon - } else if (day >= 22 && day < 29) { + } else if (dayInt >= 22 && dayInt < 29) { return 'šŸŒ—' // Last quarter } diff --git a/mobile/services/utility-service.js b/mobile/services/utility-service.js index 3ebf9a9b0..9d15c8f53 100644 --- a/mobile/services/utility-service.js +++ b/mobile/services/utility-service.js @@ -133,7 +133,7 @@ export const getHijriDateLatin = () => { const [day, month, year] = hijriDate.split(' ') - return `${year} ${day} ${month} ${moonPhaseEmoji(day)}`.replace(/,$/, '').trim() + return `${year} ${day} ${month} ${moonPhaseEmoji(month)}`.replace(/,$/, '').trim() } //replace every letter in a string based on a map defined in the function //the service is kept in the mobile codebase to lower the load on the backend diff --git a/package/Chart.yaml b/package/Chart.yaml index a0c37d87f..7ccb2eb6c 100644 --- a/package/Chart.yaml +++ b/package/Chart.yaml @@ -1,6 +1,6 @@ name: open-arabic-helm description: A Helm Chart for OpenArabic -version: 1444.0.284 +version: 1444.0.286 apiVersion: v2 type: application home: https://openarabic.io diff --git a/web/.yarn/install-state.gz b/web/.yarn/install-state.gz index 464c94f12..3598bd2d5 100644 Binary files a/web/.yarn/install-state.gz and b/web/.yarn/install-state.gz differ diff --git a/web/.yarn/versions/b37e55f9.yml b/web/.yarn/versions/b37e55f9.yml new file mode 100644 index 000000000..e69de29bb diff --git a/web/package.json b/web/package.json index 802a11f8f..fe637a24c 100644 --- a/web/package.json +++ b/web/package.json @@ -4,7 +4,7 @@ "license": "MIT", "homepage": "https://openarabic.io", "repository": "https://github.com/edenmind/OpenArabic", - "version": "1444.12.213", + "version": "1444.12.215", "authors": [ "Yunus Andreasson (https://github.com/YunusAndreasson)" ], diff --git a/web/src/redux/actions.js b/web/src/redux/actions.js index 3437b50f3..3c71c1304 100644 --- a/web/src/redux/actions.js +++ b/web/src/redux/actions.js @@ -20,6 +20,7 @@ export const SET_STATUS = createAction('SET_STATUS') export const SET_TEXT = createAction('SET_TEXT') export const SET_TITLE = createAction('SET_TITLE') export const SET_WORD_BY_WORD = createAction('SET_WORD_BY_WORD') +export const UPDATE_EXPLANATION = createAction('UPDATE_EXPLANATION') export const UPDATE_SENTENCE = createAction('UPDATE_SENTENCE') export const UPDATE_FULL_SENTENCE = createAction('UPDATE_FULL_SENTENCE') export const UPDATE_SENTENCE_QUIZ = createAction('UPDATE_SENTENCE_QUIZ') diff --git a/web/src/redux/reducers.js b/web/src/redux/reducers.js index 4ddab4245..42c9b40df 100644 --- a/web/src/redux/reducers.js +++ b/web/src/redux/reducers.js @@ -79,6 +79,10 @@ const textReducer = createReducer(initialState, (builder) => { const { indexSentence, indexArabicWord, englishWord } = action.value state.text.sentences[indexSentence].words[indexArabicWord].english = englishWord }) + .addCase(actions.UPDATE_EXPLANATION, (state, action) => { + const { indexSentence, indexArabicWord, explanation } = action.value + state.text.sentences[indexSentence].words[indexArabicWord].explanation = explanation + }) .addCase(actions.UPDATE_FULL_SENTENCE, (state, action) => { const { indexSentence, englishWords } = action.value state.text.sentences[indexSentence].words.forEach((word, index) => { @@ -86,8 +90,10 @@ const textReducer = createReducer(initialState, (builder) => { }) }) .addCase(actions.UPDATE_EXPLANATION_SENTENCE, (state, action) => { - const { indexSentence, explanation } = action.value - state.text.sentences[indexSentence].explanation = explanation + const { indexSentence, explanations } = action.value + state.text.sentences[indexSentence].words.forEach((word, index) => { + word.explanation = explanations[index].explanation + }) }) .addCase(actions.UPDATE_SENTENCE_QUIZ, (state, action) => { const { indexSentence, indexArabicWord, quiz } = action.value diff --git a/web/src/screens/text-add-words-generate.js b/web/src/screens/text-add-words-generate.js index b5d47e6e0..eb366ac8c 100644 --- a/web/src/screens/text-add-words-generate.js +++ b/web/src/screens/text-add-words-generate.js @@ -46,7 +46,8 @@ const TextAddWordsGenerate = React.memo((props) => { //prepare the words with its translation const wordPair = { arabic: illegalCharactersRemoved, - english: '' + english: '', + explanation: '' } //add the word to the words array words.push(wordPair) diff --git a/web/src/screens/text-add-words.js b/web/src/screens/text-add-words.js index 079a5b7a2..77bf83b62 100644 --- a/web/src/screens/text-add-words.js +++ b/web/src/screens/text-add-words.js @@ -22,10 +22,25 @@ function TextAddWords() { const [currentPage, setCurrentPage] = React.useState(1) const [suggestions, setSuggestions] = React.useState(Array(text.sentences.length).fill('')) + const handleChangeExplanation = useCallback( + (indexSentence, indexArabicWord, explanation) => { + const sentenceIndex = (currentPage - 1) * PAGE_SIZE + indexSentence + dispatch({ type: 'UPDATE_EXPLANATION', value: { indexSentence: sentenceIndex, indexArabicWord, explanation } }) + }, + [currentPage, dispatch, PAGE_SIZE] + ) + + const handleChangeExplanationSentence = useCallback( + (indexSentence, explanations) => { + const sentenceIndex = (currentPage - 1) * PAGE_SIZE + indexSentence + dispatch({ type: 'UPDATE_EXPLANATION_SENTENCE', value: { indexSentence: sentenceIndex, explanations } }) + }, + [currentPage, dispatch, PAGE_SIZE] + ) + const handleChangeArabic = useCallback( (indexSentence, indexArabicWord, englishWord) => { const sentenceIndex = (currentPage - 1) * PAGE_SIZE + indexSentence - dispatch({ type: 'UPDATE_SENTENCE', value: { indexSentence: sentenceIndex, indexArabicWord, englishWord } }) }, [currentPage, dispatch, PAGE_SIZE] @@ -34,7 +49,6 @@ function TextAddWords() { const handleChangeArabicFullSentence = useCallback( (indexSentence, englishWords) => { const sentenceIndex = (currentPage - 1) * PAGE_SIZE + indexSentence - dispatch({ type: 'UPDATE_FULL_SENTENCE', value: { indexSentence: sentenceIndex, englishWords } }) }, [currentPage, dispatch, PAGE_SIZE] @@ -55,7 +69,7 @@ function TextAddWords() { return sentencesToShow.map((sentence, indexSentence) => ( - +

Sentence

@@ -72,7 +86,6 @@ function TextAddWords() { variant="outlined" /> -

Words:

{sentence.words.map((word, indexArabicWord) => ( @@ -90,6 +103,20 @@ function TextAddWords() { fullWidth />
+ + +
Explanation
+
+ + handleChangeExplanation(indexSentence, indexArabicWord, event.target.value)} + fullWidth + multiline + rows={5} + /> +
Translate +