├── .env ├── main.js ├── app ├── index.tsx ├── jozz │ └── [id].tsx ├── favs │ └── index.tsx ├── surah │ └── [id].tsx ├── azkar │ └── [category].tsx ├── qibla │ └── index.tsx ├── prayers │ └── index.tsx ├── +not-found.tsx └── _layout.tsx ├── assets ├── quran.db ├── images │ ├── icon.png │ ├── kaba.png │ ├── splash.png │ ├── compass.png │ ├── dark_splash.png │ ├── adaptive-icon.png │ ├── compass_dark.png │ └── compass_light.png ├── fonts │ ├── UthmanicHafs.ttf │ ├── HelveticaNeueLTArabic-Bold.ttf │ ├── HelveticaNeueLTArabic-Light.ttf │ └── HelveticaNeueLTArabic-Roman.ttf └── icons │ ├── big_arrow.svg │ ├── Sun.svg │ ├── Menu.svg │ ├── bookmark.svg │ ├── arrow_right.svg │ ├── Search.svg │ ├── Moon.svg │ ├── finger_press.svg │ └── doc.svg ├── babel.config.js ├── motion-custom.d.ts ├── screens ├── home │ ├── components │ │ ├── NoRecentView.tsx │ │ ├── MenuItem.tsx │ │ ├── JozzCard.tsx │ │ ├── CardContent.tsx │ │ ├── ContinueReadingButton.tsx │ │ ├── ReaderDropDown.tsx │ │ ├── SurahCard.tsx │ │ ├── ContinePopup.tsx │ │ ├── SurahTab.tsx │ │ ├── JozzTab.tsx │ │ ├── AzkarTab.tsx │ │ ├── TypeTabs.tsx │ │ ├── SearchInput.tsx │ │ ├── MainCard.tsx │ │ ├── Header.tsx │ │ └── MainDrawer.tsx │ └── index.tsx ├── prayers │ ├── components │ │ └── Prayer.tsx │ └── index.tsx ├── surah │ ├── index.tsx │ ├── components │ │ ├── AyahItem.tsx │ │ ├── AyatView.tsx │ │ ├── SurahBody.tsx │ │ ├── PageBottomBar.tsx │ │ ├── AyaCard.tsx │ │ ├── AyahActionModal.tsx │ │ ├── AyahActionsWrapper.tsx │ │ └── PageView.tsx │ └── Header.tsx ├── jozz │ ├── index.tsx │ ├── components │ │ ├── AyahItem.tsx │ │ ├── AyaView.tsx │ │ ├── AyatBottomNav.tsx │ │ ├── JozzBody.tsx │ │ └── PageView.tsx │ └── Header.tsx ├── qibla │ └── index.tsx ├── favs │ ├── index.tsx │ └── components │ │ └── FavCard.tsx └── azkar │ ├── index.tsx │ └── components │ ├── CompletedModal.tsx │ ├── ZekrCard.tsx │ └── ZekerCount.tsx ├── declarations.d.ts ├── tsconfig.json ├── utils ├── db │ ├── useJozzList.ts │ ├── useGetSuar.ts │ ├── useGetAzkar.ts │ ├── useGetAzkarCategories.ts │ ├── useGetAyatAsJozz.ts │ └── useGetSurahWithAyat.ts ├── useOnChange.ts ├── useOnAyaScrolling.ts ├── useGoToRecent.ts ├── useScrollToAya.ts ├── usePagedAyat.ts ├── useGetPrayersTime.ts └── index.ts ├── services ├── prayers.ts └── Favs.ts ├── eas.json ├── components ├── InnerSplash │ └── index.tsx ├── Modal │ └── index.tsx ├── Tabs │ └── index.tsx ├── Select │ └── index.tsx └── QiblaCompass │ └── index.tsx ├── metro.config.js ├── .gitignore ├── tailwind.config.js ├── package.json ├── README.md ├── app.json ├── types └── index.ts └── LICENSE /.env: -------------------------------------------------------------------------------- 1 | EXPO_ROUTER_APP_ROOT="app" -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import "expo-router/entry"; 2 | -------------------------------------------------------------------------------- /app/index.tsx: -------------------------------------------------------------------------------- 1 | import Home from "../screens/home"; 2 | export default Home; 3 | -------------------------------------------------------------------------------- /app/jozz/[id].tsx: -------------------------------------------------------------------------------- 1 | import Jozz from "@/screens/jozz"; 2 | export default Jozz; 3 | -------------------------------------------------------------------------------- /app/favs/index.tsx: -------------------------------------------------------------------------------- 1 | import Favs from "../../screens/favs"; 2 | export default Favs; 3 | -------------------------------------------------------------------------------- /app/surah/[id].tsx: -------------------------------------------------------------------------------- 1 | import Surah from "@/screens/surah"; 2 | export default Surah; 3 | -------------------------------------------------------------------------------- /app/azkar/[category].tsx: -------------------------------------------------------------------------------- 1 | import azkar from "@/screens/azkar"; 2 | export default azkar; 3 | -------------------------------------------------------------------------------- /app/qibla/index.tsx: -------------------------------------------------------------------------------- 1 | import Qibla from "../../screens/qibla"; 2 | export default Qibla; 3 | -------------------------------------------------------------------------------- /app/prayers/index.tsx: -------------------------------------------------------------------------------- 1 | import Prayers from "../../screens/prayers"; 2 | export default Prayers; 3 | -------------------------------------------------------------------------------- /assets/quran.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obeim/react-native-quran-app/HEAD/assets/quran.db -------------------------------------------------------------------------------- /assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obeim/react-native-quran-app/HEAD/assets/images/icon.png -------------------------------------------------------------------------------- /assets/images/kaba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obeim/react-native-quran-app/HEAD/assets/images/kaba.png -------------------------------------------------------------------------------- /assets/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obeim/react-native-quran-app/HEAD/assets/images/splash.png -------------------------------------------------------------------------------- /assets/images/compass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obeim/react-native-quran-app/HEAD/assets/images/compass.png -------------------------------------------------------------------------------- /assets/fonts/UthmanicHafs.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obeim/react-native-quran-app/HEAD/assets/fonts/UthmanicHafs.ttf -------------------------------------------------------------------------------- /assets/images/dark_splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obeim/react-native-quran-app/HEAD/assets/images/dark_splash.png -------------------------------------------------------------------------------- /assets/images/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obeim/react-native-quran-app/HEAD/assets/images/adaptive-icon.png -------------------------------------------------------------------------------- /assets/images/compass_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obeim/react-native-quran-app/HEAD/assets/images/compass_dark.png -------------------------------------------------------------------------------- /assets/images/compass_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obeim/react-native-quran-app/HEAD/assets/images/compass_light.png -------------------------------------------------------------------------------- /assets/fonts/HelveticaNeueLTArabic-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obeim/react-native-quran-app/HEAD/assets/fonts/HelveticaNeueLTArabic-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/HelveticaNeueLTArabic-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obeim/react-native-quran-app/HEAD/assets/fonts/HelveticaNeueLTArabic-Light.ttf -------------------------------------------------------------------------------- /assets/fonts/HelveticaNeueLTArabic-Roman.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obeim/react-native-quran-app/HEAD/assets/fonts/HelveticaNeueLTArabic-Roman.ttf -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ["babel-preset-expo"], 5 | plugins: ["nativewind/babel"], 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /motion-custom.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@legendapp/motion" { 2 | export * from "@legendapp/motion/react-native"; 3 | import type { ComponentType } from "react"; 4 | 5 | // Fix JSX inference for Motion components 6 | export const Motion: Record>; 7 | } 8 | -------------------------------------------------------------------------------- /screens/home/components/NoRecentView.tsx: -------------------------------------------------------------------------------- 1 | import { Text } from "react-native"; 2 | 3 | export function NoRecentView() { 4 | return ( 5 | 6 | لم يتم قراءة شئ موخرا 7 | 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /declarations.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module "*.svg" { 4 | import React from "react"; 5 | import { SvgProps } from "react-native-svg"; 6 | const content: React.FC; 7 | export default content; 8 | } 9 | 10 | declare module "*.db" { 11 | const content: any; 12 | export default content; 13 | } 14 | -------------------------------------------------------------------------------- /assets/icons/big_arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true, 5 | "types": ["react", "react-native"], 6 | "jsx": "react-jsx", 7 | "paths": { 8 | "@/*": ["./*"] 9 | } 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "**/*.tsx", 14 | ".expo/types/**/*.ts", 15 | "expo-env.d.ts", 16 | "motion-custom.d.ts", 17 | "App.js" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /utils/db/useJozzList.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export default function useJozzList() { 4 | const [jozzs, setJozzs] = useState<{ id: number; name: string }[]>([]); 5 | 6 | useEffect(() => { 7 | const list = Array.from({ length: 30 }, (_, i) => ({ 8 | id: i + 1, 9 | name: `الجزء ${i + 1}`, 10 | })); 11 | setJozzs(list); 12 | }, []); 13 | 14 | return jozzs; 15 | } 16 | -------------------------------------------------------------------------------- /assets/icons/Sun.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /utils/useOnChange.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | const useOnChange = ({ delay }: { delay?: number }) => { 4 | const [timeout, setTimeOutValue] = useState(); 5 | 6 | const onChange = (callback: () => void) => { 7 | if (timeout) clearTimeout(timeout); 8 | setTimeOutValue( 9 | setTimeout(function () { 10 | callback(); 11 | }, delay || 1000) 12 | ); 13 | }; 14 | return { onChange }; 15 | }; 16 | 17 | export default useOnChange; 18 | -------------------------------------------------------------------------------- /services/prayers.ts: -------------------------------------------------------------------------------- 1 | import { PrayerTime } from "@/types"; 2 | 3 | export type PrayerResponse = PrayerTime[]; 4 | export async function getPrayerTimes({ 5 | lat, 6 | long, 7 | }: { 8 | lat: number; 9 | long: number; 10 | }) { 11 | const response = await fetch( 12 | `http://api.aladhan.com/v1/calendar/${new Date().getFullYear()}/${ 13 | new Date().getMonth() + 1 14 | }?latitude=${lat}&longitude=${long}` 15 | ); 16 | const dataResponse = await response.json(); 17 | return dataResponse.data as PrayerResponse; 18 | } 19 | -------------------------------------------------------------------------------- /eas.json: -------------------------------------------------------------------------------- 1 | { 2 | "cli": { 3 | "version": ">= 7.0.0" 4 | }, 5 | "build": { 6 | "development": { 7 | "developmentClient": true, 8 | "distribution": "internal" 9 | }, 10 | "preview": { 11 | "distribution": "internal", 12 | "env": { 13 | "EXPO_ROUTER_APP_ROOT": "app" 14 | } 15 | }, 16 | "production": { 17 | "env": { 18 | "EXPO_ROUTER_APP_ROOT": "app" 19 | }, 20 | "android": { "buildType": "apk" } 21 | } 22 | }, 23 | "submit": { 24 | "production": {} 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /screens/prayers/components/Prayer.tsx: -------------------------------------------------------------------------------- 1 | import { Text, View } from "react-native"; 2 | 3 | export function Prayer({ title, value }: { title: string; value: string }) { 4 | return ( 5 | 6 | 7 | {title} 8 | 9 | 10 | {value} 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /utils/db/useGetSuar.ts: -------------------------------------------------------------------------------- 1 | import { Surah, SurahwithAyat } from "@/types"; 2 | import { useSQLiteContext } from "expo-sqlite"; 3 | import { useEffect, useState } from "react"; 4 | 5 | const useGetSuar = () => { 6 | const db = useSQLiteContext(); 7 | const [suar, setSuar] = useState(); 8 | const query = ` 9 | SELECT * 10 | FROM suar; 11 | `; 12 | useEffect(() => { 13 | async function getSuar() { 14 | const result = await db.getAllAsync(query); 15 | setSuar(result as Surah[]); 16 | } 17 | getSuar(); 18 | }, []); 19 | return suar; 20 | }; 21 | export default useGetSuar; 22 | -------------------------------------------------------------------------------- /screens/home/components/MenuItem.tsx: -------------------------------------------------------------------------------- 1 | import { Pressable, Text, View } from "react-native"; 2 | 3 | interface ItemProps { 4 | icon: React.JSX.Element; 5 | title: string; 6 | onPress?: () => void; 7 | } 8 | export function MenuItem({ icon, title, onPress }: ItemProps) { 9 | return ( 10 | 14 | {icon} 15 | 16 | {title} 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /utils/useOnAyaScrolling.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import useOnChange from "./useOnChange"; 3 | import { onAyaChanged } from "."; 4 | 5 | const useOnAyaScrolling = ({ type }: { type?: "surah" | "jozz" }) => { 6 | const { onChange } = useOnChange({ delay: 1000 }); 7 | 8 | const onViewableItemsChanged = ({ viewableItems }: any) => { 9 | onAyaChanged({ viewableItems, onChange, type }); 10 | }; 11 | 12 | const viewabilityConfigCallbackPairs = useRef([{ onViewableItemsChanged }]); 13 | 14 | return { 15 | viewabilityConfigCallbackPairs, 16 | }; 17 | }; 18 | 19 | export default useOnAyaScrolling; 20 | -------------------------------------------------------------------------------- /components/InnerSplash/index.tsx: -------------------------------------------------------------------------------- 1 | import { useColorScheme } from "nativewind"; 2 | import { Image, View } from "react-native"; 3 | 4 | const InnerSplash = () => { 5 | const { colorScheme } = useColorScheme(); 6 | const splash = 7 | colorScheme === "dark" 8 | ? require("../../assets/images/dark_splash.png") 9 | : require("../../assets/images/splash.png"); 10 | 11 | return ( 12 | 13 | 14 | 15 | ); 16 | }; 17 | 18 | export default InnerSplash; 19 | -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | const { getDefaultConfig } = require("expo/metro-config"); 2 | 3 | module.exports = (() => { 4 | const config = getDefaultConfig(__dirname); 5 | 6 | config.resolver.assetExts.push("db"); 7 | 8 | const { transformer, resolver } = config; 9 | 10 | config.transformer = { 11 | ...transformer, 12 | babelTransformerPath: require.resolve("react-native-svg-transformer"), 13 | }; 14 | config.resolver = { 15 | ...resolver, 16 | assetExts: [...resolver.assetExts.filter((ext) => ext !== "svg")], 17 | sourceExts: [...resolver.sourceExts, "svg"], 18 | }; 19 | 20 | return config; 21 | })(); 22 | -------------------------------------------------------------------------------- /assets/icons/Menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /utils/db/useGetAzkar.ts: -------------------------------------------------------------------------------- 1 | import { Azkar } from "@/types"; 2 | import { useSQLiteContext } from "expo-sqlite"; 3 | import { useEffect, useState } from "react"; 4 | 5 | const useGetAzkar = (category: string) => { 6 | const db = useSQLiteContext(); 7 | const [categories, setCategory] = useState(); 8 | const query = ` 9 | SELECT * 10 | FROM Azkar 11 | WHERE category = ?; 12 | `; 13 | useEffect(() => { 14 | async function getAzkar() { 15 | const result = await db.getAllAsync(query, [category]); 16 | setCategory(result as Azkar[]); 17 | } 18 | getAzkar(); 19 | }, []); 20 | return categories; 21 | }; 22 | export default useGetAzkar; 23 | -------------------------------------------------------------------------------- /utils/db/useGetAzkarCategories.ts: -------------------------------------------------------------------------------- 1 | import { category } from "@/types"; 2 | import { useSQLiteContext } from "expo-sqlite"; 3 | import { useEffect, useState } from "react"; 4 | 5 | const useGetAzkarCategories = () => { 6 | const db = useSQLiteContext(); 7 | const [categories, setCategory] = useState(); 8 | const query = ` 9 | SELECT * 10 | FROM azkar_categories; 11 | `; 12 | useEffect(() => { 13 | async function getCategories() { 14 | const result = await db.getAllAsync(query); 15 | setCategory(result as category[]); 16 | } 17 | getCategories(); 18 | }, []); 19 | return categories; 20 | }; 21 | export default useGetAzkarCategories; 22 | -------------------------------------------------------------------------------- /screens/home/components/JozzCard.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from "react"; 2 | import { Text, Pressable } from "react-native"; 3 | 4 | const JozzCard = ({ 5 | jozz, 6 | onPress, 7 | }: { 8 | jozz: { id: number; name: string }; 9 | onPress?: () => void; 10 | }) => { 11 | return ( 12 | 16 | 17 | {jozz.name} 18 | 19 | 20 | ); 21 | }; 22 | 23 | export default memo(JozzCard); 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | 11 | # Native 12 | *.orig.* 13 | *.jks 14 | *.p8 15 | *.p12 16 | *.key 17 | *.mobileprovision 18 | android 19 | 20 | # Metro 21 | .metro-health-check* 22 | 23 | # debug 24 | npm-debug.* 25 | yarn-debug.* 26 | yarn-error.* 27 | 28 | # macOS 29 | .DS_Store 30 | *.pem 31 | 32 | # local env files 33 | .env*.local 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | 38 | # @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb 39 | # The following patterns were generated by expo-cli 40 | 41 | expo-env.d.ts 42 | # @end expo-cli -------------------------------------------------------------------------------- /components/Modal/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from "react"; 2 | import { Modal, Pressable } from "react-native"; 3 | 4 | const MyModal: FC< 5 | PropsWithChildren<{ 6 | isOpen: boolean; 7 | close: () => void; 8 | animationType?: "fade" | "none" | "slide"; 9 | }> 10 | > = ({ isOpen, close, children, animationType }) => { 11 | return ( 12 | close()} 14 | visible={isOpen} 15 | transparent 16 | animationType={animationType || "fade"} 17 | > 18 | { 20 | close(); 21 | }} 22 | className=" w-full h-full absolute" 23 | > 24 | 25 | {children} 26 | 27 | ); 28 | }; 29 | 30 | export default MyModal; 31 | -------------------------------------------------------------------------------- /screens/surah/index.tsx: -------------------------------------------------------------------------------- 1 | import { useLocalSearchParams } from "expo-router"; 2 | import { View } from "react-native"; 3 | import { useMemo } from "react"; 4 | 5 | import { useKeepAwake } from "expo-keep-awake"; 6 | import { useGetSurahWithAyat } from "@/utils/db/useGetSurahWithAyat"; 7 | import SurahBody from "./components/SurahBody"; 8 | 9 | const Surah = () => { 10 | useKeepAwake(); 11 | const local = useLocalSearchParams(); 12 | 13 | const surahId = useMemo( 14 | () => parseInt((local.id as string).split("s")[0]), 15 | [local.id] 16 | ); 17 | const data = useGetSurahWithAyat(surahId); 18 | 19 | if (!data) return null; 20 | 21 | return ( 22 | 23 | 24 | 25 | ); 26 | }; 27 | 28 | export default Surah; 29 | -------------------------------------------------------------------------------- /screens/jozz/index.tsx: -------------------------------------------------------------------------------- 1 | import { View } from "react-native"; 2 | import { useMemo } from "react"; 3 | import { useLocalSearchParams } from "expo-router"; 4 | import { useKeepAwake } from "expo-keep-awake"; 5 | 6 | import { useGetAyatAsJozz } from "@/utils/db/useGetAyatAsJozz"; 7 | import JozzBody from "./components/JozzBody"; 8 | 9 | const Jozz = () => { 10 | useKeepAwake(); 11 | const local = useLocalSearchParams(); 12 | 13 | const jozzId = useMemo( 14 | () => parseInt((local?.id as string).split("s")[0]), 15 | [local?.id] 16 | ); 17 | 18 | const data = useGetAyatAsJozz(jozzId); 19 | 20 | if (!data) return null; 21 | 22 | return ( 23 | 24 | 25 | 26 | ); 27 | }; 28 | 29 | export default Jozz; 30 | -------------------------------------------------------------------------------- /assets/icons/bookmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /utils/db/useGetAyatAsJozz.ts: -------------------------------------------------------------------------------- 1 | import { Ayah } from "@/types"; 2 | import { useSQLiteContext } from "expo-sqlite"; 3 | import { useEffect, useState } from "react"; 4 | 5 | export const useGetAyatAsJozz = (id: number) => { 6 | const db = useSQLiteContext(); 7 | const [ayat, setAyat] = useState(); 8 | const query = ` 9 | SELECT * 10 | FROM ayat 11 | WHERE jozz = ? 12 | ORDER BY id ASC; 13 | `; 14 | 15 | useEffect(() => { 16 | async function getAyat() { 17 | let result = (await db.getAllAsync(query, [id])) as Ayah[]; 18 | result = result.map((aya, index) => { 19 | if (index > 0 && aya.sora === result[index - 1].sora) 20 | return { ...aya, sora_name_ar: `${aya.sora_name_ar},no` }; 21 | else return aya; 22 | }); 23 | setAyat(result); 24 | } 25 | getAyat(); 26 | }, []); 27 | 28 | return ayat; 29 | }; 30 | -------------------------------------------------------------------------------- /assets/icons/arrow_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /screens/home/components/CardContent.tsx: -------------------------------------------------------------------------------- 1 | import { Text, View } from "react-native"; 2 | import { ContinueReadingButton } from "./ContinueReadingButton"; 3 | import { memo } from "react"; 4 | 5 | const CardContent = ({ 6 | primary_text, 7 | secondary_text, 8 | onClick, 9 | }: { 10 | primary_text: string; 11 | secondary_text: string; 12 | onClick: () => void; 13 | }) => { 14 | return ( 15 | <> 16 | 17 | 18 | {primary_text} 19 | 20 | 21 | {secondary_text} 22 | 23 | 24 | 25 | 26 | ); 27 | }; 28 | export default memo(CardContent); 29 | -------------------------------------------------------------------------------- /assets/icons/Search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./App.{js,jsx,ts,tsx}", 5 | "./screens/**/*.{js,jsx,ts,tsx}", 6 | "./components/**/*.{js,jsx,ts,tsx}", 7 | ], 8 | theme: { 9 | extend: { 10 | fontFamily: { 11 | HelveticaLight: ["HelveticaNeueLTArabic-Light"], 12 | HelveticaBold: ["HelveticaNeueLTArabic-Bold"], 13 | HelveticaRoman: ["HelveticaNeueLTArabic-Roman"], 14 | UthmanicHafs: ["UthmanicHafs"], 15 | }, 16 | colors: { 17 | primary: "#544981", // light mode text color 18 | secondary: "#2D264B", 19 | lotion: "#F5F4F4", // light mode card bg 20 | lightGray: "#FBFBFB", 21 | darkGray: "#505050", 22 | primaryDark: "#FAF0E6", // dark mode text color 23 | blackCoral: "#292630", //dark mode card bg 24 | darkBg: "#34303D", 25 | }, 26 | }, 27 | }, 28 | plugins: [], 29 | }; 30 | -------------------------------------------------------------------------------- /app/+not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Link, Stack } from "expo-router"; 2 | import { StyleSheet, Text, View } from "react-native"; 3 | 4 | export default function NotFoundScreen() { 5 | return ( 6 | <> 7 | 8 | 9 | This screen doesn't exist. 10 | 11 | 12 | Go to home screen! 13 | 14 | 15 | 16 | ); 17 | } 18 | 19 | const styles = StyleSheet.create({ 20 | container: { 21 | flex: 1, 22 | alignItems: "center", 23 | justifyContent: "center", 24 | padding: 20, 25 | }, 26 | title: { 27 | fontSize: 20, 28 | fontWeight: "bold", 29 | }, 30 | link: { 31 | marginTop: 15, 32 | paddingVertical: 15, 33 | }, 34 | linkText: { 35 | fontSize: 14, 36 | color: "#2e78b7", 37 | }, 38 | }); 39 | -------------------------------------------------------------------------------- /screens/home/components/ContinueReadingButton.tsx: -------------------------------------------------------------------------------- 1 | import { Pressable, Text } from "react-native"; 2 | import ArrowRight from "@/assets/icons/arrow_right.svg"; 3 | import { useColorScheme } from "nativewind"; 4 | 5 | export function ContinueReadingButton({ onClick }: { onClick?: () => void }) { 6 | const { colorScheme } = useColorScheme(); 7 | 8 | return ( 9 | { 12 | if (onClick) onClick(); 13 | }} 14 | > 15 | 16 | متابعة القراءة 17 | 18 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /screens/home/components/ReaderDropDown.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { storage } from "@/utils"; 3 | import { Select } from "@/components/Select"; 4 | 5 | const data = [ 6 | { label: " مشاري العفاسي", value: "ar.alafasy" }, 7 | { label: "ماهر المعيقلي", value: "ar.mahermuaiqly" }, 8 | { label: "الحصري", value: "ar.husary" }, 9 | { label: "محمد صديق المنشاوي", value: "ar.minshawimujawwad" }, 10 | { label: "عبد الباسط عبد الصمد", value: "ar.abdulsamad" }, 11 | { label: "عبد الرحمن السديس", value: "ar.abdurrahmaansudais" }, 12 | { label: "أيمن سويد", value: "ar.aymanswoaid" }, 13 | ]; 14 | export const ReaderDropDown = () => { 15 | const [reader, setReader] = useState( 16 | storage.getString("reader") || "ar.alafasy" 17 | ); 18 | 19 | return ( 20 | { 104 | setFont(value.value); 105 | storage.set("fontSize", value.value); 106 | }} 107 | value={font} 108 | itemFontSize={parseInt(font)} 109 | /> 110 | ); 111 | }; 112 | -------------------------------------------------------------------------------- /screens/surah/components/AyahActionsWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { Modal, Pressable, Text, View } from "react-native"; 2 | import { useState } from "react"; 3 | import { AyahActionModal } from "./AyahActionModal"; 4 | import { Ayah, FavType } from "@/types"; 5 | import * as Network from "expo-network"; 6 | import Toast from "react-native-root-toast"; 7 | import Fav from "@/services/Favs"; 8 | import { usePathname } from "expo-router"; 9 | import { AudioPlayer } from "expo-audio"; 10 | import { storage } from "@/utils"; 11 | export function AyahActionsWrapper({ 12 | close, 13 | opened, 14 | ayah, 15 | player, 16 | Favs, 17 | currentPage, 18 | }: { 19 | close: () => void; 20 | opened: boolean; 21 | ayah?: Ayah; 22 | player: AudioPlayer; 23 | Favs: FavType[]; 24 | currentPage?: number; 25 | }) { 26 | const [openMeaning, setOpenMeaning] = useState(false); 27 | const pathname = usePathname(); 28 | return ( 29 | 30 | setOpenMeaning(false)} 32 | visible={openMeaning} 33 | transparent 34 | animationType="fade" 35 | > 36 | { 38 | setOpenMeaning(false); 39 | }} 40 | className="bg-black/40 w-full h-full absolute" 41 | > 42 | 43 | 44 | معاني الأية 45 | 46 | 47 | {ayah?.maany_aya 48 | .replaceAll("\\", "") 49 | .split("n") 50 | .map((text, i) => ( 51 | {`${text} \n`} 52 | ))} 53 | 54 | 55 | 56 | item.id === ayah?.id)} 60 | showMeaning={!!ayah?.maany_aya} 61 | showSave={!!currentPage} 62 | onSave={() => { 63 | let type: { jozz?: number; sora?: number } = {}; 64 | 65 | if (pathname.includes("jozz")) type.jozz = ayah?.jozz; 66 | else type.sora = ayah?.sora; 67 | 68 | if (ayah && currentPage) { 69 | if (!Favs.some((item) => item.id === ayah?.id)) 70 | Fav.addFav({ 71 | text: ayah.aya_text, 72 | id: ayah.id, 73 | page: currentPage, 74 | sora_name: ayah.sora_name_ar, 75 | number: ayah.aya_no, 76 | ...type, 77 | }); 78 | else Fav.deleteFav(ayah.id); 79 | close(); 80 | } 81 | }} 82 | onPlay={async () => { 83 | const network = await Network.getNetworkStateAsync(); 84 | if (network.isConnected) { 85 | if (ayah) 86 | player.replace( 87 | `https://cdn.islamic.network/quran/audio/64/${ 88 | storage.getString("reader") || "ar.alafasy" 89 | }/${ayah?.id}.mp3` 90 | ); 91 | player.play(); 92 | } else { 93 | Toast.show("تأكد من أتصال الانترنت", { 94 | textStyle: { fontFamily: "HelveticaNeueLTArabic-Roman" }, 95 | }); 96 | } 97 | close(); 98 | }} 99 | onPressMeaning={() => { 100 | setOpenMeaning(true); 101 | close(); 102 | }} 103 | /> 104 | 105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /screens/jozz/components/PageView.tsx: -------------------------------------------------------------------------------- 1 | import { ScrollView, Text, View } from "react-native"; 2 | import { Ayah } from "@/types"; 3 | import usePagedAyat from "@/utils/usePagedAyat"; 4 | import { PageProps } from "./AyaView"; 5 | import { PageBottomBar } from "@/screens/surah/components/PageBottomBar"; 6 | import { Ref, useEffect, useRef } from "react"; 7 | import { copyToCliporad, storage } from "@/utils"; 8 | 9 | export const PageView = ({ 10 | data, 11 | onPress, 12 | Favs, 13 | setCurrentPage, 14 | }: PageProps) => { 15 | const listRef = useRef(null); 16 | const { ayat, nextPage, PrevPage, totalPages, currentPage } = usePagedAyat({ 17 | data: data, 18 | }); 19 | 20 | const fontSize = storage.getString("fontSize"); 21 | 22 | useEffect(() => { 23 | if (ayat) 24 | storage.set( 25 | "recent", 26 | JSON.stringify({ 27 | type: "jozz", 28 | name: ayat[0]?.sora_name_ar.split(",")[0], 29 | page: currentPage, 30 | id: ayat[0]?.jozz, 31 | }) 32 | ); 33 | setCurrentPage?.(currentPage); 34 | }, [currentPage, ayat]); 35 | 36 | return ( 37 | 38 | } 40 | bounces={false} 41 | decelerationRate={0} 42 | className=" px-2 h-[94%] mb-9 " 43 | > 44 | {ayat[0].sora_name_ar.includes("no") && ( 45 | 46 | سورة {ayat[0].sora_name_ar.split(",")[0]} 47 | 48 | )} 49 | {ayat && ( 50 | 51 | 55 | {ayat.map((aya: Ayah, i: number) => ( 56 | fav.id == aya.id) && 59 | "bg-primary/10 dark:bg-gray-200/10" 60 | }`} 61 | key={i} 62 | onLongPress={() => { 63 | copyToCliporad(aya.aya_text); 64 | }} 65 | onPress={() => { 66 | // clicking on aya view 67 | onPress?.(aya); 68 | }} 69 | > 70 | {!aya.sora_name_ar.includes("no") && ( 71 | 72 | {`${i !== 0 ? "\n" : ""} سورة ${aya.sora_name_ar}\n`} 73 | 74 | )} 75 | 76 | 77 | {(aya.sora !== 1 && aya.aya_no === 1 78 | ? aya.aya_text.replace( 79 | "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ", 80 | "" 81 | ) 82 | : aya.aya_text) + `﴿${aya.aya_no}﴾ `} 83 | 84 | 85 | ))} 86 | 87 | 88 | )} 89 | 90 | { 93 | nextPage(); 94 | listRef.current?.scrollTo({ y: 0 }); 95 | }} 96 | PrevPage={() => { 97 | PrevPage(); 98 | listRef.current?.scrollTo({ y: 0 }); 99 | }} 100 | currentPage={currentPage} 101 | totalPages={totalPages} 102 | /> 103 | 104 | ); 105 | }; 106 | -------------------------------------------------------------------------------- /assets/icons/doc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /screens/surah/components/PageView.tsx: -------------------------------------------------------------------------------- 1 | import { ScrollView, Text, View } from "react-native"; 2 | import { Ayah } from "@/types"; 3 | import usePagedAyat from "@/utils/usePagedAyat"; 4 | import { PageProps } from "./AyatView"; 5 | import { PageBottomBar } from "./PageBottomBar"; 6 | import { Ref, useEffect, useRef } from "react"; 7 | import { copyToCliporad, storage } from "@/utils"; 8 | 9 | export const PageView = ({ 10 | data, 11 | onPressAyah, 12 | setCurrentPage, 13 | Favs, 14 | }: PageProps) => { 15 | const listRef = useRef(null); 16 | 17 | const { ayat, nextPage, PrevPage, totalPages, currentPage } = usePagedAyat({ 18 | data: data?.ayat, 19 | }); 20 | 21 | const fontSize = storage.getString("fontSize"); 22 | 23 | useEffect(() => { 24 | if (data && currentPage) 25 | storage.set( 26 | "recent", 27 | JSON.stringify({ 28 | type: "surah", 29 | name: data?.name_ar.replace("سورة", ""), 30 | page: currentPage, 31 | id: data?.id, 32 | }) 33 | ); 34 | }, []); 35 | 36 | useEffect(() => { 37 | if (data) 38 | storage.set( 39 | "recent", 40 | JSON.stringify({ 41 | type: "surah", 42 | name: data?.name_ar.replace("سورة", ""), 43 | page: currentPage, 44 | id: data?.id, 45 | }) 46 | ); 47 | setCurrentPage?.(currentPage); 48 | }, [currentPage, data]); 49 | 50 | return ( 51 | 52 | } 54 | bounces={false} 55 | decelerationRate={0} 56 | className=" px-2 h-[94%] py-3 mb-9 " 57 | > 58 | 59 | الجزء {ayat[ayat?.length - 1].jozz} 60 | 61 | {currentPage === 1 && 62 | (ayat[0] as Ayah).sora !== 1 && 63 | (ayat[0] as Ayah).sora !== 9 && ( 64 | 65 | بِسْمِ ٱللَّهِ ٱلرَّحْمَٰنِ ٱلرَّحِيمِ 66 | 67 | )} 68 | {ayat && ( 69 | 70 | 74 | {ayat.map((aya: Ayah) => ( 75 | fav.id == aya.id) && 78 | "bg-primary/10 dark:bg-gray-200/10 " 79 | }`} 80 | onPress={() => { 81 | onPressAyah?.(aya); 82 | }} 83 | onLongPress={() => { 84 | copyToCliporad(aya.aya_text); 85 | }} 86 | key={aya.id} 87 | > 88 | {(aya.sora !== 1 && aya.aya_no === 1 89 | ? aya.aya_text.replace( 90 | "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ", 91 | "" 92 | ) 93 | : aya.aya_text) + `﴿${aya.aya_no}﴾ `} 94 | 95 | ))} 96 | 97 | 98 | )} 99 | 100 | { 102 | nextPage(); 103 | listRef.current?.scrollTo({ y: 0 }); 104 | }} 105 | PrevPage={() => { 106 | PrevPage(); 107 | listRef.current?.scrollTo({ y: 0 }); 108 | }} 109 | currentPage={currentPage} 110 | totalPages={totalPages} 111 | type="surah" 112 | /> 113 | 114 | ); 115 | }; 116 | -------------------------------------------------------------------------------- /components/QiblaCompass/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | useState, 3 | useEffect, 4 | useCallback, 5 | forwardRef, 6 | useImperativeHandle, 7 | } from "react"; 8 | import { Image, View, Text, StyleSheet, ActivityIndicator } from "react-native"; 9 | import { Magnetometer, MagnetometerMeasurement } from "expo-sensors"; 10 | import * as Location from "expo-location"; 11 | import { moderateScale } from "react-native-size-matters"; 12 | import { Subscription } from "expo-sensors/build/Pedometer"; 13 | import { Motion } from "@legendapp/motion"; 14 | 15 | export const useQiblaCompass = () => { 16 | const [subscription, setSubscription] = useState(); 17 | const [magnetometer, setMagnetometer] = useState(0); 18 | const [qiblad, setQiblad] = useState(0); 19 | const [error, setError] = useState(); 20 | const [isLoading, setIsLoading] = useState(true); 21 | 22 | const initCompass = useCallback(async () => { 23 | const isAvailable = await Magnetometer.isAvailableAsync(); 24 | if (!isAvailable) { 25 | setError("Compass is not available on this device"); 26 | setIsLoading(false); 27 | return; 28 | } 29 | let { status } = await Location.requestForegroundPermissionsAsync(); 30 | if (status !== "granted") { 31 | setError("Location permission not granted"); 32 | setIsLoading(false); 33 | return; 34 | } 35 | 36 | try { 37 | let location = await Location.getCurrentPositionAsync({}); 38 | const { latitude, longitude } = location.coords; 39 | calculate(latitude, longitude); 40 | } finally { 41 | setIsLoading(false); 42 | subscribe(); 43 | } 44 | }, []); 45 | 46 | useEffect(() => { 47 | initCompass(); 48 | 49 | return () => { 50 | unsubscribe(); 51 | }; 52 | }, []); 53 | 54 | const subscribe = () => { 55 | Magnetometer.setUpdateInterval(100); 56 | setSubscription( 57 | Magnetometer.addListener((data) => { 58 | setMagnetometer(angle(data)); 59 | }) 60 | ); 61 | }; 62 | 63 | const unsubscribe = () => { 64 | subscription && subscription.remove(); 65 | setSubscription(null); 66 | }; 67 | 68 | const angle = (magnetometer: MagnetometerMeasurement) => { 69 | let angle = 0; 70 | if (magnetometer) { 71 | let { x, y, z } = magnetometer; 72 | if (Math.atan2(y, x) >= 0) { 73 | angle = Math.atan2(y, x) * (180 / Math.PI); 74 | } else { 75 | angle = (Math.atan2(y, x) + 2 * Math.PI) * (180 / Math.PI); 76 | } 77 | } 78 | return Math.round(angle); 79 | }; 80 | 81 | const direction = (degree: number) => { 82 | if (degree >= 22.5 && degree < 67.5) { 83 | return "الشمال شرقي"; 84 | } else if (degree >= 67.5 && degree < 112.5) { 85 | return "شرق"; 86 | } else if (degree >= 112.5 && degree < 157.5) { 87 | return "الجنوب الشرقي"; 88 | } else if (degree >= 157.5 && degree < 202.5) { 89 | return "جنوب"; 90 | } else if (degree >= 202.5 && degree < 247.5) { 91 | return "الجنوب الغربي"; 92 | } else if (degree >= 247.5 && degree < 292.5) { 93 | return "الغرب"; 94 | } else if (degree >= 292.5 && degree < 337.5) { 95 | return "الشمال الغربي"; 96 | } else { 97 | return "الشمال"; 98 | } 99 | }; 100 | 101 | const degree = (magnetometer: number) => { 102 | return magnetometer - 90 >= 0 ? magnetometer - 90 : magnetometer + 271; 103 | }; 104 | 105 | const calculate = (latitude: number, longitude: number) => { 106 | const PI = Math.PI; 107 | let latk = (21.4225 * PI) / 180.0; 108 | let longk = (39.8264 * PI) / 180.0; 109 | let phi = (latitude * PI) / 180.0; 110 | let lambda = (longitude * PI) / 180.0; 111 | let qiblad = 112 | (180.0 / PI) * 113 | Math.atan2( 114 | Math.sin(longk - lambda), 115 | Math.cos(phi) * Math.tan(latk) - 116 | Math.sin(phi) * Math.cos(longk - lambda) 117 | ); 118 | setQiblad(qiblad); 119 | }; 120 | 121 | const compassDirection = direction(degree(magnetometer)); 122 | const compassDegree = degree(magnetometer); 123 | const compassRotate = 360 - degree(magnetometer); 124 | const kabaRotate = 360 - degree(magnetometer) + qiblad; 125 | 126 | return { 127 | qiblad, 128 | compassDirection, 129 | compassDegree, 130 | compassRotate, 131 | kabaRotate, 132 | error, 133 | isLoading, 134 | reinitCompass: initCompass, 135 | }; 136 | }; 137 | 138 | const QiblaCompass = forwardRef< 139 | void, 140 | { 141 | backgroundColor: string; 142 | color: string; 143 | textStyles: any; 144 | compassImage?: string; 145 | } 146 | >( 147 | ( 148 | { 149 | backgroundColor = "transparent", 150 | color = "#000", 151 | textStyles = {}, 152 | compassImage, 153 | }, 154 | ref 155 | ) => { 156 | const { 157 | qiblad, 158 | compassDirection, 159 | compassDegree, 160 | compassRotate, 161 | kabaRotate, 162 | error, 163 | isLoading, 164 | reinitCompass, 165 | } = useQiblaCompass(); 166 | 167 | useImperativeHandle( 168 | ref, 169 | () => { 170 | return { 171 | reinitCompass, 172 | }; 173 | }, 174 | [] 175 | ); 176 | 177 | if (isLoading) { 178 | return ( 179 | 180 | 181 | 182 | ); 183 | } 184 | 185 | return ( 186 | 187 | {error && ( 188 | 198 | Error: {error} 199 | 200 | )} 201 | 202 | 203 | {compassDirection} 204 | 205 | = Math.round(qiblad - 4) && 211 | compassDegree <= Math.round(qiblad + 4) 212 | ? "green" 213 | : color, 214 | ...textStyles, 215 | }, 216 | ]} 217 | > 218 | {compassDegree}° 219 | 220 | 221 | 228 | 233 | 245 | 256 | 257 | 258 | 259 | ); 260 | } 261 | ); 262 | 263 | const styles = StyleSheet.create({ 264 | image: { 265 | resizeMode: "contain", 266 | alignSelf: "center", 267 | position: "absolute", 268 | top: 0, 269 | width: moderateScale(300, 0.25), 270 | height: moderateScale(300, 0.25), 271 | }, 272 | container: { 273 | backgroundColor: "#f00", 274 | justifyContent: "center", 275 | alignItems: "center", 276 | position: "relative", 277 | height: "70%", 278 | }, 279 | direction: { 280 | textAlign: "center", 281 | zIndex: 300, 282 | marginBottom: 10, 283 | }, 284 | directionText: { 285 | textAlign: "center", 286 | fontSize: 30, 287 | color: "#468773", 288 | }, 289 | qiblaDirection: { 290 | flexDirection: "row", 291 | justifyContent: "center", 292 | alignItems: "center", 293 | marginTop: 10, 294 | }, 295 | }); 296 | 297 | export default QiblaCompass; 298 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | --------------------------------------------------------------------------------