diff --git a/App.js b/App.js
index b7143355..20df56d9 100644
--- a/App.js
+++ b/App.js
@@ -9,7 +9,7 @@ import Router from "./src/navigation/Router";
import { createTables, createIndexes } from "./src/services/db";
import { setStatusBar } from "./src/services/asyncStorage";
-// Redyx
+// Redux
import { Provider } from "react-redux";
import { persistor, store } from "./src/redux/store/configureStore";
import { PersistGate } from "redux-persist/lib/integration/react";
diff --git a/package-lock.json b/package-lock.json
index 09738d7d..70f89a0a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7270,6 +7270,11 @@
}
}
},
+ "redux-devtools-extension": {
+ "version": "2.13.9",
+ "resolved": "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz",
+ "integrity": "sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A=="
+ },
"redux-persist": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",
diff --git a/package.json b/package.json
index bdd70f27..d488b43b 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
"react-native-webview": "11.0.0",
"react-redux": "^7.2.2",
"redux": "^4.0.5",
+ "redux-devtools-extension": "^2.13.9",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.3.0",
"rn-sliding-up-panel": "^2.4.4"
diff --git a/src/components/common/SearchBar.js b/src/components/common/SearchBar.js
deleted file mode 100644
index 717e4a85..00000000
--- a/src/components/common/SearchBar.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import React from "react";
-import { TextInput, StyleSheet } from "react-native";
-import { theme } from "../../theme/theme";
-
-const SearchBar = ({ searchText, onChangeText, onSubmitEditing }) => (
-
-);
-
-export default SearchBar;
-
-const styles = StyleSheet.create({
- searchBar: {
- flex: 1,
- fontSize: 18,
- color: "white",
- marginLeft: 15,
- },
-});
diff --git a/src/redux/actions/chapters.js b/src/redux/actions/chapters.js
new file mode 100644
index 00000000..61fdc30a
--- /dev/null
+++ b/src/redux/actions/chapters.js
@@ -0,0 +1,57 @@
+import {
+ LOADING_CHAPTERS,
+ GET_CHAPTERS,
+ CHAPTER_DOWNLOADED,
+ CHAPTER_DELETED,
+ CHAPTER_DOWNLOADING,
+} from "./types";
+
+import {
+ deleteChapterFromDb,
+ downloadChapterFromSource,
+} from "../../services/db";
+
+import { ToastAndroid } from "react-native";
+
+export const downloadChapter = (
+ extensionId,
+ novelUrl,
+ chapterUrl,
+ chapterName
+) => async (dispatch) => {
+ dispatch({
+ type: CHAPTER_DOWNLOADING,
+ payload: {
+ extensionId,
+ novelUrl,
+ chapterUrl,
+ downloading: true,
+ downloaded: false,
+ },
+ });
+
+ await downloadChapterFromSource(extensionId, novelUrl, chapterUrl);
+
+ dispatch({
+ type: CHAPTER_DOWNLOADED,
+ payload: { extensionId, novelUrl, chapterUrl },
+ });
+
+ ToastAndroid.show(`Downloaded ${chapterName}`, ToastAndroid.SHORT);
+};
+
+export const deleteChapter = (
+ extensionId,
+ novelUrl,
+ chapterUrl,
+ chapterName
+) => (dispatch) => {
+ deleteChapterFromDb(extensionId, novelUrl, chapterUrl);
+
+ dispatch({
+ type: CHAPTER_DELETED,
+ payload: { extensionId, novelUrl, chapterUrl },
+ });
+
+ ToastAndroid.show(`Deleted ${chapterName}`, ToastAndroid.SHORT);
+};
diff --git a/src/redux/actions/novel.js b/src/redux/actions/novel.js
new file mode 100644
index 00000000..1630bb08
--- /dev/null
+++ b/src/redux/actions/novel.js
@@ -0,0 +1,52 @@
+import { LOADING_NOVEL, GET_NOVEL, GET_CHAPTERS } from "./types";
+import {
+ checkNovelInDb,
+ getChaptersFromDb,
+ getNovelInfoFromDb,
+ insertChaptersInDb,
+ insertNovelInfoInDb,
+} from "../../services/db";
+import {
+ fetchChaptersFromSource,
+ fetchNovelFromSource,
+} from "../../services/api";
+
+export const getNovel = (navigatingFrom, extensionId, novelUrl) => async (
+ dispatch
+) => {
+ dispatch({ type: LOADING_NOVEL });
+
+ if (navigatingFrom === 1) {
+ const chapters = await getChaptersFromDb(novelUrl, "", "");
+ dispatch({
+ type: GET_CHAPTERS,
+ payload: chapters,
+ });
+ } else {
+ const inCache = await checkNovelInDb(novelUrl);
+
+ if (inCache) {
+ const novel = await getNovelInfoFromDb(novelUrl);
+ const chapters = await getChaptersFromDb(novelUrl, "", "");
+
+ dispatch({
+ type: GET_NOVEL,
+ payload: { novel, chapters },
+ });
+ } else {
+ const novel = await fetchNovelFromSource(extensionId, novelUrl);
+ const chapters = await fetchChaptersFromSource(
+ extensionId,
+ novelUrl
+ );
+
+ dispatch({
+ type: GET_NOVEL,
+ payload: { novel, chapters },
+ });
+
+ insertNovelInfoInDb(novel);
+ insertChaptersInDb(novelUrl, chapters);
+ }
+ }
+};
diff --git a/src/redux/actions/types.js b/src/redux/actions/types.js
index 25425fef..ef28b4f5 100644
--- a/src/redux/actions/types.js
+++ b/src/redux/actions/types.js
@@ -7,3 +7,17 @@ export const SET_DISPLAY_MODE = "SET_DISPLAY_MODE";
export const SET_ITEMS_PER_ROW = "SET_ITEMS_PER_ROW";
export const GET_EXTENSIONS = "GET_EXTENSIONS";
+
+export const LOADING_NOVEL = "LOADING_NOVEL";
+
+export const LOADING_CHAPTERS = "LOADING_CHAPTERS";
+
+export const GET_NOVEL = "GET_NOVEL";
+
+export const GET_CHAPTERS = "GET_CHAPTERS";
+
+export const CHAPTER_DOWNLOADED = "CHAPTER_DOWNLOADED";
+
+export const CHAPTER_DELETED = "CHAPTER_DELETED";
+
+export const CHAPTER_DOWNLOADING = "CHAPTER_DOWNLOADING";
diff --git a/src/redux/reducers/chapters.js b/src/redux/reducers/chapters.js
new file mode 100644
index 00000000..f2f44833
--- /dev/null
+++ b/src/redux/reducers/chapters.js
@@ -0,0 +1,41 @@
+import {
+ CHAPTER_DOWNLOADING,
+ CHAPTER_DOWNLOADED,
+ CHAPTER_DELETED,
+} from "../actions/types";
+
+const initialState = {
+ chapters: [],
+ loading: true,
+};
+
+const downloadsReducer = (state = initialState, action) => {
+ const { type, payload } = action;
+
+ switch (type) {
+ case CHAPTER_DOWNLOADING:
+ return { ...state, downloads: [...state.downloads, payload] };
+ case CHAPTER_DOWNLOADED:
+ return {
+ ...state,
+ downloads: state.downloads.map((chapter) =>
+ chapter.chapterUrl === payload.chapterUrl &&
+ chapter.extensionId === payload.extensionId
+ ? { ...chapter, downloading: false, downloaded: true }
+ : chapter
+ ),
+ };
+ case CHAPTER_DELETED:
+ return {
+ downloads: state.downloads.filter(
+ (chapter) =>
+ chapter.chapterUrl === payload.chapterUrl &&
+ chapter.extensionId === payload.extensionId
+ ),
+ };
+ default:
+ return state;
+ }
+};
+
+export default downloadsReducer;
diff --git a/src/redux/reducers/novel.js b/src/redux/reducers/novel.js
new file mode 100644
index 00000000..8be74c67
--- /dev/null
+++ b/src/redux/reducers/novel.js
@@ -0,0 +1,60 @@
+import {
+ CHAPTER_DOWNLOADING,
+ CHAPTER_DOWNLOADED,
+ CHAPTER_DELETED,
+ GET_CHAPTERS,
+ LOADING_NOVEL,
+ GET_NOVEL,
+} from "../actions/types";
+
+const initialState = {
+ novel: null,
+ chapters: [],
+ loading: true,
+};
+
+const novelReducer = (state = initialState, action) => {
+ const { type, payload } = action;
+
+ switch (type) {
+ case LOADING_NOVEL:
+ return { ...state, loading: true };
+ case GET_NOVEL:
+ return {
+ ...state,
+ novel: payload.novel,
+ chapters: payload.chapters,
+ loading: false,
+ };
+ case GET_CHAPTERS:
+ return {
+ ...state,
+ chapters: payload,
+ loading: false,
+ };
+ case CHAPTER_DOWNLOADING:
+ return { ...state, downloads: [...state.downloads, payload] };
+ case CHAPTER_DOWNLOADED:
+ return {
+ ...state,
+ downloads: state.downloads.map((chapter) =>
+ chapter.chapterUrl === payload.chapterUrl &&
+ chapter.extensionId === payload.extensionId
+ ? { ...chapter, downloading: false, downloaded: true }
+ : chapter
+ ),
+ };
+ case CHAPTER_DELETED:
+ return {
+ downloads: state.downloads.filter(
+ (chapter) =>
+ chapter.chapterUrl === payload.chapterUrl &&
+ chapter.extensionId === payload.extensionId
+ ),
+ };
+ default:
+ return state;
+ }
+};
+
+export default novelReducer;
diff --git a/src/redux/store/configureStore.js b/src/redux/store/configureStore.js
index 12d7f82e..62a3be98 100644
--- a/src/redux/store/configureStore.js
+++ b/src/redux/store/configureStore.js
@@ -7,10 +7,12 @@ import themeReducer from "../reducers/theme";
import settingsReducer from "../reducers/settings";
import libraryReducer from "../reducers/library";
import extensionReducer from "../reducers/extension";
+import novelReducer from "../reducers/novel";
const persistConfig = {
key: "root",
storage: AsyncStorage,
+ blacklist: ["novelReducer"],
};
const persistedReducer = persistReducer(
@@ -20,6 +22,7 @@ const persistedReducer = persistReducer(
settingsReducer,
libraryReducer,
extensionReducer,
+ novelReducer,
})
);
diff --git a/src/screens/History/History.js b/src/screens/History/History.js
index f88264bc..70bc999a 100644
--- a/src/screens/History/History.js
+++ b/src/screens/History/History.js
@@ -57,6 +57,7 @@ const History = ({ navigation }) => {
]}
>
item.historyId.toString()}
renderItem={renderHistoryCard}
diff --git a/src/screens/Library/Library.js b/src/screens/Library/Library.js
index daa60aa3..dfd8e1e7 100644
--- a/src/screens/Library/Library.js
+++ b/src/screens/Library/Library.js
@@ -78,6 +78,7 @@ const LibraryScreen = ({
/>
) : (
item.novelUrl}
diff --git a/src/screens/Novel/Novel.js b/src/screens/Novel/Novel.js
index 628fefad..f742f2be 100644
--- a/src/screens/Novel/Novel.js
+++ b/src/screens/Novel/Novel.js
@@ -1,4 +1,4 @@
-import React, { useState, useCallback, useRef } from "react";
+import React, { useState, useCallback, useRef, useEffect } from "react";
import { useFocusEffect } from "@react-navigation/native";
import {
StyleSheet,
@@ -13,25 +13,22 @@ import ChapterCard from "./components/ChapterCard";
import NovelInfoHeader from "./components/NovelHeader";
import { BottomSheet } from "./components/BottomSheet";
-import {
- fetchNovelFromSource,
- fetchChaptersFromSource,
-} from "../../services/api";
-import {
- insertNovelInfoInDb,
- insertChaptersInDb,
- getChaptersFromDb,
- checkNovelInDb,
- getNovelInfoFromDb,
- toggleFavourite,
- downloadOrDeleteChapter,
-} from "../../services/db";
+import { toggleFavourite, downloadOrDeleteChapter } from "../../services/db";
-import { useSelector } from "react-redux";
+import { connect } from "react-redux";
-const NovelItem = ({ route, navigation }) => {
+import { getNovel } from "../../redux/actions/novel";
+
+const Novel = ({
+ route,
+ navigation,
+ theme,
+ novel,
+ chapters,
+ loading,
+ getNovel,
+}) => {
const item = route.params;
- const theme = useSelector((state) => state.themeReducer.theme);
const {
extensionId,
@@ -41,11 +38,7 @@ const NovelItem = ({ route, navigation }) => {
novelCover,
} = route.params;
- const [loading, setLoading] = useState(true);
- const [refreshing, setRefreshing] = useState(false);
-
- const [novel, setNovel] = useState(item);
- const [chapters, setChapters] = useState();
+ // const [refreshing, setRefreshing] = useState(false);
const [libraryStatus, setlibraryStatus] = useState(item.libraryStatus);
@@ -59,50 +52,6 @@ const NovelItem = ({ route, navigation }) => {
let _panel = useRef(null); // Bottomsheet ref
- /**
- * Fetch novel and chapters from source and set loading false
- */
-
- const getNovelFromSource = async (extensionId, novelUrl) => {
- await fetchNovelFromSource(extensionId, novelUrl).then((response) => {
- setNovel(response);
- insertNovelInfoInDb(response);
- });
- await fetchChaptersFromSource(extensionId, novelUrl).then(
- (response) => {
- setChapters(response);
- insertChaptersInDb(novelUrl, response);
- }
- );
- setlibraryStatus(0);
- setLoading(false);
- setRefreshing(false);
- };
-
- /**
- * Get chapters from db and set loading false
- */
-
- const getChapters = async (novelUrl) => {
- await getChaptersFromDb(novelUrl, filter, sort).then((response) =>
- setChapters(response)
- );
- setLoading(false);
- setRefreshing(false);
- };
-
- /**
- * Get novel and chapters from database
- */
-
- const getNovelFromDb = async () => {
- await getNovelInfoFromDb(novelUrl).then((res) => {
- setNovel(res);
- setlibraryStatus(res.libraryStatus);
- });
- getChapters(novelUrl);
- };
-
/**
* Insert or remove novel from library
*/
@@ -147,36 +96,15 @@ const NovelItem = ({ route, navigation }) => {
});
};
- /**
- * Check if the novel is in db. If not, fetch from source
- *
- * If navigating from library then directly get chapters from db
- */
-
- const checkIfExistsInDb = () => {
- if (navigatingFrom === 1) {
- getChapters(novelUrl);
- } else {
- setRefreshing(true);
- checkNovelInDb(novelUrl).then((res) => {
- if (res) {
- getNovelFromDb(novelUrl);
- } else {
- getNovelFromSource(extensionId, novelUrl);
- }
- });
- }
- };
-
useFocusEffect(
useCallback(() => {
- checkIfExistsInDb();
+ getNovel(navigatingFrom, extensionId, novelUrl);
}, [sort, filter])
);
const onRefresh = async () => {
- setRefreshing(true);
- checkIfExistsInDb();
+ // setRefreshing(true);
+ // checkIfExistsInDb();
};
const renderChapterCard = ({ item }) => (
@@ -215,7 +143,7 @@ const NovelItem = ({ route, navigation }) => {
novelName: novelName,
}}
novel={novel}
- noOfChapters={!loading && chapters.length}
+ noOfChapters={chapters.length}
libraryStatus={libraryStatus}
insertNovelInLib={insertNovelInLib}
navigatingFrom={navigatingFrom}
@@ -225,8 +153,8 @@ const NovelItem = ({ route, navigation }) => {
)}
refreshControl={
@@ -245,8 +173,14 @@ const NovelItem = ({ route, navigation }) => {
);
};
+const mapStateToProps = (state) => ({
+ theme: state.themeReducer.theme,
+ novel: state.novelReducer.novel,
+ chapters: state.novelReducer.chapters,
+ loading: state.novelReducer.loading,
+});
-export default NovelItem;
+export default connect(mapStateToProps, { getNovel })(Novel);
const styles = StyleSheet.create({
container: {
diff --git a/src/screens/Novel/components/ChapterCard.js b/src/screens/Novel/components/ChapterCard.js
index 1f405d2c..38866ee6 100644
--- a/src/screens/Novel/components/ChapterCard.js
+++ b/src/screens/Novel/components/ChapterCard.js
@@ -1,6 +1,5 @@
import React, { useState } from "react";
-
-import { View, Text, ActivityIndicator } from "react-native";
+import { View, Text, ActivityIndicator, StyleSheet } from "react-native";
import { TouchableRipple, IconButton } from "react-native-paper";
import { useSelector } from "react-redux";
@@ -19,15 +18,7 @@ const ChapterCard = ({
return (
navigation.navigate("ChapterItem", {
chapterUrl: chapter.chapterUrl,
@@ -113,3 +104,13 @@ const ChapterCard = ({
};
export default ChapterCard;
+
+const styles = StyleSheet.create({
+ chapterCardContainer: {
+ paddingHorizontal: 15,
+ paddingVertical: 7,
+ flexDirection: "row",
+ justifyContent: "space-between",
+ alignItems: "center",
+ },
+});
diff --git a/src/screens/Novel/components/NovelHeader.js b/src/screens/Novel/components/NovelHeader.js
index 44252780..9ecbc434 100644
--- a/src/screens/Novel/components/NovelHeader.js
+++ b/src/screens/Novel/components/NovelHeader.js
@@ -303,7 +303,7 @@ const NovelInfoHeader = ({
fontWeight: "bold",
}}
>
- {noOfChapters + " Chapters "}
+ {noOfChapters + " Chapters"}
{
);
});
};
+
+export const downloadChapterFromSource = async (
+ extensionId,
+ novelUrl,
+ chapterUrl
+) => {
+ const downloadUrl = `https://lnreader-extensions.herokuapp.com/api/${extensionId}/novel/${novelUrl}${chapterUrl}`;
+
+ const response = await fetch(downloadUrl);
+ const chapter = await response.json();
+
+ db.transaction((tx) => {
+ tx.executeSql(
+ `UPDATE ChapterTable SET downloaded = 1 WHERE chapterUrl = ?`,
+ [chapterUrl]
+ );
+ tx.executeSql(
+ `INSERT INTO DownloadsTable (chapterUrl, novelUrl, chapterName, chapterText, prevChapter, nextChapter) VALUES (?, ?, ?, ?, ?, ?)`,
+ [
+ chapterUrl,
+ novelUrl,
+ chapter.chapterName,
+ chapter.chapterText,
+ chapter.prevChapter,
+ chapter.nextChapter,
+ ],
+ (tx, res) => console.log(`Downloaded Chapter ${chapterUrl}`),
+ (txObj, error) => console.log("Error ", error)
+ );
+ });
+};
+
+export const deleteChapterFromDb = (extensionId, novelUrl, chapterUrl) => {
+ const updateIsDownloadedQuery = `UPDATE ChapterTable SET downloaded = 0 WHERE chapterUrl = ? AND novelUrl = ? AND extensionId = ?`;
+ const deleteChapterQuery = `DELETE FROM DownloadsTable WHERE chapterUrl = ? AND novelUrl = ? AND extensionId = ?`;
+
+ db.transaction((tx) => {
+ tx.executeSql(updateIsDownloadedQuery, [
+ chapterUrl,
+ novelUrl,
+ extensionId,
+ ]);
+ tx.executeSql(
+ deleteChapterQuery,
+ [chapterUrl, novelUrl, extensionId],
+ (tx, res) => console.log(`Chapter deleted`),
+ (txObj, error) => console.log("Error ", error)
+ );
+ });
+};