+
{sortBy !== 'juz' ? (
// when succesfull data retrieve; and sortBy == 'revelation-order' or 'surah'
{({ index, style, data }) => (
= ({
) : (
// when succesfull data retrieve; and sortBy == 'juzs'
)}
diff --git a/src/features/list-prayer-times/api/usePrayerTimes.ts b/src/features/list-prayer-times/api/usePrayerTimes.ts
new file mode 100644
index 0000000..1c57218
--- /dev/null
+++ b/src/features/list-prayer-times/api/usePrayerTimes.ts
@@ -0,0 +1,52 @@
+import { useQuery } from '@tanstack/react-query';
+import { PrayerTimesResponse } from '../types/PrayerTimesResponse';
+import axios from 'axios';
+import dayjs from 'dayjs';
+import { getGeoLocation } from 'utils/geoLocation';
+import { Position } from '@capacitor/geolocation';
+
+interface Props {
+ date?: string;
+ method?: number;
+}
+
+/**
+ * @param {number} method - Calculation method
+ * - 0 - Shia Ithna-Ansari
+ * - 1 - University of Islamic Sciences, Karachi
+ * - 2 - Islamic Society of North America
+ * - 3 - Muslim World League
+ * - 4 - Umm Al-Qura University, Makkah
+ * - 5 - Egyptian General Authority of Survey
+ * - 7 - Institute of Geophysics, University of Tehran
+ * - 8 - Gulf Region
+ * - 9 - Kuwait
+ * - 10 - Qatar
+ * - 11 - Majlis Ugama Islam Singapura, Singapore
+ * - 12 - Union Organization islamic de France
+ * - 13 - Diyanet İşleri Başkanlığı, Turkey
+ * - 14 - Spiritual Administration of Muslims of Russia
+ * - 15 - Moonsighting Committee Worldwide (also requires shafaq parameter)
+ * - 16 - Dubai (unofficial)
+ */
+function usePrayerTimes({
+ date = dayjs().format('DD-MM-YYYY'),
+ method = 4,
+}: Props = {}) {
+ return useQuery
({
+ queryKey: ['prayer-times', date],
+ queryFn: async () => {
+ const geoLocation = await getGeoLocation();
+
+ const { data } = await axios.get(
+ `https://api.aladhan.com/v1/timings/${date}?latitude=${geoLocation?.coords.latitude}&longitude=${geoLocation?.coords.longitude}&method=${method}`
+ );
+ return data;
+ },
+ staleTime: Infinity,
+ cacheTime: Infinity,
+ structuralSharing: false,
+ });
+}
+
+export { usePrayerTimes };
diff --git a/src/features/list-prayer-times/components/ListPrayerTimes.tsx b/src/features/list-prayer-times/components/ListPrayerTimes.tsx
new file mode 100644
index 0000000..804e676
--- /dev/null
+++ b/src/features/list-prayer-times/components/ListPrayerTimes.tsx
@@ -0,0 +1,44 @@
+import { IonItem, IonLabel, IonList, IonRippleEffect } from '@ionic/react';
+import { Timings } from '../types/PrayerTimesResponse';
+import dayjs from 'dayjs';
+import customParseFormat from 'dayjs/plugin/customParseFormat';
+import relativeTime from 'dayjs/plugin/relativeTime';
+import { useMemo } from 'react';
+
+dayjs.extend(relativeTime);
+dayjs.extend(customParseFormat);
+
+type Props = {
+ timings: Timings;
+};
+
+const ListPrayerTimes: React.FC = ({ timings }) => {
+ return (
+
+ {Object.keys(timings).map((prayerName, indx) => {
+ const prayerTime = useMemo(
+ () => dayjs(timings[prayerName as keyof Timings], 'HH:mm'),
+ [prayerName, timings]
+ );
+
+ return (
+
+
+ {prayerName}
+
+ {prayerTime.format('h:mm A')}
+
+
+ );
+ })}
+
+ );
+};
+
+export { ListPrayerTimes };
diff --git a/src/features/list-prayer-times/index.ts b/src/features/list-prayer-times/index.ts
new file mode 100644
index 0000000..0248ac0
--- /dev/null
+++ b/src/features/list-prayer-times/index.ts
@@ -0,0 +1,4 @@
+export * from './types/PrayerTimesResponse';
+export * from './api/usePrayerTimes';
+export * from './components/ListPrayerTimes';
+export * from './utils/prayerTimes';
diff --git a/src/features/list-prayer-times/types/PrayerTimesResponse.ts b/src/features/list-prayer-times/types/PrayerTimesResponse.ts
new file mode 100644
index 0000000..c195118
--- /dev/null
+++ b/src/features/list-prayer-times/types/PrayerTimesResponse.ts
@@ -0,0 +1,106 @@
+export interface PrayerTimesResponse {
+ code: number;
+ status: string;
+ data: Data;
+}
+
+export interface Data {
+ timings: Timings;
+ date: DateClass;
+ meta: Meta;
+}
+
+export interface DateClass {
+ readable: string;
+ timestamp: string;
+ hijri: Hijri;
+ gregorian: Gregorian;
+}
+
+export interface Gregorian {
+ date: string;
+ format: string;
+ day: string;
+ weekday: GregorianWeekday;
+ month: GregorianMonth;
+ year: string;
+ designation: Designation;
+}
+
+export interface Designation {
+ abbreviated: string;
+ expanded: string;
+}
+
+export interface GregorianMonth {
+ number: number;
+ en: string;
+}
+
+export interface GregorianWeekday {
+ en: string;
+}
+
+export interface Hijri {
+ date: string;
+ format: string;
+ day: string;
+ weekday: HijriWeekday;
+ month: HijriMonth;
+ year: string;
+ designation: Designation;
+ holidays: any[];
+}
+
+export interface HijriMonth {
+ number: number;
+ en: string;
+ ar: string;
+}
+
+export interface HijriWeekday {
+ en: string;
+ ar: string;
+}
+
+export interface Meta {
+ latitude: number;
+ longitude: number;
+ timezone: string;
+ method: Method;
+ latitudeAdjustmentMethod: string;
+ midnightMode: string;
+ school: string;
+ offset: { [key: string]: number };
+}
+
+export interface Method {
+ id: number;
+ name: string;
+ params: Params;
+ location: Location;
+}
+
+export interface Location {
+ latitude: number;
+ longitude: number;
+}
+
+export interface Params {
+ Fajr: number;
+ Isha: number;
+}
+
+export interface Timings {
+ Fajr: string;
+ Sunrise: string;
+ Dhuhr: string;
+ Asr: string;
+ Sunset: string;
+ Maghrib: string;
+ Isha: string;
+ Imsak: string;
+ Midnight: string;
+ Firstthird: string;
+ Lastthird: string;
+}
diff --git a/src/features/list-prayer-times/utils/prayerTimes.ts b/src/features/list-prayer-times/utils/prayerTimes.ts
new file mode 100644
index 0000000..5d65748
--- /dev/null
+++ b/src/features/list-prayer-times/utils/prayerTimes.ts
@@ -0,0 +1,34 @@
+import dayjs from 'dayjs';
+import { Timings } from '../types/PrayerTimesResponse';
+
+export function getNextPrayer(prayerTimes: Timings) {
+ const now = dayjs();
+ const prayerOrder = ['Fajr', 'Dhuhr', 'Asr', 'Maghrib', 'Isha'];
+ let nextPrayerIndex = 0;
+
+ for (let i = 0; i < prayerOrder.length; i++) {
+ const prayerTime = dayjs(
+ `${now.format('YYYY-MM-DD')} ${
+ prayerTimes[prayerOrder[i] as keyof Timings]
+ }`
+ );
+
+ if (prayerTime.isAfter(now)) {
+ nextPrayerIndex = i;
+ break;
+ }
+ }
+
+ const nextPrayerName = prayerOrder[nextPrayerIndex];
+ const nextPrayerTime = dayjs(
+ `${now.format('YYYY-MM-DD')} ${
+ prayerTimes[nextPrayerName as keyof Timings]
+ }`
+ );
+
+ return {
+ name: nextPrayerName,
+ time: nextPrayerTime.toDate(),
+ readableTime: nextPrayerTime.format('h:mm A'),
+ };
+}
diff --git a/src/features/settings/api/useTranslations.ts b/src/features/settings/api/useTranslations.ts
index d35b3fd..ea93d88 100644
--- a/src/features/settings/api/useTranslations.ts
+++ b/src/features/settings/api/useTranslations.ts
@@ -15,7 +15,6 @@ function useTranslations() {
staleTime: Infinity,
cacheTime: Infinity,
structuralSharing: false,
- notifyOnChangeProps: ['data'],
});
}
diff --git a/src/features/view-chapter/api/useChapter.ts b/src/features/view-chapter/api/useChapter.ts
index 0438278..6b84c0f 100644
--- a/src/features/view-chapter/api/useChapter.ts
+++ b/src/features/view-chapter/api/useChapter.ts
@@ -20,7 +20,6 @@ function useChapter({ chapterId }: Props) {
staleTime: Infinity,
cacheTime: Infinity,
structuralSharing: false,
- notifyOnChangeProps: ['data', 'isLoading', 'error'],
});
}
diff --git a/src/features/view-chapter/api/useChapterVerses.ts b/src/features/view-chapter/api/useChapterVerses.ts
index 8ed2c3c..6cc0782 100644
--- a/src/features/view-chapter/api/useChapterVerses.ts
+++ b/src/features/view-chapter/api/useChapterVerses.ts
@@ -36,14 +36,6 @@ function useChapterVerses({
cacheTime: Infinity,
staleTime: Infinity,
structuralSharing: false,
- notifyOnChangeProps: [
- 'data',
- 'error',
- 'isLoading',
- 'fetchNextPage',
- 'isFetchingNextPage',
- 'hasNextPage',
- ],
});
}
diff --git a/src/features/view-chapter/api/useVersesUthmani.ts b/src/features/view-chapter/api/useVersesUthmani.ts
index cf411af..ba81463 100644
--- a/src/features/view-chapter/api/useVersesUthmani.ts
+++ b/src/features/view-chapter/api/useVersesUthmani.ts
@@ -32,14 +32,6 @@ function useVersesUthmani({ chapterId, pages }: Props) {
cacheTime: Infinity,
staleTime: Infinity,
structuralSharing: false,
- notifyOnChangeProps: [
- 'data',
- 'error',
- 'isLoading',
- 'fetchNextPage',
- 'isFetchingNextPage',
- 'hasNextPage',
- ],
});
}
diff --git a/src/features/view-chapter/components/TranslationVerseItem.tsx b/src/features/view-chapter/components/TranslationVerseItem.tsx
index 70c1e85..db658c4 100644
--- a/src/features/view-chapter/components/TranslationVerseItem.tsx
+++ b/src/features/view-chapter/components/TranslationVerseItem.tsx
@@ -39,12 +39,14 @@ const TranslationVerseItem: React.FC<{ verse: Verse }> = ({ verse }) => {
{verse?.translations.map((translation, indx) => (
{removeHtmlTags(translation.text)}
-
- {translation.resource_name}
))}
+
+ {verse.verse_number}
+
);
diff --git a/src/screens/PrayerTimes.tsx b/src/screens/PrayerTimes.tsx
new file mode 100644
index 0000000..d468fbc
--- /dev/null
+++ b/src/screens/PrayerTimes.tsx
@@ -0,0 +1,99 @@
+import {
+ IonCard,
+ IonCardContent,
+ IonCardHeader,
+ IonCardTitle,
+ IonContent,
+ IonHeader,
+ IonIcon,
+ IonItem,
+ IonLabel,
+ IonList,
+ IonPage,
+ IonPopover,
+ IonRefresher,
+ IonRefresherContent,
+ IonRippleEffect,
+ IonSpinner,
+ IonText,
+ IonTitle,
+ IonToolbar,
+} from '@ionic/react';
+import DisplayError from 'components/DisplayError';
+import NextPrayerCard from 'components/NextPrayerCard';
+import {
+ ListPrayerTimes,
+ getNextPrayer,
+ usePrayerTimes,
+} from 'features/list-prayer-times';
+import { useMemo } from 'react';
+
+const PrayerTimes: React.FC = () => {
+ const { data, isLoading, refetch, error } = usePrayerTimes();
+
+ const prayerTimesData = useMemo(() => data?.data, [data]);
+
+ const nextPrayer = useMemo(
+ () =>
+ prayerTimesData?.timings ? getNextPrayer(prayerTimesData.timings) : null,
+ [data?.data.timings]
+ );
+
+ return (
+
+
+
+ Prayer Times
+
+
+
+ {
+ await refetch();
+ e.detail.complete();
+ }}
+ >
+
+
+
+ {data?.data && (
+
+ )}
+
+ {isLoading && (
+
+
+
+ )}
+
+ {Boolean(error) && (
+
+ )}
+
+ {prayerTimesData && (
+
+ )}
+
+
+ );
+};
+
+export default PrayerTimes;
diff --git a/src/screens/Quran.tsx b/src/screens/Quran.tsx
index 1e52c4f..7833f49 100644
--- a/src/screens/Quran.tsx
+++ b/src/screens/Quran.tsx
@@ -1,53 +1,114 @@
-import { ChaptersList, ChapterSortBy } from 'features/list-chapters';
+import {
+ ChaptersList,
+ ChapterSortBy,
+ useAllJuzs,
+ useChapersList,
+} from 'features/list-chapters';
import {
IonContent,
IonHeader,
IonLabel,
IonPage,
+ IonRefresher,
+ IonRefresherContent,
IonSearchbar,
IonSegment,
IonSegmentButton,
+ IonSpinner,
IonTitle,
IonToolbar,
+ useIonToast,
} from '@ionic/react';
import { createRef, useState } from 'react';
+import DisplayError from 'components/DisplayError';
+import { alertCircle } from 'ionicons/icons';
const Quran: React.FC = () => {
const contentRef = createRef();
const [sortBy, setSortBy] = useState('surah');
const [search, setSearch] = useState('');
+ const [presentToast] = useIonToast();
+
+ const {
+ isLoading,
+ error,
+ data: chaptersData,
+ refetch: refetchChapters,
+ } = useChapersList();
+ const { data: allJuzsData, refetch: refetchAllJuzs } = useAllJuzs();
return (
-
+
Quran
+
+
+ setSearch(e.detail.value!)}
+ className="sticky top-0 z-30 !pb-0"
+ />
+
+
+
+ setSortBy(e.detail.value as ChapterSortBy)}
+ >
+
+ Surah
+
+
+ Juz
+
+
+ Revelation Order
+
+
+
- setSearch(e.detail.value!)}
- className="sticky top-0 z-30"
- color="light"
- />
-
- setSortBy(e.detail.value as ChapterSortBy)}
+ {
+ Promise.all([await refetchChapters(), await refetchAllJuzs()])
+ .catch((err) =>
+ presentToast({
+ message: err.message,
+ duration: 4500,
+ position: 'bottom',
+ icon: alertCircle,
+ })
+ )
+ .finally(e.detail.complete);
+ }}
>
-
- Surah
-
-
- Juz
-
-
- Revelation Order
-
-
-
-
+
+
+
+ {isLoading && (
+
+
+
+ )}
+
+ {Boolean(error) && (
+
+ )}
+
+ {chaptersData?.chapters && (
+
+ )}
);
diff --git a/src/screens/Settings.tsx b/src/screens/Settings.tsx
index a700748..8af6316 100644
--- a/src/screens/Settings.tsx
+++ b/src/screens/Settings.tsx
@@ -11,7 +11,7 @@ import { QuranSettings, GeneralSettings } from 'features/settings';
const Settings: React.FC = () => {
return (
-
+
Settings
diff --git a/src/screens/ViewChapter.tsx b/src/screens/ViewChapter.tsx
index c3a797d..37cf1dc 100644
--- a/src/screens/ViewChapter.tsx
+++ b/src/screens/ViewChapter.tsx
@@ -62,7 +62,7 @@ const ViewChapter: React.FC = () => {
return (
-
+
@@ -73,21 +73,23 @@ const ViewChapter: React.FC = () => {
: `Surah No. ${chapterNo}`}
+
+
+ setType(e.detail.value!)}
+ >
+
+ Translation
+
+
+ Reading
+
+
+
- setType(e.detail.value!)}
- >
-
- Translation
-
-
- Reading
-
-
-
{/* when error appears */}
{chapterDataError ? : null}
diff --git a/src/theme/styles.css b/src/theme/styles.css
index cc06338..3d55924 100644
--- a/src/theme/styles.css
+++ b/src/theme/styles.css
@@ -29,6 +29,11 @@ html {
width: 100%;
}
+ion-refresher {
+ z-index: 50;
+ position: relative;
+}
+
@font-face {
font-family: 'Me Quran';
src: url('/assets/fonts/Me Quran.ttf') format('truetype');
diff --git a/src/utils/geoLocation.ts b/src/utils/geoLocation.ts
new file mode 100644
index 0000000..bbf013d
--- /dev/null
+++ b/src/utils/geoLocation.ts
@@ -0,0 +1,23 @@
+import { Capacitor } from '@capacitor/core';
+import {
+ Geolocation as NativeGeolocation,
+ Position,
+} from '@capacitor/geolocation';
+
+export async function getGeoLocation(): Promise {
+ if (Capacitor.isNativePlatform()) {
+ // for android
+ const reqForLocation = await NativeGeolocation.requestPermissions();
+
+ if (reqForLocation.coarseLocation === 'granted') {
+ return await NativeGeolocation.getCurrentPosition();
+ } else {
+ throw Error('Location permission not granted');
+ }
+ } else {
+ // for web
+ return await new Promise((resolve, reject) =>
+ navigator.geolocation.getCurrentPosition(resolve, reject)
+ );
+ }
+}
diff --git a/src/utils/time.ts b/src/utils/time.ts
index 44402ad..070844b 100644
--- a/src/utils/time.ts
+++ b/src/utils/time.ts
@@ -3,3 +3,21 @@ export function delay(time: number) {
setTimeout(resolve, time);
});
}
+
+export const numToHHMMSS = function (num: number) {
+ let sec_num = parseInt(num.toString(), 10);
+ let hours: string | number = Math.floor(sec_num / 3600);
+ let minutes: string | number = Math.floor((sec_num - hours * 3600) / 60);
+ let seconds: string | number = sec_num - hours * 3600 - minutes * 60;
+
+ if (hours < 10) {
+ hours = '0' + hours;
+ }
+ if (minutes < 10) {
+ minutes = '0' + minutes;
+ }
+ if (seconds < 10) {
+ seconds = '0' + seconds;
+ }
+ return hours + ':' + minutes + ':' + seconds;
+};
diff --git a/yarn.lock b/yarn.lock
index 3491eea..2eeae45 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1027,6 +1027,11 @@
dependencies:
tslib "^2.1.0"
+"@capacitor/geolocation@^4.1.0":
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/@capacitor/geolocation/-/geolocation-4.1.0.tgz#1941b5ca4288d7cb5c80940649d4416322b37ee2"
+ integrity sha512-hfI4MUcu1zcJPTvm0g6V3telTGwq9sCU8EnY4hFJpLedbIQeWPthCOSbFtNHAU5mVaAP1Zls3x6TsXL8TX08EA==
+
"@capacitor/haptics@4.1.0":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@capacitor/haptics/-/haptics-4.1.0.tgz#29b082c3b90c920477717a0754017f5d4e43ed15"
@@ -4032,6 +4037,11 @@ dateformat@^3.0.0:
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==
+dayjs@^1.11.7:
+ version "1.11.7"
+ resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"
+ integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==
+
debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
@@ -8022,7 +8032,7 @@ promzard@^1.0.0:
dependencies:
read "^2.0.0"
-prop-types@^15.6.2, prop-types@^15.8.1:
+prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -8124,6 +8134,13 @@ rc@^1.2.7, rc@^1.2.8:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
+react-countdown@^2.3.5:
+ version "2.3.5"
+ resolved "https://registry.yarnpkg.com/react-countdown/-/react-countdown-2.3.5.tgz#70c035b5cbc7e8fdb4ad91fe5f44afd7a7933a68"
+ integrity sha512-K26ENYEesMfPxhRRtm1r+Pf70SErrvW3g4CArLi/x6MPFjgfDFYePT4UghEj8p2nI0cqVV7/JjDgjyr//U60Og==
+ dependencies:
+ prop-types "^15.7.2"
+
react-dom@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"