diff --git a/src/components/ColorPickerModal.js b/src/components/ColorPickerModal.js
index a2b3b943..57f77816 100644
--- a/src/components/ColorPickerModal.js
+++ b/src/components/ColorPickerModal.js
@@ -10,6 +10,7 @@ const ColorPickerModal = ({
onSubmit,
hideModal,
modalVisible,
+ showAccentColors,
}) => {
const [text, setText] = useState(color);
const [error, setError] = useState();
diff --git a/src/components/NovelCover.js b/src/components/NovelCover.js
index bd0fa3f7..0bdbfc1c 100644
--- a/src/components/NovelCover.js
+++ b/src/components/NovelCover.js
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useCallback } from "react";
import {
StyleSheet,
View,
@@ -6,19 +6,17 @@ import {
useWindowDimensions,
Pressable,
} from "react-native";
-import { LinearGradient } from "expo-linear-gradient";
-import { TouchableRipple } from "react-native-paper";
-import { useSelector } from "react-redux";
-import { useSettings, useTheme } from "../hooks/reduxHooks";
-import ListView from "./ListView";
+import { LinearGradient } from "expo-linear-gradient";
import FastImage from "react-native-fast-image";
+
+import ListView from "./ListView";
+
import { getDeviceOrientation } from "../services/utils/helpers";
-const NovelCover = ({ item, onPress, libraryStatus }) => {
- const theme = useTheme();
+const NovelCover = ({ item, onPress, libraryStatus, theme, settings }) => {
const { displayMode, novelsPerRow, showDownloadBadges, showUnreadBadges } =
- useSettings();
+ settings;
const window = useWindowDimensions();
@@ -27,98 +25,10 @@ const NovelCover = ({ item, onPress, libraryStatus }) => {
const getNovelsPerRow = () =>
orientation === "landscape" ? 6 : novelsPerRow;
- const getHeight = () => (window.width / getNovelsPerRow()) * (4 / 3);
-
- const comfortableTitle = () =>
- displayMode === 1 && (
-
- {item.novelName}
-
- );
-
- const compactTitle = () =>
- displayMode === 0 && (
-
-
-
- {item.novelName}
-
-
-
- );
-
- const unreadBadge = () =>
- showUnreadBadges &&
- item.chaptersUnread && (
-
- {item.chaptersUnread}
-
- );
-
- const downloadBadge = () =>
- showDownloadBadges &&
- item.chaptersDownloaded && (
-
- {item.chaptersDownloaded}
-
- );
-
- const inLibraryBadge = () =>
- libraryStatus && (
-
- In library
-
- );
+ const getHeight = useCallback(
+ () => (window.width / getNovelsPerRow()) * (4 / 3),
+ []
+ );
return displayMode !== 2 ? (
{
onPress={onPress}
>
<>
-
- {inLibraryBadge()}
- {downloadBadge()}
- {unreadBadge()}
+
+ {libraryStatus && }
+ {showDownloadBadges && item.chaptersDownloaded && (
+
+ )}
+ {showUnreadBadges && item.chaptersUnread && (
+
+ )}
{
libraryStatus && { opacity: 0.5 },
]}
/>
-
- {compactTitle()}
+
+ {displayMode === 0 && (
+
+ )}
- {comfortableTitle()}
+ {displayMode === 1 && (
+
+ )}
>
) : (
+ )
+ }
+ unreadBadge={
+ showUnreadBadges &&
+ item.chaptersUnread && (
+
+ )
+ }
+ inLibraryBadge={libraryStatus && }
theme={theme}
onPress={onPress}
/>
@@ -182,6 +116,99 @@ const NovelCover = ({ item, onPress, libraryStatus }) => {
export default NovelCover;
+const ComfortableTitle = ({ theme, novelName }) => (
+
+ {novelName}
+
+);
+
+const CompactTitle = ({ novelName }) => (
+
+
+
+ {novelName}
+
+
+
+);
+
+const InLibraryBadge = ({ theme }) => (
+
+ In library
+
+);
+
+const UnreadBadge = ({
+ chaptersDownloaded,
+ chaptersUnread,
+ showDownloadBadges,
+ theme,
+}) => (
+
+ {chaptersUnread}
+
+);
+
+const DownloadBadge = ({
+ chaptersDownloaded,
+ showUnreadBadges,
+ chaptersUnread,
+}) => (
+
+ {chaptersDownloaded}
+
+);
+
const styles = StyleSheet.create({
titleContainer: {
flex: 1,
@@ -238,4 +265,17 @@ const styles = StyleSheet.create({
paddingHorizontal: 4,
fontSize: 12,
},
+ compactTitleContainer: {
+ position: "absolute",
+ bottom: 4,
+ left: 4,
+ right: 4,
+ },
+ badgeContainer: {
+ position: "absolute",
+ zIndex: 1,
+ top: 8,
+ left: 8,
+ flexDirection: "row",
+ },
});
diff --git a/src/database/queries/ChapterQueries.js b/src/database/queries/ChapterQueries.js
index a1885e5d..3810b6d8 100644
--- a/src/database/queries/ChapterQueries.js
+++ b/src/database/queries/ChapterQueries.js
@@ -17,15 +17,15 @@ export const insertChapters = async (novelId, chapters) => {
novelId,
],
(txObj, res) => {},
- (txObj, error) =>
- console.log(
- "Error ",
- error,
- " Chapter URL: ",
- chapter.chapterUrl,
- " Novel ID ",
- novelId
- )
+ (txObj, error) => {}
+ // console.log(
+ // "Error ",
+ // error,
+ // " Chapter URL: ",
+ // chapter.chapterUrl,
+ // " Novel ID ",
+ // novelId
+ // )
),
(txObj, res) => {
console.log("Success");
@@ -35,13 +35,10 @@ export const insertChapters = async (novelId, chapters) => {
});
};
-const getChaptersQuery = (sort, filter) =>
- `SELECT * FROM chapters WHERE novelId = ? ${filter || ""} ${
- sort || "ORDER BY chapterId ASC"
- }`;
+const getChaptersQuery = (sort = "ORDER BY chapterId ASC", filter = "") =>
+ `SELECT * FROM chapters WHERE novelId = ? ${filter} ${sort}`;
export const getChapters = (novelId, sort, filter) => {
- // console.log(getChaptersQuery(sort, filter));
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
@@ -262,3 +259,25 @@ export const markPreviousChaptersUnread = async (chapterId, novelId) => {
);
});
};
+
+const getDownloadedChaptersQuery = `
+ SELECT chapters.*, novels.sourceId, novels.novelName, novels.novelCover
+ FROM chapters
+ JOIN novels
+ ON chapters.novelId = novels.novelId
+ WHERE chapters.downloaded = 1`;
+
+export const getDownloadedChapters = () => {
+ return new Promise((resolve, reject) =>
+ db.transaction((tx) => {
+ tx.executeSql(
+ getDownloadedChaptersQuery,
+ null,
+ (txObj, { rows: { _array } }) => {
+ resolve(_array);
+ },
+ (txObj, error) => console.log("Error ", error)
+ );
+ })
+ );
+};
diff --git a/src/navigators/More.js b/src/navigators/More.js
index 3f6f53ff..4f4d66bc 100644
--- a/src/navigators/More.js
+++ b/src/navigators/More.js
@@ -11,6 +11,7 @@ import BackupSettings from "../screens/More/BackupSettings";
import AdvancedSettings from "../screens/More/AdvancedSettings";
import GeneralSettings from "../screens/More/GeneralSettings";
import DownloadQueue from "../screens/More/DownloadQueue";
+import Downloads from "../screens/More/Downloads";
const Stack = createStackNavigator();
@@ -32,6 +33,7 @@ const MoreStack = () => (
+
);
diff --git a/src/redux/settings/settings.reducer.js b/src/redux/settings/settings.reducer.js
index 97c81edb..1a0a883b 100644
--- a/src/redux/settings/settings.reducer.js
+++ b/src/redux/settings/settings.reducer.js
@@ -17,7 +17,6 @@ import {
strawberryDaiquiri,
palenightTheme,
} from "../../theme/theme";
-import { setRippleColor } from "./settings.actions";
const themes = {
0: amoledDarkTheme,
@@ -69,6 +68,7 @@ const initialState = {
searchAllSources: false,
textSelectable: false,
updateLibraryOnLaunch: false,
+ downloadNewChapters: false,
};
const settingsReducer = (state = initialState, action) => {
diff --git a/src/redux/updates/updates.actions.js b/src/redux/updates/updates.actions.js
index d23868d9..8c380b85 100644
--- a/src/redux/updates/updates.actions.js
+++ b/src/redux/updates/updates.actions.js
@@ -1,5 +1,7 @@
import { GET_UPDATES } from "./updates.types";
+import store from "../store";
+
import { getUpdates } from "../../database/queries/UpdateQueries";
import { updateAllNovels } from "../../services/updates";
@@ -33,10 +35,12 @@ export const getUpdatesAction = () => async (dispatch) => {
dispatch({ type: GET_UPDATES, payload: groupedUpdates });
};
-export const updateLibraryAction = () => async (dispatch) => {
+export const updateLibraryAction = () => async (dispatch, getState) => {
showToast("Updating library");
- await updateAllNovels();
+ const { downloadNewChapters = false } = getState().settingsReducer;
+
+ await updateAllNovels({ downloadNewChapters });
dispatch(getUpdatesAction());
};
diff --git a/src/screens/Browse/BrowseMal.js b/src/screens/Browse/BrowseMal.js
index 1b7b2a92..a19afbe4 100644
--- a/src/screens/Browse/BrowseMal.js
+++ b/src/screens/Browse/BrowseMal.js
@@ -14,6 +14,7 @@ import { scrapeSearchResults } from "./discover/MyAnimeListScraper";
const BrowseMalScreen = ({ navigation, route }) => {
const theme = useTheme();
+ const settings = useSettings();
const [loading, setLoading] = useState(true);
const [novels, setNovels] = useState();
@@ -75,10 +76,12 @@ const BrowseMalScreen = ({ navigation, route }) => {
novelName: item.title,
})
}
+ theme={theme}
+ settings={settings}
/>
);
- const { displayMode, novelsPerRow } = useSettings();
+ const { displayMode, novelsPerRow } = settings;
const orientation = getDeviceOrientation();
diff --git a/src/screens/Browse/BrowseSettings.js b/src/screens/Browse/BrowseSettings.js
index 99e0cd83..42f70967 100644
--- a/src/screens/Browse/BrowseSettings.js
+++ b/src/screens/Browse/BrowseSettings.js
@@ -21,7 +21,7 @@ const BrowseSettings = ({ navigation }) => {
const { filters = [], showMyAnimeList = true } = useSelector(
(state) => state.sourceReducer
);
- const languages = ["English", "Spanish", "Japanese", "Chinese"];
+ const languages = ["English", "Spanish", "Japanese", "Chinese", "Arabic"];
const { searchAllSources = false } = useSettings();
const renderItem = ({ item }) => {
diff --git a/src/screens/Browse/components/SourceCard.js b/src/screens/Browse/components/SourceCard.js
index e984b939..5275637b 100644
--- a/src/screens/Browse/components/SourceCard.js
+++ b/src/screens/Browse/components/SourceCard.js
@@ -21,11 +21,7 @@ const ExtensionCard = ({ item, theme, isPinned, dispatch, navigation }) => {
onPress={navigateToExtension}
android_ripple={{ color: theme.rippleColor }}
>
-
+
@@ -88,7 +88,7 @@ const ReaderSeekbar = ({
/>
diff --git a/src/screens/Chapter/TextToComponent.js b/src/screens/Chapter/TextToComponent.js
index e14822c9..ca3da18a 100644
--- a/src/screens/Chapter/TextToComponent.js
+++ b/src/screens/Chapter/TextToComponent.js
@@ -1,4 +1,4 @@
-import React, { memo, useCallback } from "react";
+import React, { memo, useCallback, useEffect } from "react";
import { Text, View } from "react-native";
import FitImage from "react-native-fit-image";
@@ -57,7 +57,14 @@ const HorizontalRule = ({ textColor }) => (
/>
);
-const Link = ({ link, text, textStyle, textSelectable, onPressLink }) => (
+const Link = ({
+ link,
+ text,
+ textStyle,
+ textSelectable,
+ onPressLink,
+ theme,
+}) => (
{
if (part) {
let match = part.match(/\[(.*?)\]\((.*?)\)/);
@@ -110,7 +115,7 @@ const TextToComponents = ({
text={text}
textStyle={textStyle}
textSelectable={textSelectable}
- index={index}
+ key={index}
textSize={textSize}
/>
);
@@ -121,7 +126,7 @@ const TextToComponents = ({
text={text}
textStyle={textStyle}
textSelectable={textSelectable}
- index={index}
+ key={index}
textSize={textSize}
/>
);
@@ -132,7 +137,7 @@ const TextToComponents = ({
text={text}
textStyle={textStyle}
textSelectable={textSelectable}
- index={index}
+ key={index}
textSize={textSize}
/>
);
@@ -143,7 +148,7 @@ const TextToComponents = ({
text={text}
textStyle={textStyle}
textSelectable={textSelectable}
- index={index}
+ key={index}
textSize={textSize}
/>
);
@@ -154,7 +159,7 @@ const TextToComponents = ({
text={text}
textStyle={textStyle}
textSelectable={textSelectable}
- index={index}
+ key={index}
textSize={textSize}
/>
);
@@ -165,7 +170,7 @@ const TextToComponents = ({
text={text}
textStyle={textStyle}
textSelectable={textSelectable}
- index={index}
+ key={index}
textSize={textSize}
/>
);
@@ -176,7 +181,7 @@ const TextToComponents = ({
text={text}
textStyle={textStyle}
textSelectable={textSelectable}
- index={index}
+ key={index}
textSize={textSize}
/>
);
@@ -186,7 +191,7 @@ const TextToComponents = ({
text={text}
textStyle={textStyle}
textSelectable={textSelectable}
- index={index}
+ key={index}
/>
);
@@ -198,18 +203,15 @@ const TextToComponents = ({
textSelectable={textSelectable}
text={link}
onPressLink={onPressLink}
+ theme={theme}
+ key={index}
/>
);
case "img":
- return (
-
- );
+ return ;
case "hr":
return (
-
+
);
}
} else {
@@ -218,7 +220,7 @@ const TextToComponents = ({
text={part}
textStyle={textStyle}
textSelectable={textSelectable}
- index={index}
+ key={index}
/>
);
}
diff --git a/src/screens/Chapter/components/VerticalScrollbar.js b/src/screens/Chapter/components/VerticalScrollbar.js
index 8f50219f..22114b6d 100644
--- a/src/screens/Chapter/components/VerticalScrollbar.js
+++ b/src/screens/Chapter/components/VerticalScrollbar.js
@@ -11,11 +11,9 @@ const VerticalScrollbar = ({
scrollPercentage,
setLoading,
scrollViewRef,
- setScrollPercentage,
}) => {
const onSlidingComplete = (value) => {
setLoading(true);
- // setScrollPercentage(value);
scrollViewRef.current.scrollTo({
x: 0,
y: Math.round((value * contentSize) / 100),
@@ -31,7 +29,7 @@ const VerticalScrollbar = ({
@@ -90,7 +88,7 @@ const VerticalScrollbar = ({
/>
diff --git a/src/screens/Extension/Extension.js b/src/screens/Extension/Extension.js
index 50d37c02..b7550f11 100644
--- a/src/screens/Extension/Extension.js
+++ b/src/screens/Extension/Extension.js
@@ -20,8 +20,11 @@ const Extension = ({ navigation, route }) => {
const theme = useTheme();
const library = useLibrary();
+
const dispatch = useDispatch();
- const { displayMode } = useSettings();
+
+ const settings = useSettings();
+ const { displayMode } = settings;
const [loading, setLoading] = useState(true);
const [novels, setNovels] = useState([]);
@@ -117,6 +120,8 @@ const Extension = ({ navigation, route }) => {
})
}
libraryStatus={checkIFInLibrary(item.sourceId, item.novelUrl)}
+ settings={settings}
+ theme={theme}
/>
);
diff --git a/src/screens/Library/Library.js b/src/screens/Library/Library.js
index 31970b93..06271bb4 100644
--- a/src/screens/Library/Library.js
+++ b/src/screens/Library/Library.js
@@ -27,11 +27,12 @@ import LibraryFilterSheet from "./components/LibraryFilterSheet";
const LibraryScreen = ({ navigation }) => {
const theme = useTheme();
const dispatch = useDispatch();
+ const settings = useSettings();
const {
showNumberOfNovels,
incognitoMode = false,
updateLibraryOnLaunch = false,
- } = useSettings();
+ } = settings;
const libraryFilterSheetRef = useRef(null);
@@ -79,6 +80,8 @@ const LibraryScreen = ({ navigation }) => {
navigation.navigate("Novel", item);
dispatch(setNovel(item));
}}
+ settings={settings}
+ theme={theme}
/>
),
[]
diff --git a/src/screens/More/DownloadQueue.js b/src/screens/More/DownloadQueue.js
index 00da1c89..c3fba450 100644
--- a/src/screens/More/DownloadQueue.js
+++ b/src/screens/More/DownloadQueue.js
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from "react";
+import React, { useState } from "react";
import { FlatList, View, Text, StyleSheet } from "react-native";
import {
FAB,
@@ -21,7 +21,6 @@ import {
} from "../../redux/downloads/downloads.actions";
import BackgroundService from "react-native-background-actions";
-import { downloadAllChaptersAction } from "../../redux/novel/novel.actions";
import { showToast } from "../../hooks/showToast";
const DownloadQueue = ({ navigation }) => {
@@ -34,13 +33,6 @@ const DownloadQueue = ({ navigation }) => {
const openMenu = () => setVisible(true);
const closeMenu = () => setVisible(false);
- useEffect(() => {
- // if (!BackgroundService.isRunning() && downloadQueue.length > 0) {
- // dispatch(cancelDownload());
- // }
- console.log(downloadQueue);
- }, []);
-
const [fab, setFab] = useState(BackgroundService.isRunning());
return (
@@ -60,9 +52,11 @@ const DownloadQueue = ({ navigation }) => {
>
{
- dispatch(cancelDownload());
- showToast("Downloads cancelled.");
- BackgroundService.stop();
+ if (downloadQueue.length > 0) {
+ dispatch(cancelDownload());
+ showToast("Downloads cancelled.");
+ BackgroundService.stop();
+ }
closeMenu();
}}
title="Cancel downloads"
diff --git a/src/screens/More/Downloads.js b/src/screens/More/Downloads.js
new file mode 100644
index 00000000..0a5cc72a
--- /dev/null
+++ b/src/screens/More/Downloads.js
@@ -0,0 +1,128 @@
+import React, { useCallback, useEffect, useState } from "react";
+import { FlatList, StyleSheet } from "react-native";
+
+import { useDispatch, useSelector } from "react-redux";
+
+import { Appbar } from "../../components/Appbar";
+import { ScreenContainer } from "../../components/Common";
+import EmptyView from "../../components/EmptyView";
+import { LoadingScreen } from "../../components/LoadingScreen/LoadingScreen";
+import { getDownloadedChapters } from "../../database/queries/ChapterQueries";
+
+import { useTheme } from "../../hooks/reduxHooks";
+import {
+ deleteChapterAction,
+ downloadChapterAction,
+} from "../../redux/novel/novel.actions";
+
+import UpdatesItem from "../Updates/components/UpdatesItem";
+
+const Downloads = ({ navigation }) => {
+ const theme = useTheme();
+
+ const dispatch = useDispatch();
+
+ const { downloadQueue } = useSelector((state) => state.downloadsReducer);
+
+ const [loading, setLoading] = useState(true);
+ const [chapters, setChapters] = useState([]);
+
+ const getChapters = async () => {
+ const res = await getDownloadedChapters();
+ setChapters(res);
+ setLoading(false);
+ };
+
+ const ListEmptyComponent = () =>
+ !loading && ;
+
+ useEffect(() => {
+ getChapters();
+ }, []);
+
+ const onPress = useCallback(
+ (item) =>
+ navigation.navigate("Chapter", {
+ chapterId: item.chapterId,
+ chapterUrl: item.chapterUrl,
+ sourceId: item.sourceId,
+ novelUrl: item.novelUrl,
+ chapterName: item.chapterName,
+ novelId: item.novelId,
+ novelName: item.novelName,
+ bookmark: item.bookmark,
+ }),
+ []
+ );
+
+ const onPressCover = useCallback(
+ (item) =>
+ navigation.navigate("Novel", {
+ sourceId: item.sourceId,
+ novelUrl: item.novelUrl,
+ novelName: item.novelName,
+ novelCover: item.novelCover,
+ }),
+ []
+ );
+
+ const downloadChapter = (
+ sourceId,
+ novelUrl,
+ chapterUrl,
+ chapterName,
+ chapterId
+ ) =>
+ dispatch(
+ downloadChapterAction(
+ sourceId,
+ novelUrl,
+ chapterUrl,
+ chapterName,
+ chapterId
+ )
+ );
+
+ const deleteChapter = (chapterId, chapterName) => {
+ dispatch(deleteChapterAction(chapterId, chapterName));
+ setChapters((chapters) =>
+ chapters.filter((item) => item.chapterId !== chapterId)
+ );
+ };
+
+ const renderItem = ({ item }) => (
+ onPress(item)}
+ onPressCover={() => onPressCover(item)}
+ downloadQueue={downloadQueue}
+ deleteChapter={deleteChapter}
+ downloadChapter={downloadChapter}
+ />
+ );
+
+ return (
+
+
+ {loading ? (
+
+ ) : (
+ item.chapterId.toString()}
+ renderItem={renderItem}
+ ListEmptyComponent={}
+ />
+ )}
+
+ );
+};
+
+export default Downloads;
+
+const styles = StyleSheet.create({
+ container: { flex: 1 },
+ flatList: { flexGrow: 1, paddingVertical: 8 },
+});
diff --git a/src/screens/More/GeneralSettings.js b/src/screens/More/GeneralSettings.js
index b01eb76c..287b62cb 100644
--- a/src/screens/More/GeneralSettings.js
+++ b/src/screens/More/GeneralSettings.js
@@ -30,7 +30,8 @@ const GenralSettings = ({ navigation }) => {
(state) => state.settingsReducer
);
- const { updateLibraryOnLaunch = false } = useSettings();
+ const { updateLibraryOnLaunch = false, downloadNewChapters = false } =
+ useSettings();
const displayModeLabel = (displayMode) => {
const label = {
@@ -120,6 +121,20 @@ const GenralSettings = ({ navigation }) => {
}
theme={theme}
/>
+ Auto-download
+
+ dispatch(
+ setAppSettings(
+ "downloadNewChapters",
+ !downloadNewChapters
+ )
+ )
+ }
+ theme={theme}
+ />
Theme
{
downloadQueue.length > 0 &&
downloadQueue.length + " remaining"
}
- icon="download-outline"
+ icon="progress-download"
onPress={() =>
navigation.navigate("MoreStack", {
screen: "DownloadQueue",
@@ -98,6 +98,17 @@ const MoreScreen = ({ navigation }) => {
}
theme={theme}
/>
+
+ navigation.navigate("MoreStack", {
+ screen: "Downloads",
+ })
+ }
+ theme={theme}
+ />
+
{
- const [text, setText] = useState(color);
- const [error, setError] = useState();
-
- return (
-
- {
- hideAccentColorModal();
- setText();
- setError();
- }}
- contentContainerStyle={[
- styles.containerStyle,
- { backgroundColor: theme.colorPrimary },
- ]}
- >
-
- Hex Color Code
-
- setText(text)}
- onSubmitEditing={() => {
- if (text.match(/^#([0-9a-f]{6}|[0-9a-f]{3})$/i)) {
- onPress(text);
- hideAccentColorModal();
- } else {
- setError("Enter a valid hex color code");
- }
- }}
- mode="outlined"
- theme={{
- colors: {
- primary: color,
- placeholder: theme.textColorHint,
- text: theme.textColorPrimary,
- background: "transparent",
- },
- }}
- underlineColor={theme.textColorHint}
- dense
- error={error}
- />
- {error}
-
-
- );
-};
-
-export default AccentColorModal;
-
-const styles = StyleSheet.create({
- containerStyle: {
- margin: 30,
- padding: 20,
- borderRadius: 8,
- },
- dialogTitle: {
- fontSize: 18,
- marginBottom: 16,
- },
-});
diff --git a/src/screens/Novel/Novel.js b/src/screens/Novel/Novel.js
index 2ba08f23..03b10f30 100644
--- a/src/screens/Novel/Novel.js
+++ b/src/screens/Novel/Novel.js
@@ -15,7 +15,9 @@ import { useDispatch, useSelector } from "react-redux";
import {
bookmarkChapterAction,
deleteAllChaptersAction,
+ deleteChapterAction,
downloadAllChaptersAction,
+ downloadChapterAction,
getNovelAction,
markChaptersRead,
markChapterUnreadAction,
@@ -45,7 +47,7 @@ const Novel = ({ route, navigation }) => {
const theme = useTheme();
const dispatch = useDispatch();
- const { novel, chapters, loading, updating, downloading } = useNovel();
+ const { novel, chapters, loading, updating } = useNovel();
const { downloadQueue } = useSelector((state) => state.downloadsReducer);
const [selected, setSelected] = useState([]);
@@ -54,7 +56,6 @@ const Novel = ({ route, navigation }) => {
let flatlistRef = useRef(null);
let chaptersSettingsSheetRef = useRef(null);
let trackerSheetRef = useRef(null);
- let chapterActionsSheetRef = useRef(null);
const {
sort = "ORDER BY chapterId ASC",
@@ -91,25 +92,65 @@ const Novel = ({ route, navigation }) => {
const keyExtractor = useCallback((item) => item.chapterId.toString(), []);
+ const downloadChapter = (chapterUrl, chapterName, chapterId) =>
+ dispatch(
+ downloadChapterAction(
+ sourceId,
+ novelUrl,
+ chapterUrl,
+ chapterName,
+ chapterId
+ )
+ );
+
+ const deleteChapter = (chapterId, chapterName) =>
+ dispatch(deleteChapterAction(chapterId, chapterName));
+
+ const isSelected = (chapterId) => {
+ return selected.some((obj) => obj.chapterId === chapterId);
+ };
+
+ const onSelectPress = (chapter, navigateToChapter) => {
+ if (selected.length === 0) {
+ navigateToChapter();
+ } else {
+ if (isSelected(chapter.chapterId)) {
+ setSelected((selected) =>
+ selected.filter(
+ (item) => item.chapterId !== chapter.chapterId
+ )
+ );
+ } else {
+ setSelected((selected) => [...selected, chapter]);
+ }
+ }
+ };
+
+ const onSelectLongPress = (chapter) => {
+ if (isSelected(chapter.chapterId)) {
+ setSelected((selected) =>
+ selected.filter((item) => item.chapterId !== chapter.chapterId)
+ );
+ } else {
+ setSelected((selected) => [...selected, chapter]);
+ }
+ };
+
const renderItem = ({ item, index }) => (
);
diff --git a/src/screens/Novel/components/Chapter/ChapterDownloadButtons.js b/src/screens/Novel/components/Chapter/ChapterDownloadButtons.js
index 79772bb6..abffd23d 100644
--- a/src/screens/Novel/components/Chapter/ChapterDownloadButtons.js
+++ b/src/screens/Novel/components/Chapter/ChapterDownloadButtons.js
@@ -1,7 +1,57 @@
import React from "react";
import { ActivityIndicator } from "react-native";
-import { IconButton } from "react-native-paper";
+import { IconButton, Menu } from "react-native-paper";
+
+export const DownloadButton = ({
+ downloadQueue,
+ chapter,
+ theme,
+ deleteChapter,
+ downloadChapter,
+ hideDeleteChapterMenu,
+ showDeleteChapterMenu,
+ deleteChapterMenu,
+}) => {
+ if (downloadQueue.some((chap) => chap.chapterId === chapter.chapterId)) {
+ return ;
+ } else if (chapter.downloaded === 1) {
+ return (
+
+ }
+ contentStyle={{ backgroundColor: theme.menuColor }}
+ >
+
+ deleteChapter(chapter.chapterId, chapter.chapterName)
+ }
+ title="Delete"
+ titleStyle={{ color: theme.textColorPrimary }}
+ />
+
+ );
+ } else {
+ return (
+
+ downloadChapter(
+ chapter.chapterUrl,
+ chapter.chapterName,
+ chapter.chapterId
+ )
+ }
+ />
+ );
+ }
+};
export const ChapterDownloadingButton = ({ theme }) => (
{
+ const { sourceId, novelName, novelUrl } = novelData;
const {
novelId,
chapterId,
@@ -39,7 +33,6 @@ const ChapterItem = ({
read,
releaseDate,
bookmark,
- downloaded,
} = chapter;
const [deleteChapterMenu, setDeleteChapterMenu] = useState(false);
@@ -58,84 +51,6 @@ const ChapterItem = ({
bookmark,
});
- const downloadChapter = useCallback(
- () =>
- dispatch(
- downloadChapterAction(
- sourceId,
- novelUrl,
- chapterUrl,
- chapterName,
- chapterId
- )
- ),
- []
- );
-
- const deleteChapter = () =>
- dispatch(deleteChapterAction(chapterId, chapterName));
-
- const renderDownloadIcon = () => {
- if (downloadQueue.some((chap) => chap.chapterId === chapterId)) {
- return ;
- } else if (downloaded === 1) {
- return (
-
- }
- contentStyle={{ backgroundColor: theme.menuColor }}
- >
-
-
- );
- } else {
- return (
-
- );
- }
- };
-
- const isSelected = (chapterId) => {
- return selected.some((obj) => obj.chapterId === chapterId);
- };
-
- const onPress = () => {
- if (selected.length === 0) {
- navigateToChapter();
- } else {
- if (isSelected(chapterId)) {
- setSelected((selected) =>
- selected.filter((item) => item.chapterId !== chapterId)
- );
- } else {
- setSelected((selected) => [...selected, chapter]);
- }
- }
- };
-
- const onLongPress = () => {
- if (isSelected(chapterId)) {
- setSelected((selected) =>
- selected.filter((item) => item.chapterId !== chapterId)
- );
- } else {
- setSelected((selected) => [...selected, chapter]);
- }
- };
-
const renderProgressPercentage = () => {
const savedProgress =
position && position[chapterId] && position[chapterId].percentage;
@@ -164,8 +79,8 @@ const ChapterItem = ({
backgroundColor: theme.rippleColor,
},
]}
- onPress={onPress}
- onLongPress={onLongPress}
+ onPress={() => onSelectPress(chapter, navigateToChapter)}
+ onLongPress={() => onSelectLongPress(chapter)}
android_ripple={{ color: theme.rippleColor }}
>
@@ -208,7 +123,16 @@ const ChapterItem = ({
- {renderDownloadIcon()}
+
);
};
diff --git a/src/screens/Updates/Updates.js b/src/screens/Updates/Updates.js
index 600c0dbe..556c7dc2 100644
--- a/src/screens/Updates/Updates.js
+++ b/src/screens/Updates/Updates.js
@@ -20,11 +20,16 @@ import { Searchbar } from "../../components/Searchbar/Searchbar";
import moment from "moment";
import { dateFormat } from "../../services/utils/constants";
+import {
+ deleteChapterAction,
+ downloadChapterAction,
+} from "../../redux/novel/novel.actions";
const Updates = ({ navigation }) => {
const theme = useTheme();
const dispatch = useDispatch();
const { updates, loading } = useSelector((state) => state.updatesReducer);
+ const { downloadQueue } = useSelector((state) => state.downloadsReducer);
const [searchText, setSearchText] = useState("");
const [searchResults, setSearchResults] = useState([]);
@@ -33,7 +38,7 @@ const Updates = ({ navigation }) => {
useFocusEffect(
useCallback(() => {
dispatch(getUpdatesAction());
- }, [getUpdatesAction])
+ }, [getUpdatesAction, downloadQueue])
);
const onRefresh = async () => {
@@ -68,12 +73,35 @@ const Updates = ({ navigation }) => {
[]
);
+ const downloadChapter = (
+ sourceId,
+ novelUrl,
+ chapterUrl,
+ chapterName,
+ chapterId
+ ) =>
+ dispatch(
+ downloadChapterAction(
+ sourceId,
+ novelUrl,
+ chapterUrl,
+ chapterName,
+ chapterId
+ )
+ );
+
+ const deleteChapter = (chapterId, chapterName) =>
+ dispatch(deleteChapterAction(chapterId, chapterName));
+
const renderItem = ({ item }) => (
onPress(item)}
onPressCover={() => onPressCover(item)}
+ downloadQueue={downloadQueue}
+ deleteChapter={deleteChapter}
+ downloadChapter={downloadChapter}
/>
);
diff --git a/src/screens/Updates/components/Appbar.js b/src/screens/Updates/components/Appbar.js
deleted file mode 100644
index 64d214ac..00000000
--- a/src/screens/Updates/components/Appbar.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from "react";
-import { Appbar as MaterialAppbar } from "react-native-paper";
-
-import { updateLibraryAction } from "../../../redux/updates/updates.actions";
-
-export const Appbar = ({ theme, dispatch }) => (
-
-
- dispatch(updateLibraryAction())}
- />
-
-);
diff --git a/src/screens/Updates/components/DownloadButton.js b/src/screens/Updates/components/DownloadButton.js
new file mode 100644
index 00000000..428ad5bb
--- /dev/null
+++ b/src/screens/Updates/components/DownloadButton.js
@@ -0,0 +1,60 @@
+import React from "react";
+
+import { Menu } from "react-native-paper";
+import {
+ ChapterDownloadingButton,
+ DeleteChapterButton,
+ DownloadChapterButton,
+} from "../../Novel/components/Chapter/ChapterDownloadButtons";
+
+export const DownloadButton = ({
+ downloadQueue,
+ theme,
+ chapter,
+ downloadChapter,
+ deleteChapter,
+ deleteChapterMenu,
+ showDeleteChapterMenu,
+ hideDeleteChapterMenu,
+}) => {
+ if (downloadQueue.some((chap) => chap.chapterId === chapter.chapterId)) {
+ return ;
+ } else if (chapter.downloaded === 1) {
+ return (
+
+ }
+ contentStyle={{ backgroundColor: theme.menuColor }}
+ >
+
+ deleteChapter(chapter.chapterId, chapter.chapterName)
+ }
+ title="Delete"
+ titleStyle={{ color: theme.textColorPrimary }}
+ />
+
+ );
+ } else {
+ return (
+
+ downloadChapter(
+ chapter.sourceId,
+ chapter.novelUrl,
+ chapter.chapterUrl,
+ chapter.chapterName,
+ chapter.chapterId
+ )
+ }
+ />
+ );
+ }
+};
diff --git a/src/screens/Updates/components/UpdatesItem.js b/src/screens/Updates/components/UpdatesItem.js
index 06c79fc8..57eebdbc 100644
--- a/src/screens/Updates/components/UpdatesItem.js
+++ b/src/screens/Updates/components/UpdatesItem.js
@@ -1,8 +1,22 @@
-import React from "react";
+import React, { useState } from "react";
import { View, Text, StyleSheet, Pressable } from "react-native";
-import FastImage from "react-native-fast-image";
-const UpdateCard = ({ item, theme, onPress, onPressCover }) => {
+import FastImage from "react-native-fast-image";
+import { DownloadButton } from "./DownloadButton";
+
+const UpdateCard = ({
+ item,
+ theme,
+ onPress,
+ onPressCover,
+ downloadQueue,
+ downloadChapter,
+ deleteChapter,
+}) => {
+ const [deleteChapterMenu, setDeleteChapterMenu] = useState(false);
+ const showDeleteChapterMenu = () => setDeleteChapterMenu(true);
+ const hideDeleteChapterMenu = () => setDeleteChapterMenu(false);
+
return (
{
style={styles.updateIcon}
/>
-
-
- {item.novelName}
-
-
- {item.chapterName}
-
+
+
+
+ {item.novelName}
+
+
+ {item.chapterName}
+
+
+
>
diff --git a/src/services/updates.js b/src/services/updates.js
index 58f7138b..053bf009 100644
--- a/src/services/updates.js
+++ b/src/services/updates.js
@@ -76,9 +76,16 @@ export const updateNovel = async (sourceId, novelUrl, novelId) => {
});
};
-export const updateNovelChapters = async (sourceId, novelUrl, novelId) => {
+export const updateNovelChapters = async (
+ sourceId,
+ novelUrl,
+ novelId,
+ updateSettings
+) => {
let chapters = await fetchChapters(sourceId, novelUrl);
+ const { downloadNewChapters = false } = updateSettings;
+
db.transaction((tx) => {
chapters.map((chapter) =>
tx.executeSql(
@@ -91,14 +98,14 @@ export const updateNovelChapters = async (sourceId, novelUrl, novelId) => {
],
(txObj, { insertId }) => {
if (insertId !== -1) {
- // console.log(
- // "Inserted Chapter " +
- // chapter.chapterUrl +
- // " Chapter Name " +
- // chapter.chapterName +
- // " Novel Id " +
- // novelId
- // );
+ downloadNewChapters &&
+ downloadChapter(
+ sourceId,
+ novelUrl,
+ chapter.chapterUrl,
+ insertId
+ );
+
tx.executeSql(
"INSERT OR IGNORE INTO updates (chapterId, novelId, updateTime) values (?, ?, (datetime('now','localtime')))",
[insertId, novelId],
@@ -113,7 +120,7 @@ export const updateNovelChapters = async (sourceId, novelUrl, novelId) => {
});
};
-export const updateAllNovels = async () => {
+export const updateAllNovels = async (updateSettings) => {
const libraryNovels = await getLibrary();
const options = {
@@ -149,7 +156,8 @@ export const updateAllNovels = async () => {
await updateNovelChapters(
libraryNovels[i].sourceId,
libraryNovels[i].novelUrl,
- libraryNovels[i].novelId
+ libraryNovels[i].novelId,
+ updateSettings
);
// console.log(libraryNovels[i].novelName + " Updated");
await BackgroundService.updateNotification({
diff --git a/src/sources/multisrc/wpmangastream/WPMangaStreamGenerator.js b/src/sources/multisrc/wpmangastream/WPMangaStreamGenerator.js
new file mode 100644
index 00000000..8139632a
--- /dev/null
+++ b/src/sources/multisrc/wpmangastream/WPMangaStreamGenerator.js
@@ -0,0 +1,13 @@
+import WPMangaStreamScraper from "./WPMangaStreamScraper";
+
+export const KolNovelScraper = new WPMangaStreamScraper(
+ 53,
+ "https://kolnovel.com/",
+ "KolNovel"
+);
+
+export const RewayatArScraper = new WPMangaStreamScraper(
+ 54,
+ "https://rewayat-ar.site/",
+ "RewayatAr"
+);
diff --git a/src/sources/multisrc/wpmangastream/WPMangaStreamScraper.js b/src/sources/multisrc/wpmangastream/WPMangaStreamScraper.js
new file mode 100644
index 00000000..ece1c7b2
--- /dev/null
+++ b/src/sources/multisrc/wpmangastream/WPMangaStreamScraper.js
@@ -0,0 +1,174 @@
+import cheerio from "react-native-cheerio";
+import { htmlToText } from "../../helpers/htmlToText";
+
+class WPMangaStreamScraper {
+ constructor(sourceId, baseUrl, sourceName) {
+ this.sourceId = sourceId;
+ this.baseUrl = baseUrl;
+ this.sourceName = sourceName;
+ }
+
+ async popularNovels(page) {
+ let totalPages = 100;
+ let url = this.baseUrl + "series/?page=" + page + "?m_orderby=popular";
+ let sourceId = this.sourceId;
+
+ const result = await fetch(url);
+ const body = await result.text();
+
+ const $ = cheerio.load(body);
+
+ let novels = [];
+
+ $("article.bs").each(function () {
+ const novelName = $(this).find(".ntitle").text().trim();
+ let image = $(this).find("img");
+ const novelCover = image.attr("src");
+
+ let novelUrl = $(this).find("a").attr("href").split("/")[4];
+
+ const novel = {
+ sourceId,
+ novelName,
+ novelCover,
+ novelUrl,
+ };
+
+ novels.push(novel);
+ });
+
+ return { totalPages, novels };
+ }
+
+ async parseNovelAndChapters(novelUrl) {
+ const url = `${this.baseUrl}series/${novelUrl}/`;
+
+ const result = await fetch(url);
+ const body = await result.text();
+
+ let $ = cheerio.load(body);
+
+ let novel = {};
+
+ novel.sourceId = this.sourceId;
+
+ novel.sourceName = this.sourceName;
+
+ novel.url = url;
+
+ novel.novelUrl = novelUrl;
+
+ novel.novelName = $(".entry-title").text();
+
+ novel.novelCover = $("img.wp-post-image").attr("src");
+
+ $("div.spe > span").each(function () {
+ const detailName = $(this).find("b").text().trim();
+ const detail = $(this).find("b").next().text().trim();
+
+ if ($(this).text().includes("الحالة:")) {
+ novel.status = $(this).text().replace("الحالة: ", "");
+ }
+
+ switch (detailName) {
+ case "المؤلف:":
+ novel.auhtor = detail;
+ break;
+ }
+ });
+
+ novel.genre = $(".genxed").text().replace(/\s/g, ",");
+
+ novel.summary = $('div[itemprop="description"]').text().trim();
+
+ let novelChapters = [];
+
+ $(".eplister")
+ .find("li")
+ .each(function () {
+ const chapterName =
+ $(this).find(".epl-num").text() +
+ " - " +
+ $(this).find(".epl-title").text();
+
+ const releaseDate = $(this).find(".epl-date").text().trim();
+
+ const chapterUrl = $(this).find("a").attr("href").split("/")[3];
+
+ novelChapters.push({ chapterName, releaseDate, chapterUrl });
+ });
+
+ novel.chapters = novelChapters.reverse();
+
+ return novel;
+ }
+
+ async parseChapter(novelUrl, chapterUrl) {
+ let sourceId = this.sourceId;
+
+ const url = `${this.baseUrl}${chapterUrl}`;
+
+ const result = await fetch(url);
+ const body = await result.text();
+
+ const $ = cheerio.load(body);
+
+ let chapterName = $(".entry-title").text();
+
+ let chapterText = $("div.epcontent").text();
+
+ if (chapterText) {
+ chapterText = htmlToText(chapterText);
+ }
+
+ let nextChapter = null;
+ let prevChapter = null;
+
+ const chapter = {
+ sourceId,
+ novelUrl,
+ chapterUrl,
+ chapterName,
+ chapterText,
+ nextChapter,
+ prevChapter,
+ };
+
+ console.log(chapter);
+
+ return chapter;
+ }
+
+ async searchNovels(searchTerm) {
+ const url = `${this.baseUrl}?s=${searchTerm}`;
+
+ const result = await fetch(url);
+ const body = await result.text();
+
+ const $ = cheerio.load(body);
+
+ let novels = [];
+ let sourceId = this.sourceId;
+
+ $("article.bs").each(function () {
+ const novelName = $(this).find(".ntitle").text().trim();
+ let image = $(this).find("img");
+ const novelCover = image.attr("src");
+
+ let novelUrl = $(this).find("a").attr("href").split("/")[4];
+
+ const novel = {
+ sourceId,
+ novelName,
+ novelCover,
+ novelUrl,
+ };
+
+ novels.push(novel);
+ });
+
+ return novels;
+ }
+}
+
+module.exports = WPMangaStreamScraper;
diff --git a/src/sources/sources.js b/src/sources/sources.js
index 74b9b5ce..d2aa4dd8 100644
--- a/src/sources/sources.js
+++ b/src/sources/sources.js
@@ -51,6 +51,10 @@ import WuxiaCityScraper from "./en/wuxiacity";
import NovelUpdatesScraper from "./en/novelupdates";
import RanobesScraper from "./en/ranobes";
import YushuboScraper from "./ch/yushubo";
+import {
+ KolNovelScraper,
+ RewayatArScraper,
+} from "./multisrc/wpmangastream/WPMangaStreamGenerator";
export const getSource = (sourceId) => {
const scrapers = {
@@ -105,6 +109,8 @@ export const getSource = (sourceId) => {
50: NovelUpdatesScraper,
51: RanobesScraper,
52: YushuboScraper,
+ 53: KolNovelScraper,
+ 54: RewayatArScraper,
};
return scrapers[sourceId];
diff --git a/src/sources/sources.json b/src/sources/sources.json
index d695b4be..9e5603e8 100644
--- a/src/sources/sources.json
+++ b/src/sources/sources.json
@@ -355,5 +355,19 @@
"sourceName": "Yushubo",
"icon": "https://github.com/LNReader/lnreader-sources/blob/main/src/ch/yushubo/icon.png?raw=true",
"lang": "Chinese"
+ },
+ {
+ "sourceId": 53,
+ "sourceUrl": "https://kolnovel.com/",
+ "sourceName": "KolNovel",
+ "icon": "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/wpmangastream/icons/kolnovel.png?raw=true",
+ "lang": "Arabic"
+ },
+ {
+ "sourceId": 54,
+ "sourceUrl": "https://rewayat-ar.site/",
+ "sourceName": "RewayatAr",
+ "icon": "https://github.com/LNReader/lnreader-sources/blob/main/multisrc/wpmangastream/icons/rewayatar.png?raw=true",
+ "lang": "Arabic"
}
]