Enhance app functionality and localization

- Added new dependencies: expo-file-system and expo-sharing.
- Updated package versions for @babel/core and react-dom.
- Introduced a new index screen for displaying prayer times with city selection.
- Refactored ServicesGrid to accept a dynamic services array and improved layout.
- Updated localization for new phrases and service titles in Turkmen.
- Enhanced LostKeyModal with a close button and additional text.
- Improved PhrasebookModal to allow expandable phrases for better user interaction.
This commit is contained in:
2025-09-01 13:11:31 +05:00
parent 6797ab6d9e
commit f519052b7b
12 changed files with 452 additions and 106 deletions

8
Umra.code-workspace Normal file
View File

@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}

View File

@@ -26,7 +26,7 @@ export default function TabLayout() {
backgroundColor: Colors[colorScheme].secondary,
borderTopColor: Colors[colorScheme].secondary,
},
headerShown: false, // Hide header globally for tabs
headerShown: false, // hide header globally for tabs
}}>
<Tabs.Screen
name="home"
@@ -42,11 +42,18 @@ export default function TabLayout() {
tabBarIcon: ({ color }) => <TabBarIcon name="th-large" color={color} />,
}}
/>
<Tabs.Screen
name="index"
options={{
title: i18n.t('menuSalah'),
tabBarIcon: ({ color }) => <TabBarIcon name="moon-o" color={color} />,
}}
/>
<Tabs.Screen
name="programs"
options={{
title: i18n.t('programs'),
tabBarIcon: ({ color }) => <TabBarIcon name="briefcase" color={color} />,
title: i18n.t('Programs'),
tabBarIcon: ({ color }) => <TabBarIcon name="calendar" color={color} />,
}}
/>
</Tabs>

View File

@@ -31,14 +31,22 @@ export default function HomeScreen() {
const currentLanguage = languages.find(l => l.value === i18n.locale.substring(0, 2))?.label;
const services = [
{ name: 'quran', icon: 'book-open-variant' },
{ name: 'hadith', icon: 'book-open-page-variant' },
{ name: 'dua', icon: 'human-greeting' },
];
return (
<SafeAreaView style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>{i18n.t('home')}</Text>
<Pressable onPress={() => setModalVisible(true)} style={pickerSelectStyles.inputIOS}>
<Text style={{ color: 'white' }}>{currentLanguage}</Text>
</Pressable>
</View>
<Modal
animationType="slide"
transparent={true}
@@ -69,7 +77,7 @@ export default function HomeScreen() {
/>
<PrayerTimeCard />
<ServicesGrid />
<ServicesGrid services={services} />
</View>
</ScrollView>
</SafeAreaView>
@@ -85,6 +93,7 @@ const styles = StyleSheet.create({
paddingHorizontal: 15,
},
header: {
display: 'none', // hide for now, will show later
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',

View File

@@ -1,5 +1,179 @@
import { Redirect } from 'expo-router';
import { Pressable, SafeAreaView, ScrollView, StyleSheet, Text, View } from 'react-native';
import { useCallback, useEffect, useState } from 'react';
import { getPrayerTimes, cities } from '../../utils/prayerTimeCalculator';
import i18n from '../../i18n';
import { useColorScheme } from 'react-native';
import Colors from '../../constants/Colors';
type Prayer = {
name: string;
time: string;
};
type City = keyof typeof cities;
export default function TabIndex() {
return <Redirect href="/(tabs)/home" />;
const colorScheme = useColorScheme();
const theme = Colors[colorScheme ?? 'light'];
const [selectedCity, setSelectedCity] = useState<City>('Makkah');
const [prayerTimes, setPrayerTimes] = useState<Prayer[]>([]);
const [nextPrayerName, setNextPrayerName] = useState<string | null>(null);
const prayerNameMapping: { [key: string]: string } = {
fajr: i18n.t('fajr'),
dhuhr: i18n.t('dhuhr'),
asr: i18n.t('asr'),
maghrib: i18n.t('maghrib'),
isha: i18n.t('isha'),
};
const updatePrayerTimes = useCallback(() => {
const times = getPrayerTimes(selectedCity);
const prayers: Prayer[] = Object.keys(prayerNameMapping).map((key) => ({
name: prayerNameMapping[key],
time: times[key] || '-----',
}));
setPrayerTimes(prayers);
const now = new Date();
let nextPrayer: Prayer | null = null;
for (const prayer of prayers) {
if (prayer.time === '-----') continue;
const [hours, minutes] = prayer.time.split(':').map(Number);
const prayerDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes);
if (prayerDate > now) {
nextPrayer = prayer;
break;
}
}
if (!nextPrayer && prayers.length > 0) {
const firstPrayer = prayers.find((p) => p.time !== '-----');
if (firstPrayer) {
nextPrayer = firstPrayer;
}
}
setNextPrayerName(nextPrayer ? nextPrayer.name : null);
}, [selectedCity]);
useEffect(() => {
updatePrayerTimes();
const interval = setInterval(updatePrayerTimes, 60000); // Update every minute
return () => clearInterval(interval);
}, [updatePrayerTimes]);
const renderPrayerTime = (prayer: Prayer) => {
const isNextPrayer = prayer.name === nextPrayerName;
return (
<View
key={prayer.name}
style={[styles.prayerRow, { backgroundColor: theme.secondary }, isNextPrayer && [styles.nextPrayerRow, { backgroundColor: theme.tint }]]}>
<View>
<Text style={[styles.prayerName, { color: theme.text }, isNextPrayer && styles.nextPrayerText]}>
{prayer.name}
</Text>
</View>
<Text style={[styles.prayerTime, { color: theme.text }, isNextPrayer && styles.nextPrayerTimeText]}>
{new Date(`1970-01-01T${prayer.time}:00`).toLocaleTimeString('en-US', {
hour: 'numeric',
minute: 'numeric',
hour12: false,
})}
</Text>
</View>
);
};
return (
<SafeAreaView style={[styles.container, { backgroundColor: theme.background }]}>
<View style={[styles.citySelector, { backgroundColor: theme.background }]}>
{(Object.keys(cities) as City[]).map((city) => (
<Pressable
key={city}
onPress={() => setSelectedCity(city)}
style={[styles.cityButton, { backgroundColor: theme.secondary }, selectedCity === city && [styles.activeCityButton, { backgroundColor: theme.tint }]]}>
<Text
style={[
styles.cityButtonText,
{ color: theme.text },
selectedCity === city && styles.activeCityButtonText,
]}>
{i18n.t(city)}
</Text>
</Pressable>
))}
</View>
<ScrollView contentContainerStyle={styles.listContainer}>
{prayerTimes.map(renderPrayerTime)}
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
citySelector: {
flexDirection: 'row',
justifyContent: 'center',
paddingVertical: 20,
paddingHorizontal: 10,
},
cityButton: {
paddingVertical: 10,
paddingHorizontal: 25,
borderRadius: 20,
marginHorizontal: 5,
},
activeCityButton: {},
cityButtonText: {
fontSize: 18,
fontWeight: '500',
},
activeCityButtonText: {
color: '#fff',
},
listContainer: {
paddingHorizontal: 20,
},
prayerRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 25,
paddingHorizontal: 20,
borderRadius: 15,
marginBottom: 10,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 1,
},
shadowOpacity: 0.1,
shadowRadius: 3,
elevation: 2,
},
nextPrayerRow: {},
prayerName: {
fontSize: 28,
fontWeight: '600',
},
inCityText: {
fontSize: 18,
},
prayerTime: {
fontSize: 28,
fontWeight: 'bold',
},
nextPrayerText: {
color: '#fff',
},
nextPrayerTimeText: {
color: '#fff',
fontSize: 32,
},
});

View File

@@ -7,14 +7,12 @@ import React, { useState } from 'react';
import CurrencyConverterModal from '@/components/CurrencyConverterModal';
import HotelBusinessCardModal from '@/components/HotelBusinessCardModal';
import LostKeyModal from '@/components/LostKeyModal';
import TranslatorModal from '@/components/TranslatorModal';
import PhrasebookModal from '@/components/PhrasebookModal';
export default function ServicesScreen() {
const [currencyModalVisible, setCurrencyModalVisible] = useState(false);
const [hotelModalVisible, setHotelModalVisible] = useState(false);
const [lostKeyModalVisible, setLostKeyModalVisible] = useState(false);
const [translatorModalVisible, setTranslatorModalVisible] = useState(false);
const [phrasebookModalVisible, setPhrasebookModalVisible] = useState(false);
const services = [
@@ -36,12 +34,6 @@ export default function ServicesScreen() {
icon: <FontAwesome5 name="key" size={24} color="#D4AF37" />,
onPress: () => setLostKeyModalVisible(true),
},
{
title: i18n.t('Translator'),
name: 'translator',
icon: <FontAwesome5 name="language" size={24} color="#D4AF37" />,
onPress: () => setTranslatorModalVisible(true),
},
{
title: i18n.t('Phrasebook'),
name: 'phrasebook',
@@ -73,10 +65,6 @@ export default function ServicesScreen() {
visible={lostKeyModalVisible}
onClose={() => setLostKeyModalVisible(false)}
/>
<TranslatorModal
visible={translatorModalVisible}
onClose={() => setTranslatorModalVisible(false)}
/>
<PhrasebookModal
visible={phrasebookModalVisible}
onClose={() => setPhrasebookModalVisible(false)}

View File

@@ -1,3 +1,4 @@
import { FontAwesome } from '@expo/vector-icons';
import React from 'react';
import { Modal, View, Text, StyleSheet, TouchableOpacity, Dimensions } from 'react-native';
@@ -18,7 +19,16 @@ const LostKeyModal: React.FC<LostKeyModalProps> = ({ visible, onClose }) => {
>
<TouchableOpacity style={styles.overlay} activeOpacity={1} onPress={onClose}>
<View style={styles.modalContainer}>
<Text style={styles.text}>Mastercard</Text>
<TouchableOpacity style={styles.closeButton} onPress={onClose}>
<FontAwesome name="close" size={24} color="black" />
</TouchableOpacity>
<Text style={styles.smallText} >Aşak resepşyna şul aşakdaky haty görkeziň</Text>
<Text
numberOfLines={1}
adjustsFontSizeToFit
style={styles.text}
>Mastercard</Text>
<Text style={[styles.text, styles.arabicText]}>ماستركارد</Text>
</View>
</TouchableOpacity>
@@ -34,7 +44,7 @@ const styles = StyleSheet.create({
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
modalContainer: {
width: width * 0.6,
width: width * 0.9,
padding: 20,
backgroundColor: 'white',
borderRadius: 10,
@@ -48,10 +58,22 @@ const styles = StyleSheet.create({
shadowRadius: 3.84,
elevation: 5,
},
closeButton: {
position: 'absolute',
top: 15,
right: 15,
zIndex: 1,
},
text: {
fontSize: 40,
marginBottom: 10,
},
smallText: {
marginTop: 15,
fontSize: 20,
color: '#666',
textAlign: 'center',
},
arabicText: {
fontFamily: 'System',
writingDirection: 'rtl',

View File

@@ -10,6 +10,8 @@ type PhrasebookModalProps = {
};
const PHRASES = [
{ tk: 'Bu näçe?', ar: 'بكم هذا؟ (Bikam hadha?)' },
{ tk: 'Arzanladyň', ar: 'تَخْفيض Takfidun' },
{ tk: 'Salam', ar: 'مرحبا (Marhaban)' },
{ tk: 'Hawa', ar: 'نعم (Na\'am)' },
{ tk: 'Ýok', ar: 'لا (La)' },
@@ -18,19 +20,26 @@ const PHRASES = [
{ tk: 'Haýyş edýärin', ar: 'من فضلك (Min fadlik)' },
{ tk: 'Bagyşlaň', ar: 'آسف (Asif)' },
{ tk: 'Men size nähili kömek edip bilerin?', ar: 'كيف يمكنني مساعدتك؟ (Kayfa yumkinuni musa\'adatuk?)' },
{ tk: 'Bu näçe?', ar: 'بكم هذا؟ (Bikam hadha?)' },
{ tk: 'Hajathana nirede?', ar: 'أين الحمام؟ (Ayna al-hammam?)' },
{ tk: 'Men ýolumy ýitirdim', ar: 'لقد ضللت طريقي (Laqad dalalt tariqi)' },
{ tk: 'Lukman çagyryň', ar: 'اتصل بطبيب (Ittasil bi-tabib)' },
];
const PhrasebookModal = ({ visible, onClose }: PhrasebookModalProps) => {
const renderItem = ({ item }: { item: { tk: string; ar: string } }) => (
<View style={styles.phraseItem}>
<Text style={styles.turkmenText}>{item.tk}</Text>
<Text style={styles.arabicText}>{item.ar}</Text>
</View>
);
const [expandedIndex, setExpandedIndex] = React.useState<number | null>(null);
const renderItem = ({ item, index }: { item: { tk: string; ar: string }; index: number }) => {
const isExpanded = index === expandedIndex;
return (
<TouchableOpacity onPress={() => setExpandedIndex(isExpanded ? null : index)}>
<View style={styles.phraseItem}>
<Text style={styles.turkmenText}>{item.tk}</Text>
{isExpanded && <Text style={styles.arabicText}>{item.ar}</Text>}
</View>
</TouchableOpacity>
);
};
return (
<Modal
@@ -51,6 +60,7 @@ const PhrasebookModal = ({ visible, onClose }: PhrasebookModalProps) => {
renderItem={renderItem}
keyExtractor={(item, index) => index.toString()}
ItemSeparatorComponent={() => <View style={styles.separator} />}
extraData={expandedIndex}
/>
</SafeAreaView>
</View>

View File

@@ -1,34 +1,42 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import Colors from '@/constants/Colors';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { router } from 'expo-router';
import i18n from '@/i18n';
const services = [
{ name: 'quran', icon: 'book-open-variant' },
{ name: 'hadith', icon: 'book-open-page-variant' },
{ name: 'dua', icon: 'human-greeting' },
];
export default function ServicesGrid() {
const colorScheme = 'dark';
type ServicesGridProps = {
services: {
name: string;
icon: any;
}[];
};
const ServicesGrid = ({ services }: ServicesGridProps) => {
const handlePress = (name: string) => {
if (name === 'quran') {
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>{i18n.t('servicesToEnrich')}</Text>
<View style={styles.grid}>
{services.map((service, index) => (
<View key={index} style={styles.serviceItem}>
<View style={[styles.iconContainer, { backgroundColor: Colors[colorScheme].secondary }]}>
<MaterialCommunityIcons name={service.icon} size={30} color={Colors[colorScheme].tint} />
{services.map((service) => (
<TouchableOpacity
key={service.name}
style={styles.serviceItem}
onPress={() => handlePress(service.name)}>
<View style={[styles.iconContainer, { backgroundColor: Colors['dark'].secondary }]}>
<MaterialCommunityIcons name={service.icon} size={30} color={Colors['dark'].tint} />
</View>
<Text style={styles.serviceName}>{i18n.t(service.name)}</Text>
</View>
</TouchableOpacity>
))}
</View>
</View>
);
}
};
const styles = StyleSheet.create({
container: {
@@ -37,8 +45,8 @@ const styles = StyleSheet.create({
title: {
fontSize: 18,
fontWeight: 'bold',
color: Colors.dark.text,
marginBottom: 15,
color: 'white',
marginBottom: 10,
},
grid: {
flexDirection: 'row',
@@ -50,13 +58,15 @@ const styles = StyleSheet.create({
iconContainer: {
width: 60,
height: 60,
borderRadius: 15,
borderRadius: 30,
justifyContent: 'center',
alignItems: 'center',
marginBottom: 10,
marginBottom: 5,
},
serviceName: {
color: Colors.dark.text,
fontSize: 14,
color: 'white',
textAlign: 'center',
},
});
export default ServicesGrid;

View File

@@ -1,5 +1,5 @@
{
"home": "Baş sahypa",
"home": "Öý",
"services": "Hyzmatlar",
"supplications": "Dogalar",
"yourJourneyToHajj": "Haj syýahatyňyz",
@@ -10,6 +10,7 @@
"newExperience": "Täze tejribe",
"prayerTimes": "Namaz wagtlary",
"programs": "Programmalar",
"Programs": "Respisaniýa",
"leftOnPrayer": "{{prayerName}} namazyna çenli galdy",
"servicesToEnrich": "Ruhy tejribäňizi baýlaşdyrmak üçin hyzmatlar",
"quran": "Kuran",
@@ -45,5 +46,7 @@
"Translator": "Perewod",
"Phrasebook": "Sözlük",
"Enter text in Turkmen": "Türkmençe sözleri gir",
"Translate": "Terjime et"
"Translate": "Terjime et",
"Salah": "Namaz",
"menuSalah": "Namaz"
}

205
package-lock.json generated
View File

@@ -13,10 +13,12 @@
"@react-navigation/native": "^7.1.6",
"adhan": "^4.4.3",
"expo": "~53.0.20",
"expo-file-system": "~18.1.11",
"expo-font": "~13.3.2",
"expo-linking": "~7.1.7",
"expo-localization": "^16.1.6",
"expo-router": "~5.1.4",
"expo-sharing": "~13.1.5",
"expo-splash-screen": "~0.30.10",
"expo-status-bar": "~2.2.3",
"expo-system-ui": "~5.0.10",
@@ -24,17 +26,15 @@
"expo-web-browser": "~14.2.0",
"i18n-js": "^4.5.1",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-native": "0.79.5",
"react-native-picker-select": "^9.3.1",
"react-native-reanimated": "~3.17.4",
"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-vector-icons": "^10.3.0"
},
"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",
@@ -4349,6 +4349,8 @@
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz",
"integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==",
"optional": true,
"peer": true,
"dependencies": {
"node-fetch": "^2.7.0"
}
@@ -4378,6 +4380,8 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz",
"integrity": "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==",
"optional": true,
"peer": true,
"dependencies": {
"hyphenate-style-name": "^1.0.3"
}
@@ -5179,6 +5183,14 @@
"node": ">=10"
}
},
"node_modules/expo-sharing": {
"version": "13.1.5",
"resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-13.1.5.tgz",
"integrity": "sha512-X/5sAEiWXL2kdoGE3NO5KmbfcmaCWuWVZXHu8OQef7Yig4ZgHFkGD11HKJ5KqDrDg+SRZe4ISd6MxE7vGUgm4w==",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-splash-screen": {
"version": "0.30.10",
"resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.30.10.tgz",
@@ -5291,6 +5303,13 @@
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
},
"node_modules/fast-loops": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/fast-loops/-/fast-loops-1.1.4.tgz",
"integrity": "sha512-8dbd3XWoKCTms18ize6JmQF1SFnnfj5s0B7rRry22EofgMu7B6LKHVh+XfFqFGsqnbH54xgeO83PzpKI+ODhlg==",
"optional": true,
"peer": true
},
"node_modules/fast-uri": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
@@ -5318,6 +5337,8 @@
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz",
"integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==",
"optional": true,
"peer": true,
"dependencies": {
"cross-fetch": "^3.1.5",
"fbjs-css-vars": "^1.0.0",
@@ -5331,12 +5352,16 @@
"node_modules/fbjs-css-vars": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
"integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="
"integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==",
"optional": true,
"peer": true
},
"node_modules/fbjs/node_modules/promise": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
"optional": true,
"peer": true,
"dependencies": {
"asap": "~2.0.3"
}
@@ -5767,7 +5792,9 @@
"node_modules/hyphenate-style-name": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz",
"integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw=="
"integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==",
"optional": true,
"peer": true
},
"node_modules/i18n-js": {
"version": "4.5.1",
@@ -5900,11 +5927,14 @@
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
},
"node_modules/inline-style-prefixer": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz",
"integrity": "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==",
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-6.0.4.tgz",
"integrity": "sha512-FwXmZC2zbeeS7NzGjJ6pAiqRhXR0ugUShSNb6GApMl6da0/XGc4MOJsoWAywia52EEWbXNSy0pzkwz/+Y+swSg==",
"optional": true,
"peer": true,
"dependencies": {
"css-in-js-utils": "^3.1.0"
"css-in-js-utils": "^3.1.0",
"fast-loops": "^1.1.3"
}
},
"node_modules/invariant": {
@@ -6423,6 +6453,55 @@
"react-native": "*"
}
},
"node_modules/jest-expo/node_modules/react": {
"version": "19.1.1",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/jest-expo/node_modules/react-dom": {
"version": "19.1.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
"dev": true,
"peer": true,
"dependencies": {
"scheduler": "^0.26.0"
},
"peerDependencies": {
"react": "^19.1.1"
}
},
"node_modules/jest-expo/node_modules/react-server-dom-webpack": {
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/react-server-dom-webpack/-/react-server-dom-webpack-19.0.0.tgz",
"integrity": "sha512-hLug9KEXLc8vnU9lDNe2b2rKKDaqrp5gNiES4uyu2Up3FZfZJZmdwLFXlWzdA9gTB/6/cWduSB2K1Lfag2pSvw==",
"dev": true,
"dependencies": {
"acorn-loose": "^8.3.0",
"neo-async": "^2.6.1",
"webpack-sources": "^3.2.0"
},
"engines": {
"node": ">=0.10.0"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"webpack": "^5.59.0"
}
},
"node_modules/jest-expo/node_modules/scheduler": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
"dev": true,
"peer": true
},
"node_modules/jest-get-type": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
@@ -8058,6 +8137,8 @@
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"optional": true,
"peer": true,
"dependencies": {
"whatwg-url": "^5.0.0"
},
@@ -8076,17 +8157,23 @@
"node_modules/node-fetch/node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"optional": true,
"peer": true
},
"node_modules/node-fetch/node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"optional": true,
"peer": true
},
"node_modules/node-fetch/node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"optional": true,
"peer": true,
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
@@ -8619,7 +8706,9 @@
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"optional": true,
"peer": true
},
"node_modules/pretty-bytes": {
"version": "5.6.0",
@@ -8865,14 +8954,27 @@
}
},
"node_modules/react-dom": {
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"optional": true,
"peer": true,
"dependencies": {
"scheduler": "^0.25.0"
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
},
"peerDependencies": {
"react": "^19.0.0"
"react": "^18.3.1"
}
},
"node_modules/react-dom/node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"optional": true,
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"node_modules/react-fast-compare": {
@@ -9121,33 +9223,39 @@
}
},
"node_modules/react-native-web": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.20.0.tgz",
"integrity": "sha512-OOSgrw+aON6R3hRosCau/xVxdLzbjEcsLysYedka0ZON4ZZe6n9xgeN9ZkoejhARM36oTlUgHIQqxGutEJ9Wxg==",
"version": "0.19.13",
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.13.tgz",
"integrity": "sha512-etv3bN8rJglrRCp/uL4p7l8QvUNUC++QwDbdZ8CB7BvZiMvsxfFIRM1j04vxNldG3uo2puRd6OSWR3ibtmc29A==",
"optional": true,
"peer": true,
"dependencies": {
"@babel/runtime": "^7.18.6",
"@react-native/normalize-colors": "^0.74.1",
"fbjs": "^3.0.4",
"inline-style-prefixer": "^7.0.1",
"inline-style-prefixer": "^6.0.1",
"memoize-one": "^6.0.0",
"nullthrows": "^1.1.1",
"postcss-value-parser": "^4.2.0",
"styleq": "^0.1.3"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
},
"node_modules/react-native-web/node_modules/@react-native/normalize-colors": {
"version": "0.74.89",
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.89.tgz",
"integrity": "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg=="
"integrity": "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg==",
"optional": true,
"peer": true
},
"node_modules/react-native-web/node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
"optional": true,
"peer": true
},
"node_modules/react-native/node_modules/@react-native/virtualized-lists": {
"version": "0.79.5",
@@ -9246,25 +9354,6 @@
"node": ">=0.10.0"
}
},
"node_modules/react-server-dom-webpack": {
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/react-server-dom-webpack/-/react-server-dom-webpack-19.0.0.tgz",
"integrity": "sha512-hLug9KEXLc8vnU9lDNe2b2rKKDaqrp5gNiES4uyu2Up3FZfZJZmdwLFXlWzdA9gTB/6/cWduSB2K1Lfag2pSvw==",
"dev": true,
"dependencies": {
"acorn-loose": "^8.3.0",
"neo-async": "^2.6.1",
"webpack-sources": "^3.2.0"
},
"engines": {
"node": ">=0.10.0"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"webpack": "^5.59.0"
}
},
"node_modules/react-test-renderer": {
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.0.0.tgz",
@@ -9780,7 +9869,9 @@
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"optional": true,
"peer": true
},
"node_modules/setprototypeof": {
"version": "1.2.0",
@@ -10178,7 +10269,9 @@
"node_modules/styleq": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/styleq/-/styleq-0.1.3.tgz",
"integrity": "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA=="
"integrity": "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==",
"optional": true,
"peer": true
},
"node_modules/sucrase": {
"version": "3.35.0",
@@ -10250,13 +10343,17 @@
"dev": true
},
"node_modules/tapable": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
"integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz",
"integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==",
"dev": true,
"peer": true,
"engines": {
"node": ">=6"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/tar": {
@@ -10591,6 +10688,8 @@
"url": "https://github.com/sponsors/faisalman"
}
],
"optional": true,
"peer": true,
"bin": {
"ua-parser-js": "script/cli.js"
},
@@ -10838,9 +10937,9 @@
}
},
"node_modules/webpack": {
"version": "5.101.2",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.2.tgz",
"integrity": "sha512-4JLXU0tD6OZNVqlwzm3HGEhAHufSiyv+skb7q0d2367VDMzrU1Q/ZeepvkcHH0rZie6uqEtTQQe0OEOOluH3Mg==",
"version": "5.101.3",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz",
"integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==",
"dev": true,
"peer": true,
"dependencies": {

View File

@@ -18,10 +18,12 @@
"@react-navigation/native": "^7.1.6",
"adhan": "^4.4.3",
"expo": "~53.0.20",
"expo-file-system": "~18.1.11",
"expo-font": "~13.3.2",
"expo-linking": "~7.1.7",
"expo-localization": "^16.1.6",
"expo-router": "~5.1.4",
"expo-sharing": "~13.1.5",
"expo-splash-screen": "~0.30.10",
"expo-status-bar": "~2.2.3",
"expo-system-ui": "~5.0.10",
@@ -29,14 +31,12 @@
"expo-web-browser": "~14.2.0",
"i18n-js": "^4.5.1",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-native": "0.79.5",
"react-native-picker-select": "^9.3.1",
"react-native-reanimated": "~3.17.4",
"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.19.6"
"react-native-vector-icons": "^10.3.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",

16
utils/pdf.ts Normal file
View File

@@ -0,0 +1,16 @@
import * as FileSystem from 'expo-file-system';
import * as Linking from 'expo-linking';
import { Asset } from 'expo-asset';
export const openPdf = async () => {
// const asset = Asset.fromModule(require('../assets/pdf/quran_in_turkmen.pdf'));
// const localUri = `${FileSystem.documentDirectory}${asset.name}`;
// const fileInfo = await FileSystem.getInfoAsync(localUri);
// if (!fileInfo.exists) {
// await FileSystem.downloadAsync(asset.uri, localUri);
// }
// await Linking.openURL(localUri);
};