@moustafahelmi/react-native-quran-app 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/.bundle/config +2 -0
  2. package/.eslintrc.js +4 -0
  3. package/.prettierrc.js +7 -0
  4. package/.watchmanconfig +1 -0
  5. package/App.tsx +23 -0
  6. package/Gemfile +9 -0
  7. package/Gemfile.lock +105 -0
  8. package/MIGRATION.md +163 -0
  9. package/README.md +210 -0
  10. package/ReactotronConfig.js +7 -0
  11. package/__tests__/App.test.tsx +17 -0
  12. package/android/app/build.gradle +118 -0
  13. package/android/app/debug.keystore +0 -0
  14. package/android/app/proguard-rules.pro +10 -0
  15. package/android/app/src/debug/AndroidManifest.xml +9 -0
  16. package/android/app/src/main/AndroidManifest.xml +25 -0
  17. package/android/app/src/main/assets/fonts/Cairo.ttf +0 -0
  18. package/android/app/src/main/assets/fonts/QCF_BSML.ttf +0 -0
  19. package/android/app/src/main/assets/fonts/QCF_P001.ttf +0 -0
  20. package/android/app/src/main/java/com/quranapp/MainActivity.kt +22 -0
  21. package/android/app/src/main/java/com/quranapp/MainApplication.kt +43 -0
  22. package/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
  23. package/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  24. package/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  25. package/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  26. package/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  27. package/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  28. package/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  29. package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  30. package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  31. package/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  32. package/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  33. package/android/app/src/main/res/values/strings.xml +3 -0
  34. package/android/app/src/main/res/values/styles.xml +9 -0
  35. package/android/build.gradle +21 -0
  36. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  37. package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  38. package/android/gradle.properties +41 -0
  39. package/android/gradlew +249 -0
  40. package/android/gradlew.bat +92 -0
  41. package/android/link-assets-manifest.json +17 -0
  42. package/android/settings.gradle +4 -0
  43. package/app.json +4 -0
  44. package/babel.config.js +3 -0
  45. package/index.js +11 -0
  46. package/ios/.xcode.env +11 -0
  47. package/ios/Podfile +40 -0
  48. package/ios/Podfile.lock +1460 -0
  49. package/ios/QuranApp/AppDelegate.h +6 -0
  50. package/ios/QuranApp/AppDelegate.mm +31 -0
  51. package/ios/QuranApp/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
  52. package/ios/QuranApp/Images.xcassets/Contents.json +6 -0
  53. package/ios/QuranApp/Info.plist +57 -0
  54. package/ios/QuranApp/LaunchScreen.storyboard +47 -0
  55. package/ios/QuranApp/PrivacyInfo.xcprivacy +38 -0
  56. package/ios/QuranApp/main.m +10 -0
  57. package/ios/QuranApp.xcodeproj/project.pbxproj +729 -0
  58. package/ios/QuranApp.xcodeproj/xcshareddata/xcschemes/QuranApp.xcscheme +88 -0
  59. package/ios/QuranApp.xcworkspace/contents.xcworkspacedata +10 -0
  60. package/ios/QuranAppTests/Info.plist +24 -0
  61. package/ios/QuranAppTests/QuranAppTests.m +66 -0
  62. package/ios/link-assets-manifest.json +17 -0
  63. package/jest.config.js +3 -0
  64. package/metro.config.js +11 -0
  65. package/package.json +54 -0
  66. package/react-native.config.js +7 -0
  67. package/screenshots/1.png +0 -0
  68. package/screenshots/2.png +0 -0
  69. package/screenshots/3.png +0 -0
  70. package/screenshots/4.png +0 -0
  71. package/screenshots/5.png +0 -0
  72. package/src/assets/fonts/Cairo.ttf +0 -0
  73. package/src/assets/fonts/QCF_BSML.ttf +0 -0
  74. package/src/assets/fonts/QCF_P001.ttf +0 -0
  75. package/src/assets/images/bookmark.png +0 -0
  76. package/src/assets/images/close.png +0 -0
  77. package/src/assets/images/copy.png +0 -0
  78. package/src/assets/images/down-chevron.png +0 -0
  79. package/src/assets/images/mushafFrame.png +0 -0
  80. package/src/assets/images/pause.png +0 -0
  81. package/src/assets/images/play-button.png +0 -0
  82. package/src/assets/images/play.svg +16 -0
  83. package/src/assets/images/playNext.png +0 -0
  84. package/src/assets/images/surahNameFrame.png +0 -0
  85. package/src/common/chapters.ts +1346 -0
  86. package/src/common/constants.ts +27 -0
  87. package/src/common/images.ts +13 -0
  88. package/src/common/index.ts +8 -0
  89. package/src/common/juzs.ts +411 -0
  90. package/src/common/priorityPages.ts +46 -0
  91. package/src/common/themes.ts +7 -0
  92. package/src/components/index.ts +3 -0
  93. package/src/components/lists/index.ts +3 -0
  94. package/src/components/lists/pageVersesList.tsx +220 -0
  95. package/src/components/lists/verseLinesWordsList.tsx +90 -0
  96. package/src/components/modals/index.ts +3 -0
  97. package/src/components/modals/optionsModal.tsx +126 -0
  98. package/src/components/modals/recitersModal.tsx +118 -0
  99. package/src/components/sections/audioPlayer.tsx +185 -0
  100. package/src/components/sections/audioPlayerControls.tsx +100 -0
  101. package/src/components/sections/index.ts +4 -0
  102. package/src/components/sections/loader.tsx +33 -0
  103. package/src/helpers/index.ts +1 -0
  104. package/src/helpers/quranHelpers.tsx +17 -0
  105. package/src/hooks/apis/index.ts +10 -0
  106. package/src/hooks/apis/useGetChapterAudio.ts +111 -0
  107. package/src/hooks/apis/useGetChapterByPage.ts +166 -0
  108. package/src/hooks/apis/useGetChapterLookup.ts +31 -0
  109. package/src/hooks/apis/useGetReciters.ts +44 -0
  110. package/src/hooks/controllers/index.ts +25 -0
  111. package/src/hooks/controllers/useAudioPlayerController.tsx +63 -0
  112. package/src/hooks/controllers/useOptionsModalController.ts +99 -0
  113. package/src/hooks/controllers/usePageFontFileController.ts +255 -0
  114. package/src/hooks/controllers/usePageLineController.ts +108 -0
  115. package/src/hooks/helpers/index.ts +6 -0
  116. package/src/hooks/helpers/useQuranFontPreloader.ts +225 -0
  117. package/src/hooks/index.ts +3 -0
  118. package/src/index.ts +5 -0
  119. package/src/layouts/bismillahText.tsx +18 -0
  120. package/src/layouts/index.ts +4 -0
  121. package/src/layouts/quranChapterHeader.tsx +49 -0
  122. package/src/layouts/quranPageLayout.tsx +178 -0
  123. package/src/types/global.d.ts +7 -0
  124. package/src/types/index.ts +212 -0
  125. package/src/utils/axiosInstance.ts +7 -0
  126. package/src/utils/fileHandlers.ts +96 -0
  127. package/src/utils/handleBeforeAndAfterCurrentVerse.ts +41 -0
  128. package/src/utils/index.ts +6 -0
  129. package/src/utils/matrics.ts +37 -0
  130. package/tsconfig.json +3 -0
  131. package/videos/1.gif +0 -0
@@ -0,0 +1,185 @@
1
+ import {forwardRef, useEffect, useImperativeHandle, useState} from 'react';
2
+ import {
3
+ Dimensions,
4
+ Image,
5
+ StyleSheet,
6
+ Text,
7
+ TouchableOpacity,
8
+ View,
9
+ } from 'react-native';
10
+ import TrackPlayer, {useProgress} from 'react-native-track-player';
11
+ import {
12
+ IReciter,
13
+ ISurahVerse,
14
+ IVersesBeforeAndAfterCurrentVerse,
15
+ } from '../../types';
16
+ import {COLORS, IMAGES} from '../../common';
17
+ import {useAudioPlayerController} from '../../hooks';
18
+ import {RecitersModal} from '../modals';
19
+ import AudioPlayerControls from './audioPlayerControls';
20
+ import Slider from '@react-native-community/slider';
21
+ interface IProps {
22
+ allReciter: IReciter[];
23
+ setSelectedVerse: (verse: ISurahVerse) => void;
24
+ chapterId: number;
25
+ selectedVerse: ISurahVerse;
26
+ versesBeforeAndAfterCurrentVerse: IVersesBeforeAndAfterCurrentVerse;
27
+ setVersesBeforeAndAfterCurrentVerse: (
28
+ value: IVersesBeforeAndAfterCurrentVerse,
29
+ ) => void;
30
+ originalVerse: ISurahVerse[];
31
+ showSlider?: boolean;
32
+ autoCompleteAudioAfterPlayingVerse?: boolean;
33
+ }
34
+ const {height} = Dimensions.get('screen');
35
+ const AudioPlayer = (props: IProps, ref: any) => {
36
+ const {
37
+ allReciter,
38
+ setSelectedVerse,
39
+ chapterId,
40
+ selectedVerse,
41
+ versesBeforeAndAfterCurrentVerse,
42
+ setVersesBeforeAndAfterCurrentVerse,
43
+ originalVerse,
44
+ showSlider,
45
+ autoCompleteAudioAfterPlayingVerse,
46
+ } = props;
47
+ const [selectedReciter, setSelectedReciter] = useState<IReciter>();
48
+ const {duration, position} = useProgress();
49
+ const {
50
+ changeHandler,
51
+ renderplayPauseBtn,
52
+ onPlayPause,
53
+ openReciterModal,
54
+ recitersModalRef,
55
+ formatTime,
56
+ } = useAudioPlayerController();
57
+ const [showPlayer, setShowPlayer] = useState(false);
58
+ useImperativeHandle(ref, () => ({
59
+ async setShowPlayerHandler(value: boolean) {
60
+ // if (value === false) await TrackPlayer?.reset();
61
+ setShowPlayer(value);
62
+ },
63
+ isPlayerShown() {
64
+ return showPlayer;
65
+ },
66
+ _renderSelelctedReciter() {
67
+ return selectedReciter;
68
+ },
69
+ }));
70
+ useEffect(() => {
71
+ if (allReciter?.length > 0) setSelectedReciter(allReciter[0]);
72
+ }, [allReciter]);
73
+ const closeAudioPlayer = async () => {
74
+ setShowPlayer(false);
75
+ await TrackPlayer.pause();
76
+ setSelectedVerse({} as ISurahVerse);
77
+ };
78
+ return (
79
+ showPlayer && (
80
+ <View style={styles.container}>
81
+ <View style={styles.closeContainer}>
82
+ <TouchableOpacity
83
+ onPress={closeAudioPlayer}
84
+ style={[{backgroundColor: COLORS.white, borderRadius: 10}]}>
85
+ <Image source={IMAGES.close} style={styles.closeIcon} />
86
+ </TouchableOpacity>
87
+ </View>
88
+ {showSlider && (
89
+ <Slider
90
+ minimumValue={0}
91
+ maximumValue={duration}
92
+ value={position}
93
+ minimumTrackTintColor={COLORS.darkBlack}
94
+ maximumTrackTintColor={COLORS.light}
95
+ thumbTintColor={COLORS.lighBlack}
96
+ onSlidingComplete={changeHandler}
97
+ />
98
+ )}
99
+ <View style={styles.row}>
100
+ <View style={{flex: 1}}>
101
+ <Text>
102
+ {formatTime(position)} / {formatTime(duration)}
103
+ </Text>
104
+ </View>
105
+ <View style={styles.controlersContainer}>
106
+ <AudioPlayerControls
107
+ onPlayPause={onPlayPause}
108
+ renderplayPauseBtn={renderplayPauseBtn}
109
+ versesBeforeAndAfterCurrentVerse={
110
+ versesBeforeAndAfterCurrentVerse
111
+ }
112
+ setVersesBeforeAndAfterCurrentVerse={
113
+ setVersesBeforeAndAfterCurrentVerse
114
+ }
115
+ selectedReciter={selectedReciter as IReciter}
116
+ setSelectedVerse={setSelectedVerse}
117
+ selectedVerse={selectedVerse}
118
+ originalVerse={originalVerse}
119
+ autoCompleteAudioAfterPlayingVerse={
120
+ autoCompleteAudioAfterPlayingVerse
121
+ }
122
+ />
123
+ </View>
124
+
125
+ <TouchableOpacity
126
+ onPress={openReciterModal}
127
+ style={styles.recitersBtn}>
128
+ <Text numberOfLines={1}>{selectedReciter?.name}</Text>
129
+ <Image source={IMAGES.arrowDown} style={styles.closeIcon} />
130
+ </TouchableOpacity>
131
+ </View>
132
+ <RecitersModal
133
+ ref={recitersModalRef}
134
+ allReciter={allReciter}
135
+ selectedReciter={selectedReciter}
136
+ setSelectedReciter={setSelectedReciter}
137
+ chapterId={chapterId}
138
+ selectedVerse={selectedVerse}
139
+ autoCompleteAudioAfterPlayingVerse={
140
+ autoCompleteAudioAfterPlayingVerse
141
+ }
142
+ />
143
+ </View>
144
+ )
145
+ );
146
+ };
147
+
148
+ export default forwardRef(AudioPlayer);
149
+
150
+ const styles = StyleSheet.create({
151
+ container: {
152
+ width: '90%',
153
+ backgroundColor: 'white',
154
+ position: 'absolute',
155
+ bottom: 10,
156
+ borderRadius: 10,
157
+ padding: 10,
158
+ },
159
+ row: {
160
+ flexDirection: 'row',
161
+ justifyContent: 'space-between',
162
+ alignItems: 'center',
163
+ width: '90%',
164
+ alignSelf: 'center',
165
+ },
166
+ controlersContainer: {
167
+ flex: 2,
168
+ alignItems: 'center',
169
+ justifyContent: 'center',
170
+ },
171
+ closeContainer: {
172
+ width: '100%',
173
+ alignItems: 'flex-start',
174
+ position: 'absolute',
175
+ top: -20,
176
+ left: 0,
177
+ },
178
+ closeIcon: {width: 25, height: 25},
179
+ recitersBtn: {
180
+ flex: 1,
181
+ paddingHorizontal: 5,
182
+ flexDirection: 'row',
183
+ alignItems: 'center',
184
+ },
185
+ });
@@ -0,0 +1,100 @@
1
+ import React from 'react';
2
+ import {I18nManager, Image, TouchableOpacity, View} from 'react-native';
3
+ import {IMAGES} from '../../common';
4
+ import {
5
+ IReciter,
6
+ ISurahVerse,
7
+ IVersesBeforeAndAfterCurrentVerse,
8
+ } from '../../types';
9
+ import {useOptionsModalController} from '../../hooks';
10
+ import handleVersesBeforeAndAfterCurrentVerse from '../../utils/handleBeforeAndAfterCurrentVerse';
11
+ interface IProps {
12
+ onPlayPause: () => void;
13
+ renderplayPauseBtn: () => React.ReactNode;
14
+ versesBeforeAndAfterCurrentVerse: IVersesBeforeAndAfterCurrentVerse;
15
+ selectedReciter: IReciter;
16
+ setSelectedVerse: (value: ISurahVerse) => void;
17
+ selectedVerse: ISurahVerse;
18
+ setVersesBeforeAndAfterCurrentVerse: (
19
+ value: IVersesBeforeAndAfterCurrentVerse,
20
+ ) => void;
21
+ originalVerse: ISurahVerse[];
22
+ autoCompleteAudioAfterPlayingVerse?: boolean;
23
+ }
24
+ const AudioPlayerControls = ({
25
+ onPlayPause,
26
+ renderplayPauseBtn,
27
+ versesBeforeAndAfterCurrentVerse,
28
+ selectedReciter,
29
+ selectedVerse,
30
+ setSelectedVerse,
31
+ setVersesBeforeAndAfterCurrentVerse,
32
+ autoCompleteAudioAfterPlayingVerse,
33
+ originalVerse,
34
+ }: IProps) => {
35
+ const {onPlayerPress} = useOptionsModalController({} as any);
36
+ return (
37
+ <View
38
+ style={{
39
+ flexDirection: 'row',
40
+ alignItems: 'center',
41
+ }}>
42
+ <TouchableOpacity
43
+ style={{
44
+ marginHorizontal: 12,
45
+ transform: [{rotate: I18nManager.isRTL ? '0deg' : '180deg'}],
46
+ }}
47
+ onPress={() => {
48
+ if (!versesBeforeAndAfterCurrentVerse?.beforeCurrentVerse) return;
49
+ if (versesBeforeAndAfterCurrentVerse?.beforeCurrentVerse)
50
+ setSelectedVerse(
51
+ versesBeforeAndAfterCurrentVerse?.beforeCurrentVerse,
52
+ );
53
+ handleVersesBeforeAndAfterCurrentVerse({
54
+ selectedVerse: versesBeforeAndAfterCurrentVerse?.beforeCurrentVerse,
55
+ setVersesBeforeAndAfterCurrentVerse,
56
+ originalVerse,
57
+ });
58
+
59
+ onPlayerPress({
60
+ reciterId: selectedReciter?.id,
61
+ verse_key:
62
+ versesBeforeAndAfterCurrentVerse?.beforeCurrentVerse?.verse_key,
63
+ chapterId: selectedVerse?.chapter_id,
64
+ autoCompleteAudioAfterPlayingVerse,
65
+ });
66
+ }}>
67
+ <Image source={IMAGES.playNext} style={{width: 20, height: 20}} />
68
+ </TouchableOpacity>
69
+
70
+ <TouchableOpacity style={{marginHorizontal: 12}} onPress={onPlayPause}>
71
+ {renderplayPauseBtn()}
72
+ </TouchableOpacity>
73
+
74
+ <TouchableOpacity
75
+ style={{
76
+ marginHorizontal: 12,
77
+ transform: [{rotate: I18nManager.isRTL ? '180deg' : '0deg'}],
78
+ }}
79
+ onPress={() => {
80
+ if (!versesBeforeAndAfterCurrentVerse?.afterCurrentVerse) return;
81
+ onPlayerPress({
82
+ reciterId: selectedReciter?.id,
83
+ verse_key:
84
+ versesBeforeAndAfterCurrentVerse?.afterCurrentVerse?.verse_key,
85
+ chapterId: selectedVerse?.chapter_id,
86
+ autoCompleteAudioAfterPlayingVerse,
87
+ });
88
+ setSelectedVerse(versesBeforeAndAfterCurrentVerse?.afterCurrentVerse);
89
+ handleVersesBeforeAndAfterCurrentVerse({
90
+ selectedVerse: versesBeforeAndAfterCurrentVerse?.afterCurrentVerse,
91
+ setVersesBeforeAndAfterCurrentVerse,
92
+ originalVerse,
93
+ });
94
+ }}>
95
+ <Image source={IMAGES.playNext} style={{width: 20, height: 20}} />
96
+ </TouchableOpacity>
97
+ </View>
98
+ );
99
+ };
100
+ export default AudioPlayerControls;
@@ -0,0 +1,4 @@
1
+ import AudioPlayer from './audioPlayer';
2
+ import AudioPlayerControls from './audioPlayerControls';
3
+ import Loader from './loader';
4
+ export {AudioPlayer, AudioPlayerControls, Loader};
@@ -0,0 +1,33 @@
1
+ import {ActivityIndicator, I18nManager, Text, View} from 'react-native';
2
+ import {COLORS} from '../../common';
3
+
4
+ const Loader = ({
5
+ chapterProgress,
6
+ showTxt,
7
+ }: {
8
+ chapterProgress?: number;
9
+ showTxt?: boolean;
10
+ }) => {
11
+ const txt =
12
+ chapterProgress && chapterProgress < 100
13
+ ? I18nManager.isRTL
14
+ ? 'جاري تحميل ملقات السورة'
15
+ : 'Downloading the chapter files'
16
+ : I18nManager.isRTL
17
+ ? 'جاري تحميل خطوط السورة'
18
+ : 'Downloading the chapter fonts';
19
+ const chapterProgressCondition = chapterProgress
20
+ ? chapterProgress > 0 && chapterProgress != 100
21
+ : false;
22
+ return (
23
+ <View style={{alignItems: 'center', justifyContent: 'center'}}>
24
+ <ActivityIndicator color={COLORS.lightBackground} size={25} />
25
+ <Text style={{marginTop: 5}}>
26
+ {showTxt && txt}
27
+ {chapterProgressCondition && `(${chapterProgress}%)`}
28
+ </Text>
29
+ </View>
30
+ );
31
+ };
32
+
33
+ export default Loader;
@@ -0,0 +1 @@
1
+ export * from './quranHelpers';
@@ -0,0 +1,17 @@
1
+ import {IQuranChapters} from '../types';
2
+ import {QuranChapters, SURAH_WORD_AR} from '../common';
3
+
4
+ export const getChapterCodeV1 = (chapterId: number): number =>
5
+ QuranChapters.find((item: IQuranChapters) => item?.id === chapterId)
6
+ ?.code_v1 as number;
7
+
8
+ export const _renderChapterName = (chapterId: number) => {
9
+ const surahName = `${String.fromCharCode(
10
+ getChapterCodeV1(chapterId),
11
+ )} ${SURAH_WORD_AR}`;
12
+ return surahName;
13
+ };
14
+
15
+ export const _renderChapterAyahs = (chapterId: number) => {
16
+ return QuranChapters?.find(chapter => chapter?.id == chapterId)?.versesCount;
17
+ };
@@ -0,0 +1,10 @@
1
+ import useGetChapterByPage from './useGetChapterByPage';
2
+ import useGetChapterLookup from './useGetChapterLookup';
3
+ import useGetReciters from './useGetReciters';
4
+ import useGetChapterAudio from './useGetChapterAudio';
5
+ export {
6
+ useGetChapterByPage,
7
+ useGetChapterLookup,
8
+ useGetReciters,
9
+ useGetChapterAudio,
10
+ };
@@ -0,0 +1,111 @@
1
+ import TrackPlayer from 'react-native-track-player';
2
+ import {QURAN_API} from '../../common';
3
+ import {axiosInstance} from '../../utils';
4
+ import {useState} from 'react';
5
+
6
+ let timeoutAudio: any;
7
+ const useGetChapterAudio = () => {
8
+ const [isVersePositionLoading, setIsVersePositionLoading] = useState(false);
9
+ const getVerseAudio = async (
10
+ reciterId: number,
11
+ verseKey: string,
12
+ callback?: () => void,
13
+ autoCompleteAudioAfterPlayingVerse?: boolean,
14
+ ) => {
15
+ // clear timeout created before
16
+ if (timeoutAudio) clearTimeout(timeoutAudio);
17
+ try {
18
+ setIsVersePositionLoading(true);
19
+ const url = `${QURAN_API}/audio/reciters/${reciterId}/timestamp?verse_key=${verseKey}`;
20
+ const response = await axiosInstance.get(url);
21
+ const result = response.data?.result;
22
+ setIsVersePositionLoading(false);
23
+ const start = result.timestamp_from / 1000;
24
+ await TrackPlayer.play();
25
+ await TrackPlayer.seekTo(start);
26
+ if (callback) callback();
27
+ const stop = result.timestamp_to / 1000 - result.timestamp_from / 1000;
28
+ if (!autoCompleteAudioAfterPlayingVerse)
29
+ timeoutAudio = setTimeout(() => {
30
+ TrackPlayer.pause();
31
+ clearTimeout(timeoutAudio);
32
+ }, stop * 1000);
33
+ } catch (e) {
34
+ setIsVersePositionLoading(false);
35
+ }
36
+ };
37
+
38
+ const getChapterAudionUrl = async ({
39
+ reciterId,
40
+ chapterId,
41
+ verse_key,
42
+ autoCompleteAudioAfterPlayingVerse,
43
+ callback,
44
+ }: {
45
+ reciterId: number;
46
+ chapterId: number;
47
+ verse_key?: string;
48
+ autoCompleteAudioAfterPlayingVerse?: boolean;
49
+ callback?: () => void;
50
+ }) => {
51
+ const queryParams = {
52
+ chapter: chapterId,
53
+ segments: false,
54
+ };
55
+ const queryString = Object.entries(queryParams)
56
+ .map(
57
+ ([key, value]) =>
58
+ `${encodeURIComponent(key)}=${encodeURIComponent(value)}`,
59
+ )
60
+ .join('&');
61
+ try {
62
+ const url = `${QURAN_API}/audio/reciters/${reciterId}/audio_files?${queryString}`;
63
+ const response = await axiosInstance.get(url);
64
+ const chapterAudioFiles = response.data.audio_files;
65
+ setUpChapterAudio(
66
+ chapterAudioFiles,
67
+ verse_key,
68
+ reciterId,
69
+ chapterId,
70
+ autoCompleteAudioAfterPlayingVerse,
71
+ callback,
72
+ );
73
+ return response?.data;
74
+ } catch (error) {}
75
+ };
76
+
77
+ const resetTrackPlayer = async () => {
78
+ await TrackPlayer.reset();
79
+ };
80
+
81
+ const setUpChapterAudio = async (
82
+ chapterAudioFiles: [{audio_url: string}],
83
+ verse_key?: string,
84
+ reciterId?: number,
85
+ chapterId?: number,
86
+ autoCompleteAudioAfterPlayingVerse?: boolean,
87
+ callback?: () => void,
88
+ ) => {
89
+ const audioFileUrl = chapterAudioFiles[0].audio_url;
90
+ resetTrackPlayer();
91
+ await TrackPlayer.add([
92
+ {
93
+ id: '1',
94
+ url: audioFileUrl,
95
+ title: 'Al Quran',
96
+ duration: 60,
97
+ chapter_id: chapterId,
98
+ },
99
+ ]);
100
+ if (verse_key)
101
+ getVerseAudio(
102
+ reciterId as number,
103
+ verse_key,
104
+ callback,
105
+ autoCompleteAudioAfterPlayingVerse,
106
+ );
107
+ };
108
+ return {getChapterAudionUrl, getVerseAudio, isVersePositionLoading};
109
+ };
110
+
111
+ export default useGetChapterAudio;
@@ -0,0 +1,166 @@
1
+ import {useEffect, useState} from 'react';
2
+ import {
3
+ IChapterLookUp,
4
+ IChapterVerses,
5
+ IQuranJuzs,
6
+ ISurahVerse,
7
+ QuranTypesEnums,
8
+ } from '../../types';
9
+ import {
10
+ DEFAULT_VERSES_PARAMS,
11
+ QURAN_CHAPTERS_DIRECTORY,
12
+ QURAN_JUZS_DIRECTORY,
13
+ QuranJuzs,
14
+ } from '../../common';
15
+ import {
16
+ axiosInstance,
17
+ handleQuranChaptersDirectory,
18
+ handleQuranJuzsDirectory,
19
+ isFileExists,
20
+ readFromLocalStorageFile,
21
+ saveChapterAsJsonFile,
22
+ } from '../../utils';
23
+ import {usePageFontFileController, usePageLineController} from '../controllers';
24
+ interface IProps {
25
+ chapterLookUp: IChapterLookUp[] | undefined;
26
+ chapterId: number;
27
+ type: QuranTypesEnums;
28
+ QURAN_FONTS_API: string;
29
+ }
30
+ const useGetChapterByPage = ({
31
+ chapterLookUp,
32
+ chapterId,
33
+ type,
34
+ QURAN_FONTS_API,
35
+ }: IProps) => {
36
+ const [chapterVerses, setChapterVerse] = useState<IChapterVerses[]>([]);
37
+ const [isLoading, setIsLoading] = useState(true);
38
+ const [chapterProgress, setChapterProgress] = useState(0);
39
+
40
+ const {downoladThePageFont} = usePageFontFileController();
41
+ const {_renderVersesNewForm} = usePageLineController();
42
+
43
+ useEffect(() => {
44
+ checkIfTheChapterFileExistsInLocalStorage();
45
+ }, []);
46
+ const setChapetersHandler = (chapters: IChapterVerses[]) => {
47
+ setChapterVerse([...chapters]);
48
+ };
49
+
50
+ const checkIfTheChapterFileExistsInLocalStorage = async () => {
51
+ const chapterFileName = `${chapterId}.json`;
52
+ const chapterPath = `${
53
+ type === QuranTypesEnums?.chapter
54
+ ? QURAN_CHAPTERS_DIRECTORY
55
+ : QURAN_JUZS_DIRECTORY
56
+ }/${chapterFileName}`;
57
+ const isFileExistsLocaly = await isFileExists(chapterPath);
58
+ if (type === QuranTypesEnums.chapter) await handleQuranChaptersDirectory();
59
+ else await handleQuranJuzsDirectory();
60
+
61
+ if (!isFileExistsLocaly) {
62
+ const res: IChapterVerses[] | undefined | any =
63
+ await getTargetChapterPage();
64
+ await saveChapterAsJsonFile(chapterFileName, JSON.stringify(res), type);
65
+ await handleFontLoad(res);
66
+ if (res) setChapetersHandler([...res]);
67
+ } else {
68
+ const storedChapterFile = await readFromLocalStorageFile(
69
+ chapterFileName,
70
+ type,
71
+ );
72
+ await handleFontLoad(JSON.parse(storedChapterFile as any));
73
+ if (storedChapterFile)
74
+ setChapetersHandler([...JSON.parse(storedChapterFile)]);
75
+ }
76
+ };
77
+ const handleFontLoad = async (verses: ISurahVerse[]) => {
78
+ const pagesNumbers = getSurahOrJuzPagesCount(verses);
79
+ const promises = pagesNumbers?.map((item: number, index) => {
80
+ return downoladThePageFont(Number(item), () => {}, QURAN_FONTS_API);
81
+ });
82
+ try {
83
+ const promiseRes = await Promise.all(promises);
84
+ setTimeout(() => {
85
+ setIsLoading(false);
86
+ }, 500);
87
+ } catch (e) {}
88
+ };
89
+
90
+ const getTargetChapterPage = async () => {
91
+ const params = _rednerQueryParams();
92
+ try {
93
+ const response = await axiosInstance.get(
94
+ `/verses/by_${type}/${chapterId}?${params}`,
95
+ {
96
+ onDownloadProgress: progressEvent => {
97
+ if (progressEvent && progressEvent?.total) {
98
+ setChapterProgress(
99
+ Math.round((progressEvent.loaded / progressEvent?.total) * 100),
100
+ );
101
+ }
102
+ },
103
+ },
104
+ );
105
+ const allChapterVerses: ISurahVerse[] = response?.data?.verses;
106
+ return await handleAllChapterPagesFormat(allChapterVerses);
107
+ } catch (e) {}
108
+ };
109
+ const getSurahOrJuzPagesCount = (verses: ISurahVerse[]) => {
110
+ const pagesNumbers: number[] = [];
111
+ verses?.map((verse: ISurahVerse) => {
112
+ if (!pagesNumbers.includes(verse?.page_number))
113
+ pagesNumbers.push(verse?.page_number);
114
+ });
115
+ return pagesNumbers;
116
+ };
117
+ const handleAllChapterPagesFormat = async (
118
+ allChapterVerses: ISurahVerse[],
119
+ ) => {
120
+ const chapterPagesWithVersesToSave = [];
121
+ const pagesArr = getSurahOrJuzPagesCount(allChapterVerses);
122
+ const pagesCount = pagesArr?.length;
123
+ for (let i = 0; i < pagesCount; i++) {
124
+ const currentPage: any = pagesArr[i];
125
+ const currentPageVerses = allChapterVerses?.filter(
126
+ item => item?.page_number == currentPage,
127
+ );
128
+ const currentPageJuzNumber = currentPageVerses[0]?.juz_number;
129
+ const chapter_id = currentPageVerses[0]?.chapter_id;
130
+ const isFirstChapterPage = currentPageVerses[0]?.verse_number == 1;
131
+ chapterPagesWithVersesToSave.push({
132
+ verses: _renderVersesNewForm({
133
+ pageVerses: currentPageVerses,
134
+ }),
135
+ page_number: currentPage,
136
+ juz_number: currentPageJuzNumber,
137
+ originalVerses: currentPageVerses,
138
+ chapter_id,
139
+ isFirstChapterPage,
140
+ });
141
+ }
142
+
143
+ return chapterPagesWithVersesToSave;
144
+ };
145
+ const _rednerQueryParams = () => {
146
+ const juzVersesCount = QuranTypesEnums?.juz
147
+ ? QuranJuzs.find((item: IQuranJuzs) => item?.juz_number == chapterId)
148
+ ?.verses_count
149
+ : 0;
150
+ const per_page = type === QuranTypesEnums.chapter ? 'all' : juzVersesCount;
151
+ const queryParams = {
152
+ ...DEFAULT_VERSES_PARAMS,
153
+ per_page,
154
+ };
155
+ const queryString = Object.entries(queryParams)
156
+ .map(
157
+ ([key, value]) =>
158
+ `${encodeURIComponent(key)}=${encodeURIComponent(value)}`,
159
+ )
160
+ .join('&');
161
+ return queryString;
162
+ };
163
+ return {chapterVerses, isLoading, chapterProgress};
164
+ };
165
+
166
+ export default useGetChapterByPage;
@@ -0,0 +1,31 @@
1
+ import {useEffect, useState} from 'react';
2
+ import {IChapterLookUp, QuranTypesEnums} from '../../types';
3
+ import {axiosInstance} from '../../utils';
4
+
5
+ interface IProps {
6
+ chapterId: number;
7
+ type: QuranTypesEnums;
8
+ }
9
+
10
+ const useGetChapterLookup = ({chapterId, type}: IProps) => {
11
+ const [chapterLookUp, setChapterLookUp] = useState<IChapterLookUp[]>();
12
+ useEffect(() => {
13
+ fetchChapterLookUp();
14
+ }, []);
15
+ const fetchChapterLookUp = async () => {
16
+ try {
17
+ let response = await axiosInstance.get(
18
+ `https://api.quran.com/api/qdc/pages/lookup?${type}_number=${chapterId}`,
19
+ );
20
+ let pageslookup = Object.entries(response?.data?.pages);
21
+ const pageslookupEdited = pageslookup?.map(([key, value]) => ({
22
+ page_number: key,
23
+ page_range: value,
24
+ }));
25
+ setChapterLookUp(pageslookupEdited as IChapterLookUp[]);
26
+ } catch (e) {}
27
+ };
28
+ return {chapterLookUp};
29
+ };
30
+
31
+ export default useGetChapterLookup;