├── 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 |
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 |
135 |
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 | 
75 | 
76 | 
77 |
78 | ## It is responsive with all devices's screens
79 |
80 | This App is fully responsive
81 |
82 | 
83 | 
84 | 
85 |
86 | ## Dark Theme v Light Theme
87 |
88 | This app provides 2 themes. You can run it with your favourite one
89 |
90 | 
91 | 
92 | 
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"]
--------------------------------------------------------------------------------