├── imgs ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── 8.png ├── 9.png ├── 10.png ├── 11.png └── 12.png ├── public ├── favicon.ico ├── logo128.png ├── logo512.png ├── robots.txt ├── manifest.json └── index.html ├── src ├── imgs │ └── quran.jpg ├── fonts │ └── Quicksand-Regular.ttf ├── routes │ ├── index.js │ └── Home.js ├── components │ ├── index.js │ ├── drawer │ │ ├── Switch.jsx │ │ └── TemporaryDrawer.jsx │ ├── player │ │ ├── CurrReciter.jsx │ │ ├── MinimizedPlayer.jsx │ │ ├── VolumeController.jsx │ │ ├── MaximizedPlayer.jsx │ │ ├── Volume.jsx │ │ ├── ArtWork.jsx │ │ ├── CurrSura.jsx │ │ ├── Player.jsx │ │ ├── MediaPlayer.jsx │ │ ├── TimelineController.jsx │ │ ├── Playlist.jsx │ │ └── Controller.jsx │ ├── cards │ │ ├── Card.jsx │ │ └── Cards.jsx │ └── header │ │ └── Header.jsx ├── hooks │ ├── scrollToTop.js │ ├── useResize.js │ └── useDarkTheme.js ├── index.css ├── Providers │ ├── ThemeProvider.js │ ├── MainProvider.js │ └── ControlProvider.js ├── index.js ├── api │ └── fetchApi.js ├── errors │ └── Error.js ├── App.js ├── service-worker.js └── serviceWorkerRegistration.js ├── .gitignore ├── package.json ├── README.md └── .eslintcache /imgs/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3You7/holyquran/HEAD/imgs/1.png -------------------------------------------------------------------------------- /imgs/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3You7/holyquran/HEAD/imgs/2.png -------------------------------------------------------------------------------- /imgs/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3You7/holyquran/HEAD/imgs/3.png -------------------------------------------------------------------------------- /imgs/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3You7/holyquran/HEAD/imgs/4.png -------------------------------------------------------------------------------- /imgs/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3You7/holyquran/HEAD/imgs/5.png -------------------------------------------------------------------------------- /imgs/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3You7/holyquran/HEAD/imgs/6.png -------------------------------------------------------------------------------- /imgs/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3You7/holyquran/HEAD/imgs/7.png -------------------------------------------------------------------------------- /imgs/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3You7/holyquran/HEAD/imgs/8.png -------------------------------------------------------------------------------- /imgs/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3You7/holyquran/HEAD/imgs/9.png -------------------------------------------------------------------------------- /imgs/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3You7/holyquran/HEAD/imgs/10.png -------------------------------------------------------------------------------- /imgs/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3You7/holyquran/HEAD/imgs/11.png -------------------------------------------------------------------------------- /imgs/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3You7/holyquran/HEAD/imgs/12.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3You7/holyquran/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3You7/holyquran/HEAD/public/logo128.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3You7/holyquran/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/imgs/quran.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3You7/holyquran/HEAD/src/imgs/quran.jpg -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/fonts/Quicksand-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3You7/holyquran/HEAD/src/fonts/Quicksand-Regular.ttf -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | export { default as Home } from "./Home"; 2 | //TODO: I will add more routes here due to improving the app later in the futur; 3 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Header } from "./header/Header"; 2 | export { default as Cards } from "./cards/Cards"; 3 | export { default as MediaPlayer } from "./player/MediaPlayer"; 4 | export { default as TemporaryDrawer } from "./drawer/TemporaryDrawer"; 5 | -------------------------------------------------------------------------------- /src/hooks/scrollToTop.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useLocation } from "react-router-dom"; 3 | 4 | export default function ScrollToTop() { 5 | const { pathname } = useLocation(); 6 | 7 | useEffect(() => { 8 | window.scrollTo(0, 0); 9 | }, [pathname]); 10 | 11 | return null; 12 | } 13 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Quicksand"; 3 | src: local("Quicksand-Regular"), 4 | url(./fonts/Quicksand-Regular.ttf) format("truetype"); 5 | } 6 | 7 | /* * Reset Configs */ 8 | body, 9 | html { 10 | padding: 0; 11 | margin: 0; 12 | width: 100%; 13 | font-family: "Quicksand", sans-serif; 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "HolyQuran", 3 | "name": "HolyQuran", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo128.png", 12 | "type": "image/png", 13 | "sizes": "128x128" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "fullscreen", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/components/drawer/Switch.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { IconButton } from "@material-ui/core"; 3 | import { MuiThemeContext } from "../../Providers/ThemeProvider"; 4 | import { Brightness4Rounded, Brightness7Rounded } from "@material-ui/icons/"; 5 | 6 | export default function Switch() { 7 | const { theme, toggleTheme } = useContext(MuiThemeContext); 8 | const { 9 | palette: { type }, 10 | } = theme; 11 | 12 | return ( 13 | 14 | {type === "light" ? : } 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/Providers/ThemeProvider.js: -------------------------------------------------------------------------------- 1 | import React, { createContext } from "react"; 2 | import { createMuiTheme, ThemeProvider, CssBaseline } from "@material-ui/core"; 3 | import useDarkTheme from "../hooks/useDarkTheme"; 4 | 5 | export const MuiThemeContext = createContext(); 6 | 7 | const MuiThemeProvider = ({ children }) => { 8 | const [theme, toggleTheme] = useDarkTheme(); 9 | 10 | const currTheme = createMuiTheme(theme); 11 | 12 | return ( 13 | 14 | 15 | 16 | {children} 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default MuiThemeProvider; 23 | -------------------------------------------------------------------------------- /src/hooks/useResize.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | 3 | const getWidth = () => 4 | window.innerWidth || 5 | document.documentElement.clientWidth || 6 | document.body.clientWidth; 7 | 8 | const getHeight = () => 9 | window.innerHeight || 10 | document.documentElement.clientHeight || 11 | document.body.clientHeight; 12 | 13 | const useResize = () => { 14 | const [width, setWidth] = useState(getWidth()); 15 | const [height, setHeight] = useState(getHeight()); 16 | 17 | useEffect(() => { 18 | const handleResize = () => { 19 | setWidth(getWidth()); 20 | setHeight(getHeight()); 21 | }; 22 | 23 | window.addEventListener("resize", handleResize); 24 | 25 | return () => window.removeEventListener("resize", handleResize); 26 | }, []); 27 | 28 | return [width, height]; 29 | }; 30 | 31 | export default useResize; 32 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | import MainProvider from "./Providers/MainProvider"; 5 | 6 | import { HashRouter as Router } from "react-router-dom"; 7 | import ScrollToTop from "./hooks/scrollToTop"; 8 | 9 | import * as serviceWorkerRegistration from "./serviceWorkerRegistration"; 10 | 11 | import "./index.css"; 12 | 13 | ReactDOM.render( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | , 22 | document.getElementById("root") 23 | ); 24 | 25 | // If you want your app to work offline and load faster, you can change 26 | // unregister() to register() below. Note this comes with some pitfalls. 27 | // Learn more about service workers: https://cra.link/PWA 28 | serviceWorkerRegistration.register(); 29 | -------------------------------------------------------------------------------- /src/api/fetchApi.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const getAllReaders = "https://mp3quran.net/api/_english.php"; 4 | const getAllSuras = "https://unpkg.com/quran-json@1.0.1/json/surahs.json"; 5 | 6 | const fetchAllReaders = async () => { 7 | try { 8 | const response = await axios.get(getAllReaders); 9 | const result = await response.data.reciters; 10 | return result; 11 | } catch (e) { 12 | const message = e.response ? e.response.status : null || e.message; 13 | return message; 14 | } 15 | 16 | // axios 17 | // .get(getAllReaders) 18 | // .then((response) => { 19 | // const result = response.data.reciters; 20 | // return result; 21 | // }) 22 | // .catch((error) => { 23 | // throw new Error(); 24 | // }); 25 | }; 26 | 27 | const fetchAllSuras = async () => { 28 | try { 29 | const response = await axios.get(getAllSuras); 30 | const result = await response.data; 31 | return result; 32 | } catch (e) { 33 | console.log(e.message); 34 | } 35 | }; 36 | 37 | //fetchAllSuras(); 38 | 39 | export { fetchAllReaders, fetchAllSuras }; 40 | -------------------------------------------------------------------------------- /src/errors/Error.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button, Grid, Typography } from "@material-ui/core"; 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | 5 | const useStyles = makeStyles((theme) => ({ 6 | container: { 7 | width: "100%", 8 | height: "100vh", 9 | padding: theme.spacing(2), 10 | }, 11 | })); 12 | 13 | export default function NetworkError({ data }) { 14 | const classes = useStyles(); 15 | 16 | const networkError = () => ( 17 | <> 18 | 19 | There seems to be a problem with your connection 20 | 21 | 22 | ); 23 | 24 | const error = () => { 25 | return ( 26 | <> 27 | Something Went Wrong{" "} 28 | 29 | 30 | ); 31 | }; 32 | return ( 33 | 39 | 40 | Whooops 41 | {data === "Network Error" ? networkError() : error()} 42 | 43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MediaPlayer } from "./components"; 3 | import { Home } from "./routes"; 4 | import ControlProvider from "./Providers/ControlProvider"; 5 | import ThemeProvider from "./Providers/ThemeProvider"; 6 | import { Redirect, Route, Switch, useLocation } from "react-router-dom"; 7 | 8 | function App() { 9 | const location = useLocation(); 10 | const background = location.state && location.state.to; 11 | 12 | return ( 13 | <> 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ( 30 | 31 | 32 | 33 | )} 34 | /> 35 |
36 |
37 | 38 | ); 39 | } 40 | 41 | export default App; 42 | -------------------------------------------------------------------------------- /src/hooks/useDarkTheme.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | const initTheme = { 4 | props: { 5 | MuiLink: { 6 | underline: "none", 7 | color: "textPrimary", 8 | }, 9 | }, 10 | breakpoints: { 11 | values: { 12 | xs: 0, 13 | sm: 576, 14 | md: 768, 15 | lg: 992, 16 | xl: 1200, 17 | }, 18 | }, 19 | 20 | palette: { 21 | type: "dark", 22 | primary: { 23 | main: "#005036", 24 | light: "#3b8360", 25 | dark: "#002c10", 26 | }, 27 | }, 28 | 29 | typography: { 30 | fontFamily: '"Quicksand", sans-serif', 31 | }, 32 | }; 33 | 34 | const useDarkTheme = () => { 35 | const [theme, setTheme] = useState(initTheme); 36 | const { 37 | palette: { type }, 38 | } = theme; 39 | 40 | const toggleTheme = () => { 41 | const updatedTheme = { 42 | ...theme, 43 | palette: { 44 | ...theme.palette, 45 | type: type === "light" ? "dark" : "light", 46 | background: { 47 | default: type === "dark" ? "#ebeff1" : "#303030", 48 | }, 49 | }, 50 | }; 51 | 52 | setTheme(updatedTheme); 53 | }; 54 | 55 | return [theme, toggleTheme]; 56 | }; 57 | 58 | export default useDarkTheme; 59 | -------------------------------------------------------------------------------- /src/components/player/CurrReciter.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import ArtWork from "./ArtWork"; 3 | import { useHistory } from "react-router-dom"; 4 | import { Grid } from "@material-ui/core"; 5 | 6 | import { makeStyles } from "@material-ui/core/styles"; 7 | 8 | import { ControlContext } from "../../Providers/ControlProvider"; 9 | 10 | //TODO: Add favourite reciter functionality later 11 | 12 | const useStyles = makeStyles((theme) => ({ 13 | root: { 14 | cursor: "pointer", 15 | }, 16 | })); 17 | 18 | const CurrReciter = (props) => { 19 | const { dispatch } = useContext(ControlContext); 20 | const classes = useStyles(props); 21 | const history = useHistory(); 22 | 23 | const handleClick = () => { 24 | dispatch({ type: "SET_PLAYERSTATE", payload: "expanded" }); 25 | history.push({ 26 | pathname: "/player", 27 | }); 28 | }; 29 | return ( 30 | 37 | 38 | 39 | 40 | 41 | ); 42 | }; 43 | 44 | export default CurrReciter; 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quran-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "https://th3you7.github.io/holyquran", 6 | "dependencies": { 7 | "@material-ui/core": "^4.11.3", 8 | "@material-ui/icons": "^4.11.2", 9 | "@material-ui/lab": "^4.0.0-alpha.57", 10 | "axios": "^0.21.1", 11 | "memoize-one": "^5.1.1", 12 | "react": "^17.0.1", 13 | "react-dom": "^17.0.1", 14 | "react-router-dom": "^5.2.0", 15 | "react-scripts": "4.0.1", 16 | "react-virtualized-auto-sizer": "^1.0.4", 17 | "react-window": "^1.8.6", 18 | "styled-components": "^5.2.1" 19 | }, 20 | "scripts": { 21 | "predeploy": "npm run build", 22 | "deploy": "gh-pages -d build", 23 | "start": "react-scripts start", 24 | "build": "react-scripts build", 25 | "test": "react-scripts test", 26 | "eject": "react-scripts eject" 27 | }, 28 | "eslintConfig": { 29 | "extends": [ 30 | "react-app", 31 | "react-app/jest" 32 | ] 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "devDependencies": { 47 | "gh-pages": "^3.1.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Providers/MainProvider.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { fetchAllReaders, fetchAllSuras } from "../api/fetchApi"; 3 | 4 | export const mainContext = React.createContext(); 5 | 6 | const MainProvider = ({ children }) => { 7 | const initCurrReciter = { 8 | id: null, 9 | name: "", 10 | suras: [], 11 | server: null, 12 | rewaya: "", 13 | count: "", 14 | }; 15 | 16 | const initCurrSura = { 17 | number: null, 18 | name: "", 19 | translate: "", 20 | index: null, 21 | }; 22 | 23 | //* fetch all needed data 24 | const [data, setData] = useState(null); 25 | const [surasNames, setSurasNames] = useState([]); 26 | // 27 | const [currReciter, setCurrReciter] = useState(initCurrReciter); 28 | const [currSura, setCurrSura] = useState(initCurrSura); 29 | 30 | useEffect(() => { 31 | const fetching = async () => setData(await fetchAllReaders()); 32 | fetching(); 33 | }, []); 34 | 35 | useEffect(() => { 36 | const fetching = async () => setSurasNames(await fetchAllSuras()); 37 | fetching(); 38 | }, []); 39 | 40 | return ( 41 | 52 | {children} 53 | 54 | ); 55 | }; 56 | 57 | export default MainProvider; 58 | -------------------------------------------------------------------------------- /src/routes/Home.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from "react"; 2 | import { Box } from "@material-ui/core"; 3 | 4 | import Error from "../errors/Error"; 5 | import { Header, Cards, TemporaryDrawer } from "../components"; 6 | import { mainContext } from "../Providers/MainProvider"; 7 | 8 | const Home = () => { 9 | const { data } = useContext(mainContext); 10 | const [state, setState] = useState(false); 11 | const [input, setInput] = useState(""); 12 | 13 | document.body.style.overflowY = "auto"; 14 | document.body.style.overflowX = "hidden"; 15 | 16 | //*handling changes to filter reciters 17 | const handleChange = (e) => { 18 | setInput(e.target.value); 19 | }; 20 | 21 | // 22 | const toggleDrawer = (event) => { 23 | if ( 24 | event && 25 | event.type === "keydown" && 26 | (event.key === "Tab" || event.key === "Shift") 27 | ) { 28 | return; 29 | } 30 | 31 | setState(!state); 32 | }; 33 | 34 | return ( 35 | 36 | 37 |
42 | {data === "Network Error" || typeof data === "number" ? ( 43 | 44 | ) : ( 45 | 46 | )} 47 | 48 | ); 49 | }; 50 | 51 | export default Home; 52 | -------------------------------------------------------------------------------- /src/components/player/MinimizedPlayer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import Controller from "./Controller"; 3 | import CurrReciter from "./CurrReciter"; 4 | import VolumeController from "./VolumeController"; 5 | import { Grid } from "@material-ui/core"; 6 | import { makeStyles } from "@material-ui/core/styles"; 7 | import TimelineController from "./TimelineController"; 8 | import { ControlContext } from "../../Providers/ControlProvider"; 9 | 10 | const useStyles = makeStyles((theme) => ({ 11 | container: { 12 | padding: theme.spacing(1), 13 | backgroundColor: theme.palette.background.paper, 14 | boxShadow: theme.shadows[7], 15 | width: "100%", 16 | }, 17 | })); 18 | 19 | const MinimizedPlayer = () => { 20 | const { width } = useContext(ControlContext); 21 | const classes = useStyles(); 22 | 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | {width <= 576 ? ( 30 | 31 | ) : ( 32 | 33 | )} 34 | 35 | {width >= 768 ? ( 36 | 37 | 38 | 39 | ) : null} 40 | 41 | 42 | {width <= 576 ? ( 43 | 44 | ) : ( 45 | 46 | )} 47 | 48 | 49 | ); 50 | }; 51 | 52 | export default MinimizedPlayer; 53 | -------------------------------------------------------------------------------- /src/components/player/VolumeController.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Volume from "./Volume"; 3 | import { Grid, IconButton, Menu, MenuItem } from "@material-ui/core"; 4 | import { VolumeUpRounded } from "@material-ui/icons"; 5 | import { makeStyles } from "@material-ui/core/styles"; 6 | 7 | const useStyles = makeStyles(() => ({ 8 | container: { 9 | flex: "0 1 auto", 10 | }, 11 | 12 | icon: { 13 | fontSize: (props) => (props.ultraMinimized ? "24px" : "32px"), 14 | }, 15 | })); 16 | 17 | const VolumeController = (props) => { 18 | const { maximized } = props; 19 | const classes = useStyles(props); 20 | 21 | const [anchorEl, setAnchorEl] = React.useState(null); 22 | 23 | const handleClick = (event) => { 24 | setAnchorEl(event.currentTarget); 25 | event.stopPropagation(); 26 | }; 27 | 28 | const handleClose = (e) => { 29 | setAnchorEl(null); 30 | e.stopPropagation(); 31 | }; 32 | 33 | return ( 34 | 35 | 36 | 42 | 43 | 44 | 45 | 52 | 53 | 54 | 55 | 56 | 57 | ); 58 | }; 59 | 60 | export default VolumeController; 61 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | HolyQuran 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/player/MaximizedPlayer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import Player from "./Player"; 3 | import Playlist from "./Playlist"; 4 | import { Grid } from "@material-ui/core"; 5 | import { makeStyles } from "@material-ui/core/styles"; 6 | import { ControlContext } from "../../Providers/ControlProvider"; 7 | 8 | const useStyles = makeStyles((theme) => ({ 9 | root: { 10 | padding: 0, 11 | width: "100%", 12 | height: "100vh", 13 | // [theme.breakpoints.up("md")]: { 14 | // padding: theme.spacing(2), 15 | // }, 16 | // [theme.breakpoints.up("lg")]: { 17 | // padding: theme.spacing(4), 18 | // }, 19 | }, 20 | 21 | player: { 22 | height: "100%", 23 | flex: "0 1 100%", 24 | // [theme.breakpoints.up("md")]: { 25 | // flex: "0 1 70%", 26 | // padding: theme.spacing(4), 27 | // }, 28 | // [theme.breakpoints.up("lg")]: { 29 | // flex: "0 1 50%", 30 | // }, 31 | }, 32 | 33 | playlist: { 34 | height: "100%", 35 | flex: "0 1 100%", 36 | [theme.breakpoints.up("md")]: { 37 | flex: "0 1 70%", 38 | padding: theme.spacing(4), 39 | }, 40 | }, 41 | })); 42 | 43 | export default function MaximizedPlayer() { 44 | const classes = useStyles(); 45 | const { 46 | state: { playerState }, 47 | } = useContext(ControlContext); 48 | 49 | document.body.style.overflow = "hidden"; 50 | 51 | if (playerState === "playlist") { 52 | document.body.style.transform = "translateY(-100vh)"; 53 | document.body.style.transition = "all .3s ease"; 54 | } else { 55 | document.body.style.transform = "none"; 56 | //document.body.style.transition = "all .5s ease"; 57 | } 58 | 59 | return ( 60 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /src/components/cards/Card.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { Paper, Link, Typography } from "@material-ui/core"; 3 | import { Link as RouterLink } from "react-router-dom"; 4 | import { makeStyles } from "@material-ui/core/styles"; 5 | import { mainContext } from "../../Providers/MainProvider"; 6 | import { ControlContext } from "../../Providers/ControlProvider"; 7 | 8 | const useStyles = makeStyles((theme) => ({ 9 | paper: { 10 | padding: theme.spacing(1), 11 | }, 12 | 13 | typography: { 14 | color: theme.palette.text.primary, 15 | 16 | "&:hover": { 17 | color: theme.palette.warning.main, 18 | }, 19 | }, 20 | })); 21 | 22 | export default function Card({ id, name, suras, server, rewaya, count }) { 23 | const { surasNames, setCurrSura, currReciter, setCurrReciter } = useContext( 24 | mainContext 25 | ); 26 | const { dispatch } = useContext(ControlContext); 27 | const classes = useStyles(); 28 | 29 | const handleClick = () => { 30 | //* Defining the selected reciter 31 | setCurrReciter({ 32 | ...currReciter, 33 | id, 34 | name, 35 | suras, 36 | server, 37 | rewaya, 38 | count, 39 | }); 40 | 41 | //* setting the first available sura in the selected reciter's list of suras 42 | 43 | const allSurasIndex = suras.split(","); 44 | const { number, transliteration_en, translation_en } = surasNames.find( 45 | (sura) => sura.number === Number(allSurasIndex[0]) 46 | ); 47 | 48 | setCurrSura({ 49 | number, 50 | name: transliteration_en, 51 | translate: translation_en, 52 | index: 0, 53 | }); 54 | 55 | dispatch({ type: "SET_PLAYERSTATE", payload: "expanded" }); 56 | }; 57 | 58 | const link = { 59 | pathname: "/player", 60 | state: { 61 | from: { 62 | pathname: "/home", 63 | }, 64 | to: { 65 | pathname: "/player", 66 | }, 67 | }, 68 | }; 69 | 70 | return ( 71 | 72 | 73 | 74 | {name.indexOf("(") > 0 ? name.slice(0, name.indexOf("(")) : name} 75 | 76 | 77 | 78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /src/components/player/Volume.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect } from "react"; 2 | import { makeStyles, withStyles } from "@material-ui/core/styles"; 3 | import { Grid, Slider, IconButton } from "@material-ui/core/"; 4 | import { VolumeDown, VolumeOff, VolumeUp } from "@material-ui/icons/"; 5 | import { ControlContext } from "../../Providers/ControlProvider"; 6 | 7 | const PrettoSlider = withStyles({ 8 | root: { 9 | color: "#005036", 10 | }, 11 | thumb: { 12 | height: 16, 13 | width: 16, 14 | backgroundColor: "#fff", 15 | border: "4px solid currentColor", 16 | 17 | marginTop: -6, 18 | marginLeft: -8, 19 | "&:focus, &:hover, &$active": { 20 | boxShadow: "inherit", 21 | }, 22 | }, 23 | active: {}, 24 | 25 | track: { 26 | height: 4, 27 | borderRadius: 4, 28 | }, 29 | rail: { 30 | height: 4, 31 | borderRadius: 4, 32 | }, 33 | })(Slider); 34 | 35 | const useStyles = makeStyles({ 36 | root: { 37 | width: 120, 38 | }, 39 | }); 40 | 41 | export default function ContinuousSlider() { 42 | const { player, volume, volDispatch } = useContext(ControlContext); 43 | 44 | const { volState, currVol } = volume; 45 | 46 | useEffect(() => { 47 | if (player) { 48 | !volState ? (player.muted = true) : (player.muted = false); 49 | } 50 | 51 | player.volume = currVol / 100; 52 | }, [player, volState, currVol]); 53 | 54 | //* handling volume's slider change 55 | const handleChange = (e, newValue) => { 56 | volDispatch({ type: "SET_CURRVOL", payload: newValue }); 57 | e.stopPropagation(); 58 | }; 59 | 60 | const classes = useStyles(); 61 | 62 | return ( 63 |
64 | 65 | 66 | { 68 | e.stopPropagation(); 69 | volDispatch({ type: "SET_VOLSTATE" }); 70 | }} 71 | > 72 | {!volState || currVol === 0 ? ( 73 | 74 | ) : currVol > 50 ? ( 75 | 76 | ) : ( 77 | 78 | )} 79 | 80 | 81 | 82 | 83 | 84 | 85 |
86 | ); 87 | } 88 | -------------------------------------------------------------------------------- /src/components/player/ArtWork.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { Grid, Avatar, Typography } from "@material-ui/core"; 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | import img from "../../imgs/quran.jpg"; 5 | import { mainContext } from "../../Providers/MainProvider"; 6 | import { ControlContext } from "../../Providers/ControlProvider"; 7 | 8 | const useStyles = makeStyles((theme) => ({ 9 | avatar: { 10 | width: (props) => (props.maximized ? theme.spacing(30) : theme.spacing(6)), 11 | height: (props) => (props.maximized ? theme.spacing(26) : theme.spacing(6)), 12 | marginRight: (props) => (!props.maximized ? theme.spacing(1.5) : 0), 13 | marginBottom: (props) => (props.maximized ? theme.spacing(3) : 0), 14 | }, 15 | 16 | title: { 17 | fontSize: (props) => 18 | props.maximized ? theme.spacing(3) : theme.spacing(2), 19 | fontWeight: 600, 20 | lineHeight: 1.25, 21 | }, 22 | 23 | subTitle: { 24 | fontSize: (props) => 25 | props.maximized ? theme.spacing(2) : theme.spacing(1.65), 26 | fontWeight: 400, 27 | }, 28 | })); 29 | 30 | const ArtWork = (props) => { 31 | const { currReciter, currSura } = useContext(mainContext); 32 | const { width } = useContext(ControlContext); 33 | 34 | const { maximized, minimized } = props; 35 | const classes = useStyles(props); 36 | 37 | return ( 38 | 44 | {width < 576 && minimized ? null : ( 45 | 46 | 51 | 52 | )} 53 | 59 | 60 | 61 | {currReciter.name} 62 | 63 | 64 | 65 | 66 | {currSura.name} 67 | 68 | 69 | 70 | 71 | ); 72 | }; 73 | 74 | export default ArtWork; 75 | -------------------------------------------------------------------------------- /src/components/drawer/TemporaryDrawer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Switch from "./Switch"; 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | import { 5 | Drawer, 6 | List, 7 | ListItem, 8 | ListItemIcon, 9 | ListItemText, 10 | Divider, 11 | } from "@material-ui/core"; 12 | import { LocalLibrary, Favorite, Radio, GitHub } from "@material-ui/icons/"; 13 | 14 | const listItems = [ 15 | { 16 | text: "All Readers", 17 | icon: , 18 | }, 19 | { 20 | text: "Favorite ", 21 | icon: , 22 | }, 23 | { 24 | text: "Live Radio", 25 | icon: , 26 | }, 27 | { 28 | text: "Github", 29 | icon: , 30 | }, 31 | ]; 32 | const useStyles = makeStyles({ 33 | list: { 34 | width: 250, 35 | }, 36 | 37 | container: { 38 | margin: ".3rem", 39 | }, 40 | }); 41 | 42 | export default function TemporaryDrawer({ toggleDrawer, state }) { 43 | const classes = useStyles(); 44 | 45 | const list = () => ( 46 |
47 | 48 |
49 | 50 |
51 | 52 | {listItems.map((item) => 53 | item.text === "Github" ? ( 54 | toggleDrawer(e)} 59 | onKeyDown={(e) => toggleDrawer(e)} 60 | target="_blank" 61 | href="https://www.github.com/Th3You7/HolyQuran-App" 62 | > 63 | {item.icon} 64 | 65 | 66 | ) : ( 67 | toggleDrawer(e)} 71 | onKeyDown={(e) => toggleDrawer(e)} 72 | > 73 | {item.icon} 74 | 75 | 76 | ) 77 | )} 78 |
79 | 80 |
81 | ); 82 | 83 | return ( 84 |
85 | <> 86 | toggleDrawer(e)}> 87 | {list()} 88 | 89 | 90 |
91 | ); 92 | } 93 | -------------------------------------------------------------------------------- /src/components/player/CurrSura.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { Grid, Box, Typography, IconButton } from "@material-ui/core"; 3 | import { PlayArrowRounded, StopRounded } from "@material-ui/icons"; 4 | 5 | import { makeStyles } from "@material-ui/core/styles"; 6 | import { mainContext } from "../../Providers/MainProvider"; 7 | 8 | const useStyles = makeStyles((theme) => ({ 9 | root: { 10 | flex: "0 1 90%", 11 | padding: theme.spacing(1.5), 12 | backgroundColor: theme.palette.background.paper, 13 | borderRadius: "4px", 14 | [theme.breakpoints.up("md")]: { 15 | flex: "0 1 98%", 16 | }, 17 | }, 18 | 19 | title: { color: theme.palette.text.primary, fontWeight: 600 }, 20 | 21 | subtitle: { 22 | color: "grey", 23 | textTransform: "uppercase", 24 | fontSize: theme.spacing(1.5), 25 | }, 26 | 27 | index: { 28 | height: theme.spacing(4), 29 | width: theme.spacing(4), 30 | display: "flex", 31 | fontWeight: 600, 32 | alignItems: "center", 33 | justifyContent: "center", 34 | backgroundColor: theme.palette.primary.main, 35 | color: theme.palette.primary.contrastText, 36 | borderRadius: "50%", 37 | }, 38 | 39 | playPause: { 40 | color: theme.palette.text.primary, 41 | fontSize: 24, 42 | }, 43 | })); 44 | 45 | function CurrSura({ index, translate, name, handleClick, allSurasIndex }) { 46 | const classes = useStyles(); 47 | const { 48 | currSura: { number }, 49 | } = useContext(mainContext); 50 | 51 | return ( 52 | 53 | 54 | {index + 1} 55 | 56 | 57 | 58 | {name} 59 | 60 | 61 | {translate} 62 | 63 | 64 | 65 | 66 | {Number(allSurasIndex[index]) === number ? ( 67 | 68 | ) : ( 69 | 70 | )} 71 | 72 | 73 | 74 | ); 75 | } 76 | 77 | export default CurrSura; 78 | -------------------------------------------------------------------------------- /src/components/cards/Cards.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import Card from "./Card"; 3 | import { Grid } from "@material-ui/core"; 4 | import Skeleton from "@material-ui/lab/Skeleton"; 5 | import { makeStyles } from "@material-ui/core/styles"; 6 | import { mainContext } from "../../Providers/MainProvider"; 7 | 8 | const useStyles = makeStyles((theme) => ({ 9 | root: { 10 | padding: "85px 30px 60px ", 11 | [theme.breakpoints.up("sm")]: { 12 | padding: "80px 40px ", 13 | }, 14 | }, 15 | })); 16 | 17 | const Cards = ({ input }) => { 18 | const { data } = useContext(mainContext); 19 | const classes = useStyles(); 20 | 21 | //* skeletons 22 | if (!data) { 23 | return ( 24 | 25 | {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((n) => ( 26 | 27 | 28 | 29 | ))} 30 | 31 | ); 32 | } 33 | 34 | //*if you enter a name in the search bar it will render the filtered names 35 | if (input) { 36 | return ( 37 | 38 | {data 39 | .filter((reciter) => 40 | reciter.name.toLowerCase().includes(input.toLowerCase()) 41 | ) 42 | .map((filteredReciter) => ( 43 | 44 | 52 | 53 | ))} 54 | 55 | ); 56 | } 57 | 58 | //*else it will render all available reciters 59 | if (data && !input) { 60 | return ( 61 | 62 | {data.map((item) => ( 63 | 64 | 72 | 73 | ))} 74 | 75 | ); 76 | } 77 | }; 78 | 79 | export default Cards; 80 | -------------------------------------------------------------------------------- /src/service-worker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-globals */ 2 | 3 | // This service worker can be customized! 4 | // See https://developers.google.com/web/tools/workbox/modules 5 | // for the list of available Workbox modules, or add any other 6 | // code you'd like. 7 | // You can also remove this file if you'd prefer not to use a 8 | // service worker, and the Workbox build step will be skipped. 9 | 10 | import { clientsClaim } from "workbox-core"; 11 | import { ExpirationPlugin } from "workbox-expiration"; 12 | import { precacheAndRoute, createHandlerBoundToURL } from "workbox-precaching"; 13 | import { registerRoute } from "workbox-routing"; 14 | import { StaleWhileRevalidate } from "workbox-strategies"; 15 | 16 | clientsClaim(); 17 | 18 | // Precache all of the assets generated by your build process. 19 | // Their URLs are injected into the manifest variable below. 20 | // This variable must be present somewhere in your service worker file, 21 | // even if you decide not to use precaching. See https://cra.link/PWA 22 | precacheAndRoute(self.__WB_MANIFEST); 23 | 24 | // Set up App Shell-style routing, so that all navigation requests 25 | // are fulfilled with your index.html shell. Learn more at 26 | // https://developers.google.com/web/fundamentals/architecture/app-shell 27 | const fileExtensionRegexp = new RegExp("/[^/?]+\\.[^/]+$"); 28 | registerRoute( 29 | // Return false to exempt requests from being fulfilled by index.html. 30 | ({ request, url }) => { 31 | // If this isn't a navigation, skip. 32 | if (request.mode !== "navigate") { 33 | return false; 34 | } // If this is a URL that starts with /_, skip. 35 | 36 | if (url.pathname.startsWith("/_")) { 37 | return false; 38 | } // If this looks like a URL for a resource, because it contains // a file extension, skip. 39 | 40 | if (url.pathname.match(fileExtensionRegexp)) { 41 | return false; 42 | } // Return true to signal that we want to use the handler. 43 | 44 | return true; 45 | }, 46 | createHandlerBoundToURL(process.env.PUBLIC_URL + "/index.html") 47 | ); 48 | 49 | // An example runtime caching route for requests that aren't handled by the 50 | // precache, in this case same-origin .png requests like those from in public/ 51 | registerRoute( 52 | // Add in any other file extensions or routing criteria as needed. 53 | ({ url }) => 54 | url.origin === self.location.origin && url.pathname.endsWith(".png"), // Customize this strategy as needed, e.g., by changing to CacheFirst. 55 | new StaleWhileRevalidate({ 56 | cacheName: "images", 57 | plugins: [ 58 | // Ensure that once this runtime cache reaches a maximum size the 59 | // least-recently used images are removed. 60 | new ExpirationPlugin({ maxEntries: 50 }), 61 | ], 62 | }) 63 | ); 64 | 65 | // This allows the web app to trigger skipWaiting via 66 | // registration.waiting.postMessage({type: 'SKIP_WAITING'}) 67 | self.addEventListener("message", (event) => { 68 | if (event.data && event.data.type === "SKIP_WAITING") { 69 | self.skipWaiting(); 70 | } 71 | }); 72 | 73 | // Any other custom service worker logic can go here. 74 | -------------------------------------------------------------------------------- /src/Providers/ControlProvider.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useReducer, useState } from "react"; 2 | import useResize from "../hooks/useResize"; 3 | 4 | export const ControlContext = createContext(); 5 | 6 | const ControlProvider = ({ children }) => { 7 | //get the width & height of device 8 | const [width, height] = useResize(); 9 | //initialize player using ref 10 | const [player, setPlayer] = useState(undefined); 11 | 12 | const initControlState = { 13 | //* there will be 3 states of playerState: playlist, reduced, expanded 14 | playerState: "expanded", 15 | isPlaying: false, 16 | isRepeated: false, 17 | isLoading: false, 18 | isLoaded: false, 19 | isSeeking: false, 20 | isSeeked: false, 21 | //isMinimized: false, 22 | }; 23 | 24 | const controlReducer = (state, action) => { 25 | switch (action.type) { 26 | case "SET_PLAYERSTATE": 27 | return { 28 | ...state, 29 | playerState: action.payload, 30 | }; 31 | 32 | case "SET_ISPLAYING": 33 | return { 34 | ...state, 35 | isPlaying: action.payload, 36 | }; 37 | 38 | case "SET_REPEAT": 39 | return { 40 | ...state, 41 | isRepeated: !state.isRepeated, 42 | }; 43 | case "SET_ISLOADING": 44 | return { 45 | ...state, 46 | isLoading: true, 47 | isLoaded: false, 48 | }; 49 | 50 | case "SET_ISLOADED": 51 | return { 52 | ...state, 53 | isLoading: false, 54 | isLoaded: !(true && action.payload), 55 | }; 56 | 57 | case "SET_ISSEEKING": 58 | return { 59 | ...state, 60 | isSeeking: true, 61 | isSeeked: false, 62 | }; 63 | 64 | case "SET_ISSEEKED": 65 | return { 66 | ...state, 67 | isSeeking: false, 68 | isSeeked: !(true && action.payload), 69 | }; 70 | default: 71 | return; 72 | } 73 | }; 74 | 75 | const initVolState = { 76 | //* it will toggle between 2 states: false for muted & true for unmuted 77 | volState: true, 78 | currVol: 50, 79 | }; 80 | 81 | const volReducer = (state, action) => { 82 | switch (action.type) { 83 | case "SET_VOLSTATE": 84 | return { 85 | ...state, 86 | volState: !state.volState, 87 | }; 88 | 89 | case "SET_CURRVOL": { 90 | return { 91 | ...state, 92 | volState: true, 93 | currVol: action.payload, 94 | }; 95 | } 96 | 97 | default: 98 | return; 99 | } 100 | }; 101 | 102 | const [volume, volDispatch] = useReducer(volReducer, initVolState); 103 | const [state, dispatch] = useReducer(controlReducer, initControlState); 104 | 105 | return ( 106 | 118 | {children} 119 | 120 | ); 121 | }; 122 | 123 | export default ControlProvider; 124 | -------------------------------------------------------------------------------- /src/components/header/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | AppBar, 4 | Toolbar, 5 | IconButton, 6 | Typography, 7 | InputBase, 8 | } from "@material-ui/core/"; 9 | import { fade, makeStyles } from "@material-ui/core/styles"; 10 | import { Menu, Search } from "@material-ui/icons/"; 11 | 12 | const useStyles = makeStyles((theme) => ({ 13 | root: { 14 | flexGrow: 1, 15 | }, 16 | 17 | appBar: { 18 | paddingRight: theme.spacing(5), 19 | top: 0, 20 | bottom: "auto", 21 | }, 22 | menuButton: { 23 | marginRight: theme.spacing(2), 24 | }, 25 | title: { 26 | flexGrow: 1, 27 | textAlign: "center", 28 | display: "none", 29 | [theme.breakpoints.up("sm")]: { 30 | display: "block", 31 | }, 32 | }, 33 | 34 | search: { 35 | position: "relative", 36 | borderRadius: theme.shape.borderRadius, 37 | backgroundColor: fade(theme.palette.common.white, 0.15), 38 | "&:hover": { 39 | backgroundColor: fade(theme.palette.common.white, 0.25), 40 | }, 41 | marginLeft: 0, 42 | width: "100%", 43 | [theme.breakpoints.up("sm")]: { 44 | marginLeft: theme.spacing(1), 45 | width: "auto", 46 | }, 47 | }, 48 | searchIcon: { 49 | padding: theme.spacing(0, 2), 50 | height: "100%", 51 | position: "absolute", 52 | pointerEvents: "none", 53 | display: "flex", 54 | alignItems: "center", 55 | justifyContent: "center", 56 | }, 57 | inputRoot: { 58 | color: "inherit", 59 | }, 60 | inputInput: { 61 | padding: theme.spacing(1, 1, 1, 0), 62 | // vertical padding + font size from searchIcon 63 | paddingLeft: `calc(1em + ${theme.spacing(4)}px)`, 64 | transition: theme.transitions.create("width"), 65 | width: "100%", 66 | [theme.breakpoints.up("sm")]: { 67 | width: "12ch", 68 | "&:focus": { 69 | width: "20ch", 70 | }, 71 | }, 72 | }, 73 | })); 74 | 75 | export default function Header({ toggleDrawer, input, handleChange }) { 76 | const classes = useStyles(); 77 | 78 | return ( 79 |
80 | 81 | 82 | toggleDrawer(e)} 88 | > 89 | 90 | 91 | 92 | HolyQuran-App 93 | 94 |
95 |
96 | 97 |
98 | 108 |
109 | 110 | 111 |
112 | ); 113 | } 114 | -------------------------------------------------------------------------------- /src/components/player/Player.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import ArtWork from "./ArtWork"; 3 | import TimelineController from "./TimelineController"; 4 | import VolumeController from "./VolumeController"; 5 | import Controller from "./Controller"; 6 | 7 | import { Grid, IconButton } from "@material-ui/core"; 8 | import { makeStyles } from "@material-ui/core/styles"; 9 | import { 10 | KeyboardArrowDownOutlined, 11 | ListRounded, 12 | RepeatOneRounded, 13 | RepeatRounded, 14 | } from "@material-ui/icons/"; 15 | 16 | import { ControlContext } from "../../Providers/ControlProvider"; 17 | import { useHistory } from "react-router-dom"; 18 | 19 | const useStyles = makeStyles((theme) => ({ 20 | root: { 21 | backgroundColor: theme.palette.background.paper, 22 | color: theme.palette.text.primary, 23 | padding: `8px 24px`, 24 | [theme.breakpoints.up("lg")]: { 25 | padding: `16px 80px`, 26 | }, 27 | height: "100%", 28 | borderRadius: "4px", 29 | }, 30 | 31 | icon: { 32 | fontSize: "32px", 33 | }, 34 | })); 35 | 36 | const Player = () => { 37 | const classes = useStyles(); 38 | 39 | const { 40 | dispatch, 41 | state: { isRepeated }, 42 | } = useContext(ControlContext); 43 | 44 | const history = useHistory(); 45 | 46 | const handleReduce = () => { 47 | dispatch({ type: "SET_PLAYERSTATE", payload: "reduced" }); 48 | 49 | history.push({ 50 | pathname: "/home", 51 | state: { 52 | from: { 53 | pathname: "/player", 54 | }, 55 | to: { 56 | pathname: "/home", 57 | }, 58 | }, 59 | }); 60 | }; 61 | 62 | const handlePlaylist = () => { 63 | dispatch({ type: "SET_PLAYERSTATE", payload: "playlist" }); 64 | }; 65 | 66 | const handleRepeat = () => { 67 | dispatch({ type: "SET_REPEAT" }); 68 | }; 69 | 70 | return ( 71 | 72 | 79 | 80 | 81 | 82 | 83 | 84 | {/* {width < 768 ? ( 85 | 86 | 87 | 88 | 89 | 90 | ) : null} */} 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 111 | 112 | 113 | {isRepeated ? : } 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | ); 125 | }; 126 | 127 | export default Player; 128 | -------------------------------------------------------------------------------- /src/components/player/MediaPlayer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useRef, useCallback, useEffect } from "react"; 2 | import { useLocation } from "react-router-dom"; 3 | import { mainContext } from "../../Providers/MainProvider"; 4 | import { ControlContext } from "../../Providers/ControlProvider"; 5 | import MaximizedPlayer from "./MaximizedPlayer"; 6 | import MinimizedPlayer from "./MinimizedPlayer"; 7 | import { Box } from "@material-ui/core"; 8 | 9 | export default function MediaPlayer() { 10 | const { 11 | setPlayer, 12 | dispatch, 13 | state: { playerState, isPlaying, isRepeated, isSeeking, isSeeked }, 14 | } = useContext(ControlContext); 15 | 16 | const { currReciter, currSura, setCurrSura, surasNames } = useContext( 17 | mainContext 18 | ); 19 | 20 | const location = useLocation(); 21 | 22 | const { server, suras } = currReciter; 23 | const { number, index } = currSura; 24 | 25 | const audioPlayer = useRef(); 26 | 27 | //TODO: formatting src of audio 28 | let src; 29 | if (Number(number) < 10) { 30 | src = `${server}/00${number}.mp3`; 31 | } else if (Number(number) >= 100) { 32 | src = `${server}/${number}.mp3`; 33 | } else { 34 | src = `${server}/0${number}.mp3`; 35 | } 36 | 37 | //* Handling Src changin 38 | const setSrc = useCallback(() => { 39 | if (number && server) { 40 | audioPlayer.current.src = src; 41 | } 42 | }, [src, number, server]); 43 | 44 | //*handling play/pause functionality 45 | const playPauseAudio = useCallback(() => { 46 | if (!isPlaying) { 47 | audioPlayer.current.pause(); 48 | } else { 49 | audioPlayer.current.play(); 50 | } 51 | }, [isPlaying]); 52 | 53 | //* handling audio player if ended 54 | const handleEnd = () => { 55 | const allSurasIndex = suras.split(",").map((n) => Number(n)); 56 | 57 | //*NOTE: if repeated selected it will replay itself; else, it will end 58 | if (isRepeated) { 59 | audioPlayer.current.play(); 60 | audioPlayer.current.currentTime = 0; 61 | } else if (!allSurasIndex[index + 1]) { 62 | dispatch({ type: "SET_ISPLAYING", payload: false }); 63 | } else { 64 | const { number, transliteration_en } = surasNames.find( 65 | (x) => x.number === allSurasIndex[index + 1] 66 | ); 67 | setCurrSura({ 68 | index: index + 1, 69 | name: transliteration_en, 70 | number, 71 | }); 72 | } 73 | }; 74 | 75 | //*handling loading data 76 | const handleLoading = () => { 77 | dispatch({ type: "SET_ISLOADING" }); 78 | //console.log(isLoaded, isLoading); 79 | }; 80 | //*handling loaded data 81 | const handleLoaded = () => { 82 | dispatch({ type: "SET_ISLOADED" }); 83 | 84 | dispatch({ type: "SET_ISPLAYING", payload: true }); 85 | }; 86 | //*handling seeking 87 | const handleSeeking = () => { 88 | dispatch({ type: "SET_ISSEEKING" }); 89 | console.log(isSeeked, isSeeking); 90 | }; 91 | //*handling seeked 92 | const handleSeeked = () => { 93 | dispatch({ type: "SET_ISSEEKED" }); 94 | console.log(isSeeked, isSeeking); 95 | }; 96 | 97 | //*Setting src of audio, and player 98 | useEffect(() => { 99 | setPlayer(audioPlayer.current); 100 | setSrc(); 101 | }, [setSrc, setPlayer]); 102 | 103 | /* 104 | *play pause functionality 105 | */ 106 | useEffect(() => { 107 | if (number && server) { 108 | playPauseAudio(); 109 | } 110 | }, [number, server, playPauseAudio]); 111 | 112 | return ( 113 | 121 | {playerState === "reduced" && location.pathname === "/home" ? ( 122 | 123 | ) : location.pathname === "/player" ? ( 124 | 125 | ) : null} 126 | 127 | 136 | ); 137 | } 138 | -------------------------------------------------------------------------------- /src/components/player/TimelineController.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useReducer, useEffect, useCallback } from "react"; 2 | import { Grid, Typography, Slider } from "@material-ui/core"; 3 | import { makeStyles, withStyles } from "@material-ui/core/styles"; 4 | import { ControlContext } from "../../Providers/ControlProvider"; 5 | 6 | const PrettoSlider = withStyles({ 7 | root: { 8 | color: "#005036", 9 | }, 10 | thumb: { 11 | height: 16, 12 | width: 16, 13 | backgroundColor: "#fff", 14 | border: "4px solid currentColor", 15 | 16 | marginTop: -4, 17 | marginLeft: -8, 18 | "&:focus, &:hover, &$active": { 19 | boxShadow: "inherit", 20 | }, 21 | }, 22 | active: {}, 23 | 24 | track: { 25 | height: 8, 26 | borderRadius: 4, 27 | }, 28 | rail: { 29 | height: 8, 30 | borderRadius: 4, 31 | }, 32 | })(Slider); 33 | 34 | const useStyles = makeStyles(() => ({ 35 | container: { 36 | flex: "0 1 45%", 37 | }, 38 | 39 | slider: { 40 | flexGrow: 2, 41 | }, 42 | })); 43 | 44 | const TimelineController = () => { 45 | const classes = useStyles(); 46 | 47 | const { 48 | player, 49 | state: { isPlaying, isLoading }, 50 | } = useContext(ControlContext); 51 | 52 | const initTimeState = { 53 | currTime: 0, 54 | duration: 0, 55 | }; 56 | 57 | const timeReducer = (state, action) => { 58 | switch (action.type) { 59 | case "SET_CURRTIME": 60 | return { 61 | ...state, 62 | currTime: action.payload, 63 | }; 64 | 65 | case "SET_DURATION": 66 | return { 67 | ...state, 68 | duration: action.payload, 69 | }; 70 | 71 | default: 72 | return; 73 | } 74 | }; 75 | 76 | const [timeState, timeDispatch] = useReducer(timeReducer, initTimeState); 77 | 78 | const { currTime } = timeState; 79 | 80 | //* updating current time of audio 81 | const handleTimeUpdate = useCallback(() => { 82 | const currentTime = Math.ceil(player.currentTime); 83 | timeDispatch({ type: "SET_CURRTIME", payload: currentTime }); 84 | }, [player, timeDispatch]); 85 | 86 | //* handling time value changing 87 | const handleTimeChange = (e, newValue) => { 88 | if (isLoading) return; 89 | player.currentTime = newValue; 90 | timeDispatch({ type: "SET_CURRTIME", payload: newValue }); 91 | }; 92 | 93 | useEffect(() => { 94 | if (player) { 95 | //* this functionality is similar to updatetime event of HtmlMediaElement, it will update every 500ms 96 | let setTimeInterval; 97 | if (isPlaying) { 98 | setTimeInterval = setInterval(handleTimeUpdate, 500); 99 | } else { 100 | clearInterval(setTimeInterval); 101 | } 102 | return () => clearInterval(setTimeInterval); 103 | } 104 | }, [isPlaying, player, handleTimeUpdate]); 105 | 106 | //*formating time 107 | const setTimeFormat = (time) => { 108 | let secs = time % 60; 109 | let mins = ((time - secs) / 60) % 60; 110 | let hours = ((time - secs) / 60 - mins) / 60; 111 | 112 | if (secs < 10) { 113 | secs = `0${secs}`; 114 | } 115 | if (mins < 10) { 116 | mins = `0${mins}`; 117 | } 118 | 119 | if (hours >= 1) { 120 | return `0${hours}:${mins}:${secs}`; 121 | } else { 122 | return `${mins}:${secs}`; 123 | } 124 | }; 125 | 126 | return ( 127 |
128 | 129 | = 3600 ? "54px" : "42px", 134 | }} 135 | > 136 | {setTimeFormat(currTime)} 137 | 138 | 139 | 145 | 146 | 147 | 148 | {player && player.duration 149 | ? setTimeFormat(Math.trunc(player.duration)) 150 | : setTimeFormat(0)} 151 | 152 | 153 | 154 |
155 | ); 156 | }; 157 | 158 | export default TimelineController; 159 | -------------------------------------------------------------------------------- /src/components/player/Playlist.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, memo } from "react"; 2 | import CurrSura from "./CurrSura"; 3 | import AutoSizer from "react-virtualized-auto-sizer"; 4 | import { FixedSizeList as List, areEqual } from "react-window"; 5 | import memoize from "memoize-one"; 6 | 7 | import { Box, IconButton } from "@material-ui/core"; 8 | import { ArrowBackRounded } from "@material-ui/icons"; 9 | import { makeStyles } from "@material-ui/core/styles"; 10 | import { ControlContext } from "../../Providers/ControlProvider"; 11 | import { mainContext } from "../../Providers/MainProvider"; 12 | 13 | const useStyles = makeStyles((theme) => ({ 14 | row: { 15 | display: "flex", 16 | alignItems: "center", 17 | justifyContent: "center", 18 | }, 19 | box: { 20 | //backgroundColor: "#ebeff1", 21 | height: "100%", 22 | }, 23 | 24 | back: { 25 | display: "flex", 26 | alignItems: "center", 27 | padding: theme.spacing(0.5), 28 | borderRadius: 2, 29 | }, 30 | 31 | list: { 32 | height: "100%", 33 | }, 34 | })); 35 | 36 | //* the row of playlist 37 | const Row = memo((props) => { 38 | const classes = useStyles(); 39 | const { index, style, data } = props; 40 | const { 41 | state: { isLoading }, 42 | } = useContext(ControlContext); 43 | 44 | const { allSurasIndex, surasNames, setCurrSura, currSura, dispatch } = data; 45 | 46 | if (surasNames) { 47 | const { number, transliteration_en, translation_en } = surasNames.find( 48 | (sura) => sura.number === Number(allSurasIndex[index]) 49 | ); 50 | 51 | //*handle clicking play btn 52 | const handleClick = () => { 53 | if (isLoading) return; 54 | 55 | setCurrSura({ 56 | ...currSura, 57 | number, 58 | name: transliteration_en, 59 | translate: translation_en, 60 | index, 61 | }); 62 | 63 | //TODO: even if payload is true , it will reverse to false, check the state 64 | dispatch({ type: "SET_ISLOADED", payload: true }); 65 | dispatch({ type: "SET_ISSEEKED", payload: true }); 66 | }; 67 | 68 | return ( 69 |
70 | 77 |
78 | ); 79 | } 80 | return
Loading...
; 81 | }, areEqual); 82 | 83 | //*Memoized fn 84 | const createItemData = memoize( 85 | (allSurasIndex, surasNames, setCurrSura, currSura, dispatch) => ({ 86 | allSurasIndex, 87 | surasNames, 88 | setCurrSura, 89 | currSura, 90 | dispatch, 91 | }) 92 | ); 93 | 94 | //*Playlist Component 95 | const Playlist = (props) => { 96 | const classes = useStyles(props); 97 | // 98 | const { dispatch } = useContext(ControlContext); 99 | //const deviceWidth = width; 100 | // 101 | const { 102 | surasNames, 103 | setCurrSura, 104 | currSura, 105 | currReciter: { suras }, 106 | } = useContext(mainContext); 107 | // 108 | if (suras) { 109 | const allSurasIndex = suras.split(","); 110 | 111 | // 112 | const itemData = createItemData( 113 | allSurasIndex, 114 | surasNames, 115 | setCurrSura, 116 | currSura, 117 | dispatch 118 | ); 119 | 120 | const handleBack = () => { 121 | dispatch({ type: "SET_PLAYERSTATE", payload: "expanded" }); 122 | }; 123 | return ( 124 | 125 | {/* {width < 768 ? ( 126 | 127 | 128 | 129 | 130 | 131 | ) : null} */} 132 | 133 | 134 | 135 | 136 | 137 | 138 | { 139 | //TODO: search about react-window's lazy loading 140 | } 141 | 142 | {({ width, height }) => ( 143 | 150 | {Row} 151 | 152 | )} 153 | 154 | 155 | 156 | ); 157 | } 158 | return null; 159 | }; 160 | 161 | export default Playlist; 162 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 71 | 72 | # HolyQuran is a PWA made with ReactJS, React-Router & Material-UI 73 | 74 | ![app in desktop, large width](imgs/1.png) 75 | ![app in desktop, large width](imgs/2.png) 76 | ![app in desktop, large width](imgs/3.png) 77 | 78 | ## It is responsive with all devices's screens 79 | 80 | This App is fully responsive 81 | 82 | ![app in small width](imgs/6.png) 83 | ![app in small width](imgs/7.png) 84 | ![app in small width](imgs/4.png) 85 | 86 | ## Dark Theme v Light Theme 87 | 88 | This app provides 2 themes. You can run it with your favourite one 89 | 90 | ![app in small width](imgs/10.png) 91 | ![app in small width](imgs/11.png) 92 | ![app in small width](imgs/12.png) 93 | 94 | ## Run It Locally 95 | 96 | 1. Clone the repo 97 | 2. Do a `npm install` 98 | 3. Run it using `npm start` 99 | 100 | ## Install it 101 | 102 | You can install this App in your device by your browser. it will pop up in the url bar 103 | 104 | ## Credits Goes To: 105 | 106 | 1.[mp3Quran](https://www.mp3quran.net/ar) 107 | 108 | 2.[Ylight Music](https://github.com/ShivamJoker/Ylight-Music/) 109 | 110 | 3.[Freepik](https://www.freepik.com) 111 | 112 | ## Please open an issue for any bugs 113 | -------------------------------------------------------------------------------- /src/serviceWorkerRegistration.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://cra.link/PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === "localhost" || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === "[::1]" || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener("load", () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | "This web app is being served cache-first by a service " + 46 | "worker. To learn more, visit https://cra.link/PWA" 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then((registration) => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === "installed") { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | "New content is available and will be used when all " + 74 | "tabs for this page are closed. See https://cra.link/PWA." 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log("Content is cached for offline use."); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch((error) => { 97 | console.error("Error during service worker registration:", error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { "Service-Worker": "script" }, 105 | }) 106 | .then((response) => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get("content-type"); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf("javascript") === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then((registration) => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | "No internet connection found. App is running in offline mode." 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ("serviceWorker" in navigator) { 133 | navigator.serviceWorker.ready 134 | .then((registration) => { 135 | registration.unregister(); 136 | }) 137 | .catch((error) => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/components/player/Controller.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { Grid, IconButton } from "@material-ui/core"; 3 | import { mainContext } from "../../Providers/MainProvider"; 4 | import { 5 | PauseRounded, 6 | PlayArrowRounded, 7 | SkipNextRounded, 8 | SkipPreviousRounded, 9 | } from "@material-ui/icons"; 10 | import { makeStyles } from "@material-ui/core/styles"; 11 | import { ControlContext } from "../../Providers/ControlProvider"; 12 | 13 | const useStyles = makeStyles((theme) => ({ 14 | playPauseIcon: { 15 | fontSize: (props) => 16 | props.ultraMinimized ? "24px" : props.minimized ? "40px" : "48px", 17 | }, 18 | 19 | playPauseBtn: { 20 | border: "1px solid", 21 | borderColor: theme.palette.grey[700], 22 | }, 23 | controlIcons: { 24 | fontSize: (props) => (props.minimized ? "24px" : "36px"), 25 | }, 26 | })); 27 | 28 | const Controller = (props) => { 29 | const classes = useStyles(props); 30 | // 31 | const { minimized, ultraMinimized } = props; 32 | // 33 | const { 34 | player, 35 | dispatch, 36 | state: { isPlaying, isLoading, isLoaded, isSeeking, isSeeked }, 37 | } = useContext(ControlContext); 38 | // 39 | const { currReciter, currSura, setCurrSura, surasNames } = useContext( 40 | mainContext 41 | ); 42 | // 43 | const { suras } = currReciter; 44 | const { index } = currSura; 45 | 46 | //*handling next & prev functionality 47 | const handleNextPrev = (e, num) => { 48 | e.stopPropagation(); 49 | const allSurasIndex = suras.split(",").map((n) => Number(n)); 50 | 51 | //*case of prev clicked and the current time of player is bigger than or equal to 5s, it will reset to 0; 52 | if (num === -1 && player.currentTime >= 5) { 53 | player.currentTime = 0; 54 | } else if (!allSurasIndex[index + num]) { 55 | if (num === 1) { 56 | //* case of: next clicked and the sura is the last; it will reset to the first available sura in the playlist 57 | const { transliteration_en } = surasNames.find( 58 | (x) => x.number === allSurasIndex[0] 59 | ); 60 | setCurrSura({ 61 | index: 0, 62 | name: transliteration_en, 63 | number: allSurasIndex[0], 64 | }); 65 | } else { 66 | //* case of: prev clicked and the sura is the first; it will reset to the last available sura in the playlist 67 | const { transliteration_en, number } = surasNames.find( 68 | (x) => x.number === allSurasIndex[allSurasIndex.length - 1] 69 | ); 70 | setCurrSura({ 71 | index: allSurasIndex.length - 1, 72 | name: transliteration_en, 73 | number, 74 | }); 75 | } 76 | } else { 77 | const { number, transliteration_en } = surasNames.find( 78 | (x) => x.number === allSurasIndex[index + num] 79 | ); 80 | setCurrSura({ 81 | index: index + num, 82 | name: transliteration_en, 83 | number, 84 | }); 85 | } 86 | }; 87 | 88 | //*return play or pause according to the state 89 | const playPauseBtn = () => { 90 | return player ? ( 91 | isSeeking || isLoading ? ( 92 | 93 | 94 | 95 | ) : isSeeked || isLoaded ? ( 96 | { 99 | e.stopPropagation(); 100 | dispatch({ type: "SET_ISPLAYING", payload: !isPlaying }); 101 | }} 102 | className={classes.playPauseBtn} 103 | > 104 | {isPlaying ? ( 105 | 106 | ) : ( 107 | 108 | )} 109 | 110 | ) : ( 111 | 112 | 113 | 114 | ) 115 | ) : ( 116 | 117 | 118 | 119 | ); 120 | }; 121 | 122 | const minimizedController = () => { 123 | return {playPauseBtn()}; 124 | }; 125 | 126 | const maximizedController = () => { 127 | if (isLoaded) { 128 | return ( 129 | <> 130 | 131 | handleNextPrev(e, -1)}> 132 | 133 | 134 | 135 | {playPauseBtn()} 136 | 137 | handleNextPrev(e, 1)}> 138 | 139 | 140 | 141 | 142 | ); 143 | } 144 | 145 | //* return diasbled buttons to avoid aborting the fetching process of media resource 146 | return ( 147 | <> 148 | 149 | 150 | 151 | 152 | 153 | {playPauseBtn()} 154 | 155 | 156 | 157 | 158 | 159 | 160 | ); 161 | }; 162 | 163 | return ( 164 | 165 | {minimized && ultraMinimized 166 | ? minimizedController() 167 | : maximizedController()} 168 | 169 | ); 170 | }; 171 | 172 | export default Controller; 173 | -------------------------------------------------------------------------------- /.eslintcache: -------------------------------------------------------------------------------- 1 | [{"/home/th3you7/Desktop/quran-app/src/index.js":"1","/home/th3you7/Desktop/quran-app/src/routes/Player.js":"2","/home/th3you7/Desktop/quran-app/src/components/header/Header.jsx":"3","/home/th3you7/Desktop/quran-app/src/components/player/TimelineController.jsx":"4","/home/th3you7/Desktop/quran-app/src/components/drawer/Switch.jsx":"5","/home/th3you7/Desktop/quran-app/src/Providers/MainProvider.js":"6","/home/th3you7/Desktop/quran-app/src/components/player/BottomBar.jsx":"7","/home/th3you7/Desktop/quran-app/src/components/cards/Cards.jsx":"8","/home/th3you7/Desktop/quran-app/src/components/player/MediaPlayer.jsx":"9","/home/th3you7/Desktop/quran-app/src/App.js":"10","/home/th3you7/Desktop/quran-app/src/routes/index.js":"11","/home/th3you7/Desktop/quran-app/src/routes/Home.js":"12","/home/th3you7/Desktop/quran-app/src/components/index.js":"13","/home/th3you7/Desktop/quran-app/src/components/drawer/TemporaryDrawer.jsx":"14","/home/th3you7/Desktop/quran-app/src/api/fetchApi.js":"15","/home/th3you7/Desktop/quran-app/src/components/cards/Card.jsx":"16","/home/th3you7/Desktop/quran-app/src/Providers/ControlProvider.js":"17","/home/th3you7/Desktop/quran-app/src/components/player/Volume.jsx":"18","/home/th3you7/Desktop/quran-app/src/components/player/Controller.jsx":"19","/home/th3you7/Desktop/quran-app/src/components/player/MinimizedPlayer.jsx":"20","/home/th3you7/Desktop/quran-app/src/components/player/VolumeController.jsx":"21","/home/th3you7/Desktop/quran-app/src/components/player/ArtWork.jsx":"22","/home/th3you7/Desktop/quran-app/src/components/player/Player.jsx":"23","/home/th3you7/Desktop/quran-app/src/components/player/CurrReciter.jsx":"24","/home/th3you7/Desktop/quran-app/src/hooks/useResize.js":"25","/home/th3you7/Desktop/quran-app/src/components/player/CurrSura.jsx":"26","/home/th3you7/Desktop/quran-app/src/components/player/Playlist.jsx":"27","/home/th3you7/Desktop/quran-app/src/Providers/ThemeProvider.js":"28","/home/th3you7/Desktop/quran-app/src/components/player/MaximizedPlayer.jsx":"29"},{"size":219,"mtime":1608660892414,"results":"30","hashOfConfig":"31"},{"size":176,"mtime":1607440821092,"results":"32","hashOfConfig":"31"},{"size":2574,"mtime":1607426789458,"results":"33","hashOfConfig":"31"},{"size":3666,"mtime":1609702038794,"results":"34","hashOfConfig":"31"},{"size":297,"mtime":1607426539007,"results":"35","hashOfConfig":"31"},{"size":1223,"mtime":1609723760071,"results":"36","hashOfConfig":"31"},{"size":3432,"mtime":1608477112668,"results":"37","hashOfConfig":"31"},{"size":872,"mtime":1607632659556,"results":"38","hashOfConfig":"31"},{"size":2768,"mtime":1609706167130,"results":"39","hashOfConfig":"31"},{"size":892,"mtime":1609446105035,"results":"40","hashOfConfig":"31"},{"size":88,"mtime":1607428187913,"results":"41","hashOfConfig":"31"},{"size":553,"mtime":1607441094395,"results":"42","hashOfConfig":"31"},{"size":237,"mtime":1607426651933,"results":"43","hashOfConfig":"31"},{"size":1501,"mtime":1607426602813,"results":"44","hashOfConfig":"31"},{"size":528,"mtime":1607611807978,"results":"45","hashOfConfig":"31"},{"size":1523,"mtime":1607628918878,"results":"46","hashOfConfig":"31"},{"size":2019,"mtime":1609708018445,"results":"47","hashOfConfig":"31"},{"size":2163,"mtime":1608306104882,"results":"48","hashOfConfig":"31"},{"size":4233,"mtime":1609617044637,"results":"49","hashOfConfig":"31"},{"size":1333,"mtime":1608939928493,"results":"50","hashOfConfig":"31"},{"size":763,"mtime":1608941302230,"results":"51","hashOfConfig":"31"},{"size":1962,"mtime":1609704085016,"results":"52","hashOfConfig":"31"},{"size":2615,"mtime":1609709343524,"results":"53","hashOfConfig":"31"},{"size":703,"mtime":1609024904678,"results":"54","hashOfConfig":"31"},{"size":725,"mtime":1608935675958,"results":"55","hashOfConfig":"31"},{"size":1748,"mtime":1609724068317,"results":"56","hashOfConfig":"31"},{"size":3511,"mtime":1609724514617,"results":"57","hashOfConfig":"31"},{"size":422,"mtime":1609460724641,"results":"58","hashOfConfig":"31"},{"size":1552,"mtime":1609708343326,"results":"59","hashOfConfig":"31"},{"filePath":"60","messages":"61","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"62"},"1dfycz0",{"filePath":"63","messages":"64","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"62"},{"filePath":"65","messages":"66","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"62"},{"filePath":"67","messages":"68","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"69","messages":"70","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"71"},{"filePath":"72","messages":"73","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"74","messages":"75","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"76"},{"filePath":"77","messages":"78","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"79"},{"filePath":"80","messages":"81","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"82","messages":"83","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"84","messages":"85","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"79"},{"filePath":"86","messages":"87","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"79"},{"filePath":"88","messages":"89","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"71"},{"filePath":"90","messages":"91","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"92"},{"filePath":"93","messages":"94","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"79"},{"filePath":"95","messages":"96","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"71"},{"filePath":"97","messages":"98","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"99","messages":"100","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"101","messages":"102","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"103","messages":"104","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"92"},{"filePath":"105","messages":"106","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"76"},{"filePath":"107","messages":"108","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"109","messages":"110","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"111","messages":"112","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"71"},{"filePath":"113","messages":"114","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"62"},{"filePath":"115","messages":"116","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"117","messages":"118","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"119","messages":"120","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"121","messages":"122","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/home/th3you7/Desktop/quran-app/src/index.js",[],["123","124"],"/home/th3you7/Desktop/quran-app/src/routes/Player.js",[],"/home/th3you7/Desktop/quran-app/src/components/header/Header.jsx",[],"/home/th3you7/Desktop/quran-app/src/components/player/TimelineController.jsx",["125"],"/home/th3you7/Desktop/quran-app/src/components/drawer/Switch.jsx",[],["126","127"],"/home/th3you7/Desktop/quran-app/src/Providers/MainProvider.js",[],"/home/th3you7/Desktop/quran-app/src/components/player/BottomBar.jsx",[],["128","129"],"/home/th3you7/Desktop/quran-app/src/components/cards/Cards.jsx",[],["130","131"],"/home/th3you7/Desktop/quran-app/src/components/player/MediaPlayer.jsx",[],"/home/th3you7/Desktop/quran-app/src/App.js",[],"/home/th3you7/Desktop/quran-app/src/routes/index.js",[],"/home/th3you7/Desktop/quran-app/src/routes/Home.js",[],"/home/th3you7/Desktop/quran-app/src/components/index.js",[],"/home/th3you7/Desktop/quran-app/src/components/drawer/TemporaryDrawer.jsx",[],["132","133"],"/home/th3you7/Desktop/quran-app/src/api/fetchApi.js",[],"/home/th3you7/Desktop/quran-app/src/components/cards/Card.jsx",[],"/home/th3you7/Desktop/quran-app/src/Providers/ControlProvider.js",[],"/home/th3you7/Desktop/quran-app/src/components/player/Volume.jsx",[],"/home/th3you7/Desktop/quran-app/src/components/player/Controller.jsx",[],"/home/th3you7/Desktop/quran-app/src/components/player/MinimizedPlayer.jsx",[],"/home/th3you7/Desktop/quran-app/src/components/player/VolumeController.jsx",[],"/home/th3you7/Desktop/quran-app/src/components/player/ArtWork.jsx",[],"/home/th3you7/Desktop/quran-app/src/components/player/Player.jsx",[],"/home/th3you7/Desktop/quran-app/src/components/player/CurrReciter.jsx",[],"/home/th3you7/Desktop/quran-app/src/hooks/useResize.js",[],"/home/th3you7/Desktop/quran-app/src/components/player/CurrSura.jsx",[],"/home/th3you7/Desktop/quran-app/src/components/player/Playlist.jsx",[],"/home/th3you7/Desktop/quran-app/src/Providers/ThemeProvider.js",[],"/home/th3you7/Desktop/quran-app/src/components/player/MaximizedPlayer.jsx",[],{"ruleId":"134","replacedBy":"135"},{"ruleId":"136","replacedBy":"137"},{"ruleId":"138","severity":1,"message":"139","line":50,"column":25,"nodeType":"140","messageId":"141","endLine":50,"endColumn":36},{"ruleId":"134","replacedBy":"142"},{"ruleId":"136","replacedBy":"143"},{"ruleId":"134","replacedBy":"144"},{"ruleId":"136","replacedBy":"145"},{"ruleId":"134","replacedBy":"146"},{"ruleId":"136","replacedBy":"147"},{"ruleId":"134","replacedBy":"148"},{"ruleId":"136","replacedBy":"149"},"no-native-reassign",["150"],"no-negated-in-lhs",["151"],"no-unused-vars","'playerState' is assigned a value but never used.","Identifier","unusedVar",["150"],["151"],["150"],["151"],["150"],["151"],["150"],["151"],"no-global-assign","no-unsafe-negation"] --------------------------------------------------------------------------------