diff --git a/app/_layout.tsx b/app/_layout.tsx index 4a8d119..3f57700 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,12 +1,11 @@ import FontAwesome from '@expo/vector-icons/FontAwesome'; -import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'; +import { DarkTheme, ThemeProvider } from '@react-navigation/native'; import { useFonts } from 'expo-font'; import { Stack } from 'expo-router'; import * as SplashScreen from 'expo-splash-screen'; import React, { useEffect } from 'react'; import 'react-native-reanimated'; -import { useColorScheme } from '@/components/useColorScheme'; import { initializeLanguage } from '@/i18n'; export { @@ -55,10 +54,8 @@ export default function RootLayout() { } function RootLayoutNav() { - const colorScheme = useColorScheme(); - return ( - + diff --git a/components/PrayerTimeCard.tsx b/components/PrayerTimeCard.tsx index 56f1507..c046393 100644 --- a/components/PrayerTimeCard.tsx +++ b/components/PrayerTimeCard.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, ImageBackground, Pressable } from 'react-native'; import Colors from '@/constants/Colors'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; -import prayerTimeCalculator, { cities } from '@/utils/prayerTimeCalculator'; +import { getPrayerTimes, cities } from '@/utils/prayerTimeCalculator'; import i18n from '@/i18n'; type Prayer = { @@ -24,14 +24,12 @@ export default function PrayerTimeCard() { const [prayerTimes, setPrayerTimes] = useState([]); const [nextPrayer, setNextPrayer] = useState<{ name: string; time: string } | null>(null); const [remainingTime, setRemainingTime] = useState(''); - const [selectedCity, setSelectedCity] = useState<'Makkah' | 'Medina' | 'Jeddah'>('Makkah'); + const [selectedCity, setSelectedCity] = useState('Makkah'); useEffect(() => { - const city = cities[selectedCity]; - const times = prayerTimeCalculator.getTimes(new Date(), city.coords, city.timezone, 0, '24h'); - + const times = getPrayerTimes(selectedCity); const prayers: Prayer[] = Object.keys(prayerIconMapping).map(key => ({ - name: key.charAt(0).toUpperCase() + key.slice(1), + name: i18n.t(key), time: times[key] || '-----', icon: prayerIconMapping[key], })); @@ -56,7 +54,6 @@ export default function PrayerTimeCard() { } setNextPrayer(nextPrayerInfo); - }, [selectedCity]); useEffect(() => { @@ -90,9 +87,9 @@ export default function PrayerTimeCard() { - {Object.keys(cities).map(city => ( - setSelectedCity(city as any)} style={[styles.cityButton, selectedCity === city && styles.activeCity]}> - {city} + {(Object.keys(cities) as Array).map(city => ( + setSelectedCity(city)} style={[styles.cityButton, selectedCity === city && styles.activeCity]}> + {i18n.t(city)} ))} diff --git a/locales/tk.json b/locales/tk.json index 9f875ec..9b17dc4 100644 --- a/locales/tk.json +++ b/locales/tk.json @@ -15,6 +15,15 @@ "dua": "Doga", "adhkar": "Zikir", "hisnAlMuslim": "Musulmanyň goragy", + "Makkah": "Mekke", + "Medina": "Medine", + "Jeddah": "Jidda", + "fajr": "Ertir", + "sunrise": "Gün", + "dhuhr": "Öýle", + "asr": "Ikindi", + "maghrib": "Agşam", + "isha": "Ýatsy", "morningEveningThikr": "Ertir we agşam aýdylýan zikir", "beforeSleepingThikr": "Uklamazdan öňki zikir", "afterSalamThikr": "Salamdan soňky zikir", diff --git a/package-lock.json b/package-lock.json index 61c2cb7..4fe112a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@expo/vector-icons": "^14.1.0", "@react-native-async-storage/async-storage": "2.1.2", "@react-navigation/native": "^7.1.6", + "adhan": "^4.4.3", "expo": "~53.0.20", "expo-font": "~13.3.2", "expo-linking": "~7.1.7", @@ -21,7 +22,6 @@ "expo-system-ui": "~5.0.10", "expo-updates": "~0.28.17", "expo-web-browser": "~14.2.0", - "hijri-date": "^0.2.2", "i18n-js": "^4.5.1", "react": "19.0.0", "react-dom": "19.0.0", @@ -3328,6 +3328,11 @@ "node": ">=0.4.0" } }, + "node_modules/adhan": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/adhan/-/adhan-4.4.3.tgz", + "integrity": "sha512-568KkQd8OMLUj7o7+d2FDcm6vZHWQrE7vsm/Evssh8sfUDpPyaboj3PVsScZAr7L7sNRgPrtLMmDZZfM7VeAYw==" + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -5666,11 +5671,6 @@ "hermes-estree": "0.25.1" } }, - "node_modules/hijri-date": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/hijri-date/-/hijri-date-0.2.2.tgz", - "integrity": "sha512-LiuslQTPqePwh/KguBB2KH/hzISQd7nQtfVPwdCKgfFv0GDzwjIeTQX/LkBKLI8rPeuXasr3sQMuJL35g8HEAw==" - }, "node_modules/hosted-git-info": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", diff --git a/package.json b/package.json index b3c5407..ea262a9 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@expo/vector-icons": "^14.1.0", "@react-native-async-storage/async-storage": "2.1.2", "@react-navigation/native": "^7.1.6", + "adhan": "^4.4.3", "expo": "~53.0.20", "expo-font": "~13.3.2", "expo-linking": "~7.1.7", @@ -26,7 +27,6 @@ "expo-system-ui": "~5.0.10", "expo-updates": "~0.28.17", "expo-web-browser": "~14.2.0", - "hijri-date": "^0.2.2", "i18n-js": "^4.5.1", "react": "19.0.0", "react-dom": "19.0.0", @@ -36,10 +36,10 @@ "react-native-safe-area-context": "5.4.0", "react-native-screens": "~4.11.1", "react-native-vector-icons": "^10.3.0", - "react-native-web": "~0.20.0" + "react-native-web": "~0.19.6" }, "devDependencies": { - "@babel/core": "^7.25.2", + "@babel/core": "^7.20.0", "@types/react": "~19.0.10", "@types/react-native-vector-icons": "^6.4.18", "jest": "^29.2.1", diff --git a/utils/prayerTimeCalculator.ts b/utils/prayerTimeCalculator.ts index f0cd71f..9b7a9af 100644 --- a/utils/prayerTimeCalculator.ts +++ b/utils/prayerTimeCalculator.ts @@ -1,364 +1,53 @@ -// PrayTimes-JS: Prayer Times Calculator (ver 2.3) -// https://github.com/abodeo/prayertimes +import { CalculationMethod, Coordinates, PrayerTimes, Qibla } from 'adhan'; -//--------------------- Copyright Block ---------------------- -/* - - PrayTimes-JS: Prayer Times Calculator (ver 2.3) - Copyright (C) 2007-2011 PrayTimes.org - - JS Code By: Hussain Ali Khan - Original JS Code By: Hamid Zarrabi-Zadeh - - Original C++ Code By: Hamid Zarrabi-Zadeh - - License: GNU LGPL v3.0 - - TERMS OF USE: - Permission is granted to use this code, with or without - modification, in any website or application provided that - this copyright block is reproduced in its entirety. This - notice must be placed at the beginning of the code block - and may not be altered in any way. This notice must remain - as part of the code block at all times. -*/ - -import HijriDate from 'hijri-date'; - -type PrayerTimes = { - fajr: string; - sunrise: string; - dhuhr: string; - asr: string; - sunset: string; - maghrib: string; - isha: string; - imsak?: string; - midnight?: string; - [key: string]: string | undefined; +type Prayer = { + [key: string]: string; }; -type Coordinates = [number, number]; +export const getPrayerTimes = ( + city: keyof typeof cities, + date: Date = new Date() +): Prayer => { + const { coords, timezone } = cities[city]; + const coordinates = new Coordinates(coords[0], coords[1]); + const params = CalculationMethod.UmmAlQura(); + const prayerTimes = new PrayerTimes(coordinates, date, params); -type DateComponents = { - year: number; - month: number; - day: number; -}; + const formatTime = (time: Date) => { + return time.toLocaleTimeString('en-US', { + hour: '2-digit', + minute: '2-digit', + hour12: false, + timeZone: timezone, + }); + }; -type CalculationMethod = { - name: string; - params: { - fajr: number | string; - isha: number | string; - maghrib?: number | string; - midnight?: string; + return { + fajr: formatTime(prayerTimes.fajr), + sunrise: formatTime(prayerTimes.sunrise), + dhuhr: formatTime(prayerTimes.dhuhr), + asr: formatTime(prayerTimes.asr), + maghrib: formatTime(prayerTimes.maghrib), + isha: formatTime(prayerTimes.isha), }; }; -type TimeNames = - | 'imsak' - | 'fajr' - | 'sunrise' - | 'dhuhr' - | 'asr' - | 'sunset' - | 'maghrib' - | 'isha' - | 'midnight'; - -class PrayTimes { - private lat: number = 0; - private lng: number = 0; - private elv: number = 0; - private timeZone: number = 0; - private jDate: number = 0; - - private calcMethod: string = 'MWL'; - private setting: { [key: string]: any } = {}; - - private methods: { [key: string]: CalculationMethod } = { - MWL: { - name: 'Muslim World League', - params: { fajr: 18, isha: 17 }, - }, - ISNA: { - name: 'Islamic Society of North America (ISNA)', - params: { fajr: 15, isha: 15 }, - }, - Egypt: { - name: 'Egyptian General Authority of Survey', - params: { fajr: 19.5, isha: 17.5 }, - }, - Makkah: { - name: 'Umm Al-Qura University, Makkah', - params: { fajr: 18.5, isha: '90 min' }, - }, - Karachi: { - name: 'University of Islamic Sciences, Karachi', - params: { fajr: 18, isha: 18 }, - }, - Tehran: { - name: 'Institute of Geophysics, University of Tehran', - params: { fajr: 17.7, isha: 14, maghrib: 4.5, midnight: 'Jafari' }, - }, - Jafari: { - name: 'Shia Ithna-Ashari, Leva Institute, Qum', - params: { fajr: 16, isha: 14, maghrib: 4, midnight: 'Jafari' }, - }, - }; - - private defaultParams = { - maghrib: '0 min', - midnight: 'Standard', - }; - - private timeSuffixes: string[] = ['am', 'pm']; - private invalidTime: string = '-----'; - - private numIterations: number = 1; - private offset: { [key: string]: number } = {}; - - private timeNames: { [key in TimeNames]: string } = { - imsak: 'Imsak', - fajr: 'Fajr', - sunrise: 'Sunrise', - dhuhr: 'Dhuhr', - asr: 'Asr', - sunset: 'Sunset', - maghrib: 'Maghrib', - isha: 'Isha', - midnight: 'Midnight', - }; - - constructor(method: string = 'Makkah') { - this.setMethod(method); - } - - setMethod(method: string): void { - if (this.methods[method]) { - this.calcMethod = method; - const params = this.methods[method].params; - this.setting = { ...this.defaultParams, ...params }; - } - } - - getTimes( - date: Date, - coords: Coordinates, - timezone: number, - elv: number = 0, - format: string = '24h', - ): PrayerTimes { - this.lat = coords[0]; - this.lng = coords[1]; - this.elv = elv; - this.timeZone = timezone; - this.jDate = this.julian(date.getFullYear(), date.getMonth() + 1, date.getDate()) - this.lng / (15 * 24); - - return this.computeTimes(format); - } - - private julian(year: number, month: number, day: number): number { - if (month <= 2) { - year -= 1; - month += 12; - } - const A = Math.floor(year / 100); - const B = 2 - A + Math.floor(A / 4); - return Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + B - 1524.5; - } - - private computeTimes(format: string): PrayerTimes { - const times: { [key: string]: number } = { - imsak: 5, - fajr: 5, - sunrise: 6, - dhuhr: 12, - asr: 13, - sunset: 18, - maghrib: 18, - isha: 18, - }; - - const dayPortion = this.sunPosition(this.jDate + 0.5); - - times.dhuhr = this.midDay(this.jDate); - times.sunrise = this.sunAngleTime(this.jDate, this.riseSetAngle(), 'ccw'); - times.sunset = this.sunAngleTime(this.jDate, this.riseSetAngle(), 'cw'); - times.asr = this.asrTime(this.jDate, 1); - times.maghrib = this.sunAngleTime(this.jDate, this.eval(this.setting.maghrib), 'cw'); - - if (this.calcMethod === 'Makkah') { - const gDate = new Date((this.jDate - 2440587.5) * 86400000); - const hijriDate = new HijriDate(gDate.getTime()); - const isRamadan = hijriDate.getMonth() === 9; // Ramadan is the 9th month in Hijri - // For Umm al-Qura, Isha is 90 minutes after Maghrib, 120 during Ramadan - times.isha = times.maghrib + (isRamadan ? 2 : 1.5); - } else { - times.isha = this.sunAngleTime(this.jDate, this.eval(this.setting.isha), 'cw'); - } - - times.fajr = this.sunAngleTime(this.jDate, this.eval(this.setting.fajr), 'ccw'); - - const result = this.adjustTimes(times); - return this.formatTimes(result, format); - } - - private sunPosition(jd: number): [number, number] { - const D = jd - 2451545.0; - const g = this.fixAngle(357.529 + 0.98560028 * D); - const q = this.fixAngle(280.459 + 0.98564736 * D); - const L = this.fixAngle(q + 1.915 * this.dSin(g) + 0.02 * this.dSin(2 * g)); - - const R = 1.00014 - 0.01671 * this.dCos(g) - 0.00014 * this.dCos(2 * g); - const e = 23.439 - 0.00000036 * D; - - const RA = this.dAtan2(this.dCos(e) * this.dSin(L), this.dCos(L)) / 15; - const dec = this.dAsin(this.dSin(e) * this.dSin(L)); - const EqT = q / 15 - this.fixHour(RA); - - return [dec, EqT]; - } - - private sunAngleTime(jd: number, angle: number, direction: string): number { - const [dec, EqT] = this.sunPosition(jd); - const t = - (1 / 15) * - this.dACos( - (-this.dSin(angle) - this.dSin(dec) * this.dSin(this.lat)) / - (this.dCos(dec) * this.dCos(this.lat)), - ); - return this.midDay(jd) + (direction === 'cw' ? t : -t) - EqT; - } - - private asrTime(jd: number, factor: number): number { - const [dec] = this.sunPosition(jd); - const angle = -this.dACot(factor + this.dTan(Math.abs(dec - this.lat))); - return this.sunAngleTime(jd, angle, 'cw'); - } - - private riseSetAngle(): number { - return 0.833 + 0.0347 * Math.sqrt(this.elv); - } - - private midDay(jd: number): number { - const [, EqT] = this.sunPosition(jd); - return 12 - EqT; - } - - private adjustTimes(times: { [key: string]: number }): { [key: string]: number } { - const adjustedTimes = { ...times }; - for (const i in adjustedTimes) { - adjustedTimes[i] += this.timeZone - this.lng / 15; - } - return adjustedTimes; - } - - private formatTimes(times: { [key: string]: number }, format: string): PrayerTimes { - const formattedTimes: PrayerTimes = {} as PrayerTimes; - for (const i in times) { - if (format === '24h') { - formattedTimes[i] = this.floatToTime24(times[i]); - } else { - formattedTimes[i] = this.floatToTime12(times[i]); - } - } - return formattedTimes; - } - - private floatToTime24(time: number): string { - if (isNaN(time)) { - return this.invalidTime; - } - time = this.fixHour(time + 0.5 / 60); // add 0.5 minutes to round - const hours = Math.floor(time); - const minutes = Math.floor((time - hours) * 60); - return this.twoDigitsFormat(hours) + ':' + this.twoDigitsFormat(minutes); - } - - private floatToTime12(time: number, noSuffix: boolean = false): string { - if (isNaN(time)) { - return this.invalidTime; - } - time = this.fixHour(time + 0.5 / 60); // add 0.5 minutes to round - let hours = Math.floor(time); - const minutes = Math.floor((time - hours) * 60); - const suffix = hours >= 12 ? 'pm' : 'am'; - hours = (hours + 12 - 1) % 12 + 1; - return hours + ':' + this.twoDigitsFormat(minutes) + (noSuffix ? '' : ' ' + suffix); - } - - private twoDigitsFormat(num: number): string { - return num < 10 ? '0' + num : num.toString(); - } - - private eval(str: string | number | undefined): number { - if (str === undefined) { - return 0; - } - if (typeof str === 'number') { - return str; - } - const [value, unit] = str.split(' '); - return unit === 'min' ? parseInt(value) / 60 : parseInt(value); - } - - private dSin(d: number): number { - return Math.sin(this.dToR(d)); - } - private dCos(d: number): number { - return Math.cos(this.dToR(d)); - } - private dTan(d: number): number { - return Math.tan(this.dToR(d)); - } - private dAsin(d: number): number { - return this.rToD(Math.asin(d)); - } - private dACos(d: number): number { - return this.rToD(Math.acos(d)); - } - private dAtan(d: number): number { - return this.rToD(Math.atan(d)); - } - private dAtan2(y: number, x: number): number { - return this.rToD(Math.atan2(y, x)); - } - private dACot(x: number): number { - return this.rToD(Math.atan(1 / x)); - } - - private dToR(d: number): number { - return (d * Math.PI) / 180.0; - } - private rToD(r: number): number { - return (r * 180.0) / Math.PI; - } - - private fixAngle(a: number): number { - a = a - 360.0 * Math.floor(a / 360.0); - return a < 0 ? a + 360.0 : a; - } - private fixHour(a: number): number { - a = a - 24.0 * Math.floor(a / 24.0); - return a < 0 ? a + 24.0 : a; - } -} +export const getQiblaDirection = (latitude: number, longitude: number): number => { + const coordinates = new Coordinates(latitude, longitude); + return Qibla(coordinates); +}; export const cities = { Makkah: { - coords: [21.4225, 39.8262] as Coordinates, - timezone: 3, + coords: [21.4225, 39.8262], + timezone: 'Asia/Riyadh', }, Medina: { - coords: [24.4667, 39.6] as Coordinates, - timezone: 3, + coords: [24.4667, 39.6], + timezone: 'Asia/Riyadh', }, Jeddah: { - coords: [21.5433, 39.1728] as Coordinates, - timezone: 3, + coords: [21.5433, 39.1728], + timezone: 'Asia/Riyadh', }, }; - -export default new PrayTimes('Makkah');