diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle index ca1c63f..5f41603 100644 --- a/android/app/capacitor.build.gradle +++ b/android/app/capacitor.build.gradle @@ -10,6 +10,7 @@ android { apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" dependencies { implementation project(':capacitor-app') + implementation project(':capacitor-geolocation') implementation project(':capacitor-haptics') implementation project(':capacitor-keyboard') implementation project(':capacitor-splash-screen') diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c5968d1..818ddae 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ - + @@ -12,4 +12,7 @@ + + + diff --git a/android/app/src/main/AndroidManifest.xml.orig b/android/app/src/main/AndroidManifest.xml.orig index 64d98c5..0c24250 100644 --- a/android/app/src/main/AndroidManifest.xml.orig +++ b/android/app/src/main/AndroidManifest.xml.orig @@ -1,7 +1,7 @@ - + @@ -12,4 +12,7 @@ + + + diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle index fd6199e..ffbd2a4 100644 --- a/android/capacitor.settings.gradle +++ b/android/capacitor.settings.gradle @@ -5,6 +5,9 @@ project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/ include ':capacitor-app' project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android') +include ':capacitor-geolocation' +project(':capacitor-geolocation').projectDir = new File('../node_modules/@capacitor/geolocation/android') + include ':capacitor-haptics' project(':capacitor-haptics').projectDir = new File('../node_modules/@capacitor/haptics/android') diff --git a/git-conventional-commits.yaml b/git-conventional-commits.yaml index a681a45..1781e31 100644 --- a/git-conventional-commits.yaml +++ b/git-conventional-commits.yaml @@ -47,6 +47,7 @@ convention: 'readme', 'contributing', 'capacitor', + 'prayer-times', ] releaseTagGlobPattern: v[0-9]*.[0-9]*.[0-9]* changelog: diff --git a/ios/App/Podfile b/ios/App/Podfile index f12f18b..26816d9 100644 --- a/ios/App/Podfile +++ b/ios/App/Podfile @@ -12,6 +12,7 @@ def capacitor_pods pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app' + pod 'CapacitorGeolocation', :path => '../../node_modules/@capacitor/geolocation' pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics' pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard' pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen' diff --git a/package.json b/package.json index 91e1bc5..9955062 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@capacitor/android": "4.7.3", "@capacitor/app": "4.1.1", "@capacitor/core": "4.7.3", + "@capacitor/geolocation": "^4.1.0", "@capacitor/haptics": "4.1.0", "@capacitor/ios": "4.7.3", "@capacitor/keyboard": "4.1.1", @@ -29,6 +30,7 @@ "@types/react-window": "^1.8.5", "@vitejs/plugin-react": "^3.1.0", "axios": "^1.3.4", + "dayjs": "^1.11.7", "framer-motion": "^10.10.0", "history": "^5.3.0", "ionicons": "^7.1.0", @@ -36,6 +38,7 @@ "marked": "^4.3.0", "minimist": "^1.2.8", "react": "^18.2.0", + "react-countdown": "^2.3.5", "react-dom": "^18.2.0", "react-router": "^5.3.4", "react-router-dom": "^5.3.4", diff --git a/src/App.tsx b/src/App.tsx index 1e2a00c..8122fca 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,8 +13,11 @@ import { import { IonReactRouter } from '@ionic/react-router'; import { book, - person, - cog, + bookOutline, + settings, + settingsOutline, + time, + timeOutline, // chatbubble, happy } from 'ionicons/icons'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; @@ -47,6 +50,7 @@ import './theme/styles.css'; import QuranPage from 'screens/Quran'; import ViewChapterPage from 'screens/ViewChapter'; import SettingsPage from 'screens/Settings'; +import PrayerTimes from 'screens/PrayerTimes'; // const QuranPage = React.lazy(() => import('screens/Quran')); // const ViewChapterPage = React.lazy(() => import('screens/ViewChapter')); // const SettingsPage = React.lazy(() => import('screens/Settings')); @@ -63,12 +67,17 @@ const routes: { { name: 'Quran', path: '/quran', - icon: book, + icon: bookOutline, }, + // { + // name: 'Sunnah', + // path: '/sunnah', + // icon: person, + // }, { - name: 'Sunnah', - path: '/sunnah', - icon: person, + name: 'Prayer Times', + path: '/prayer-times', + icon: timeOutline, }, // { // name: 'Dhikr', @@ -83,7 +92,7 @@ const routes: { { name: 'Settings', path: '/settings', - icon: cog, + icon: settingsOutline, }, ]; @@ -109,6 +118,11 @@ const App: React.FC = () => { + } + /> } /> - + Cancel diff --git a/src/components/DisplayError.tsx b/src/components/DisplayError.tsx index 990a1b5..fff7978 100644 --- a/src/components/DisplayError.tsx +++ b/src/components/DisplayError.tsx @@ -1,26 +1,58 @@ -import { IonIcon, IonText, IonToast } from '@ionic/react'; +import { IonIcon, IonText, IonToast, useIonToast } from '@ionic/react'; import { alertCircle, alertCircleOutline } from 'ionicons/icons'; -import React from 'react'; +import { ReactNode, useMemo } from 'react'; type Props = { error: Error | any; + className?: string; + toastOnly?: boolean; }; -function DisplayError({ error }: Props) { +function DisplayError({ error, className, toastOnly = false }: Props) { + const [presentToast] = useIonToast(); + + const message = useMemo(() => { + if (!(error as any)?.message) return error ?? 'Unknown Error'; + + let errMsg = 'Unknown Error'; + + switch (error.message) { + case 'User denied Geolocation': + errMsg = 'Please enable location access for the app.'; + break; + + case 'Network Error': + errMsg = 'The app required network to load data on first time.'; + break; + + default: + errMsg = error.message; + break; + } + + presentToast({ + message: errMsg, + duration: 4500, + position: 'bottom', + icon: alertCircle, + }); + return errMsg; + }, [error]); + return ( <> - -
- - -

{error ? (error as any).message : error}

-
-
+ {!toastOnly && ( +
+ + +

+ {message} +

+
+
+ )} ); } diff --git a/src/components/NextPrayerCard.tsx b/src/components/NextPrayerCard.tsx new file mode 100644 index 0000000..a47af2f --- /dev/null +++ b/src/components/NextPrayerCard.tsx @@ -0,0 +1,114 @@ +import { + IonCard, + IonCardContent, + IonCardHeader, + IonCardTitle, + IonContent, + IonIcon, + IonPopover, + IonSpinner, + IonText, +} from '@ionic/react'; +import { chevronForwardOutline, locationOutline } from 'ionicons/icons'; +import Countdown from 'react-countdown'; + +type Props = { + isLoading: boolean; + nextPrayer: { name: string; time: Date; readableTime: string } | null; + timezone: string; + methodName: string; + hijriDate: string; + hijriWeekDay: string; + gregorianDate: string; + gregorianWeekDay: string; +}; + +export default function NextPrayerCard({ + isLoading, + nextPrayer, + timezone, + methodName, + hijriDate, + hijriWeekDay, + gregorianDate, + gregorianWeekDay, +}: Props) { + return ( + + {isLoading ? ( + + + + ) : ( + <> + + {nextPrayer?.name} + + + + + is the next prayer in + + + ({nextPrayer?.readableTime}) + + + +
+ + {' '} + {timezone} + + + + + Prayer times calculation method. + + + + {' '} + {methodName} + +
+ +
+ + + + + + + + + + + + + + +
HijriGregorian
+ Yawm {hijriWeekDay},
+ {hijriDate} +
+ {gregorianWeekDay},
+ {gregorianDate.normalize()} +
+
+ + )} +
+ ); +} diff --git a/src/features/list-chapters/api/useAllJuzs.ts b/src/features/list-chapters/api/useAllJuzs.ts index 0b37fac..c9a5d30 100644 --- a/src/features/list-chapters/api/useAllJuzs.ts +++ b/src/features/list-chapters/api/useAllJuzs.ts @@ -16,7 +16,6 @@ function useAllJuzs() { staleTime: Infinity, cacheTime: Infinity, structuralSharing: false, - notifyOnChangeProps: ['data'], }); } diff --git a/src/features/list-chapters/api/useChapersList.ts b/src/features/list-chapters/api/useChapersList.ts index adc3ebd..31dab1e 100644 --- a/src/features/list-chapters/api/useChapersList.ts +++ b/src/features/list-chapters/api/useChapersList.ts @@ -16,7 +16,6 @@ function useChapersList() { staleTime: Infinity, cacheTime: Infinity, structuralSharing: false, - notifyOnChangeProps: ['data', 'error', 'isLoading'], }); } diff --git a/src/features/list-chapters/components/ChaptersList.tsx b/src/features/list-chapters/components/ChaptersList.tsx index dc59042..19efe4f 100644 --- a/src/features/list-chapters/components/ChaptersList.tsx +++ b/src/features/list-chapters/components/ChaptersList.tsx @@ -1,30 +1,30 @@ import { IonItemGroup, IonSpinner, useIonViewDidEnter } from '@ionic/react'; import React, { useMemo, useRef, useState } from 'react'; import { ChapterSortBy } from '../types/Chapter'; -import { useChapersList } from '../api/useChapersList'; -import { useAllJuzs } from '../api/useAllJuzs'; import { ChapterItem } from './ChapterItem'; import { SortedByJuz } from './SortedByJuz'; import { FixedSizeList as List } from 'react-window'; -import DisplayError from 'components/DisplayError'; +import { Chapter } from '../types/Chapter'; +import { Juz } from '../types/Juz'; interface ChapetersListProps { sortBy: ChapterSortBy; search: string; + chapters: Chapter[]; + juzs: Juz[]; } const ChaptersList: React.FC = ({ sortBy = 'surah', search, + chapters, + juzs, }) => { - const contentRef = useRef(null); + const contentRef = useRef(null); const [chapterListHeight, setChapterListHeight] = useState( window.innerHeight ); - const { isLoading, error, data: chapterData } = useChapersList(); - const { data: allJuzsData } = useAllJuzs(); - useIonViewDidEnter(() => { setChapterListHeight( contentRef.current?.clientHeight ?? window.innerHeight @@ -33,53 +33,43 @@ const ChaptersList: React.FC = ({ const searchedChapters = useMemo(() => { if (search.trim()) { - return chapterData?.chapters?.filter((chapter) => { + return chapters?.filter((chapter) => { const chapterValues = `${chapter.translated_name.name}, ${chapter.id}, ${chapter.name_arabic}, ${chapter.name_simple}`.toLocaleLowerCase(); return chapterValues.includes(search.toLowerCase()); }); } - }, [chapterData?.chapters, search]); + }, [chapters, search]); const sortedChapters = useMemo(() => { const sorted = sortBy === 'revelation-order' - ? chapterData?.chapters.sort( - (a, b) => a.revelation_order - b.revelation_order - ) - : chapterData?.chapters.sort((a, b) => a.id - b.id); + ? chapters.sort((a, b) => a.revelation_order - b.revelation_order) + : chapters.sort((a, b) => a.id - b.id); return sorted; - }, [chapterData, sortBy]); + }, [chapters, sortBy]); // chapters after sorted and searched - const chapters = useMemo( + const updatedChaptersArr = useMemo( () => searchedChapters ?? sortedChapters ?? [], [searchedChapters, sortedChapters] ); return ( -
- {/* when error appears */} - {error ? : null} - - {/* when api is loading */} - {isLoading && ( -
- -
- )} - - +
+ {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"