Refactor
This commit is contained in:
@@ -10,6 +10,7 @@ const ColorPickerModal = ({
|
||||
onSubmit,
|
||||
hideModal,
|
||||
modalVisible,
|
||||
showAccentColors,
|
||||
}) => {
|
||||
const [text, setText] = useState(color);
|
||||
const [error, setError] = useState();
|
||||
|
||||
+166
-126
@@ -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 && (
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.title,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
padding: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
);
|
||||
|
||||
const compactTitle = () =>
|
||||
displayMode === 0 && (
|
||||
<View style={styles.titleContainer}>
|
||||
<LinearGradient
|
||||
colors={["transparent", "rgba(0,0,0,0.7)"]}
|
||||
style={styles.linearGradient}
|
||||
>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[styles.title, { color: "rgba(255,255,255,1)" }]}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
</View>
|
||||
);
|
||||
|
||||
const unreadBadge = () =>
|
||||
showUnreadBadges &&
|
||||
item.chaptersUnread && (
|
||||
<Text
|
||||
style={[
|
||||
styles.unreadBadge,
|
||||
!item.chaptersDownloaded && {
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
},
|
||||
!showDownloadBadges && {
|
||||
borderRadius: 4,
|
||||
},
|
||||
{
|
||||
backgroundColor: theme.colorAccent,
|
||||
color: theme.colorButtonText,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.chaptersUnread}
|
||||
</Text>
|
||||
);
|
||||
|
||||
const downloadBadge = () =>
|
||||
showDownloadBadges &&
|
||||
item.chaptersDownloaded && (
|
||||
<Text
|
||||
style={[
|
||||
styles.downloadBadge,
|
||||
!item.chaptersUnread && {
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
},
|
||||
!showUnreadBadges && {
|
||||
borderRadius: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.chaptersDownloaded}
|
||||
</Text>
|
||||
);
|
||||
|
||||
const inLibraryBadge = () =>
|
||||
libraryStatus && (
|
||||
<Text
|
||||
style={[
|
||||
styles.inLibraryBadge,
|
||||
{
|
||||
backgroundColor: theme.colorAccent,
|
||||
color: theme.colorButtonText,
|
||||
borderRadius: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
In library
|
||||
</Text>
|
||||
);
|
||||
const getHeight = useCallback(
|
||||
() => (window.width / getNovelsPerRow()) * (4 / 3),
|
||||
[]
|
||||
);
|
||||
|
||||
return displayMode !== 2 ? (
|
||||
<View
|
||||
@@ -134,18 +44,23 @@ const NovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
onPress={onPress}
|
||||
>
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
zIndex: 1,
|
||||
top: 8,
|
||||
left: 8,
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
{inLibraryBadge()}
|
||||
{downloadBadge()}
|
||||
{unreadBadge()}
|
||||
<View style={styles.badgeContainer}>
|
||||
{libraryStatus && <InLibraryBadge theme={theme} />}
|
||||
{showDownloadBadges && item.chaptersDownloaded && (
|
||||
<DownloadBadge
|
||||
showUnreadBadges={showUnreadBadges}
|
||||
chaptersDownloaded={item.chaptersDownloaded}
|
||||
chaptersUnread={item.chaptersUnread}
|
||||
/>
|
||||
)}
|
||||
{showUnreadBadges && item.chaptersUnread && (
|
||||
<UnreadBadge
|
||||
theme={theme}
|
||||
chaptersDownloaded={item.chaptersDownloaded}
|
||||
chaptersUnread={item.chaptersUnread}
|
||||
showDownloadBadges={showDownloadBadges}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<FastImage
|
||||
source={{ uri: item.novelCover }}
|
||||
@@ -154,26 +69,45 @@ const NovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
libraryStatus && { opacity: 0.5 },
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 4,
|
||||
left: 4,
|
||||
right: 4,
|
||||
}}
|
||||
>
|
||||
{compactTitle()}
|
||||
<View style={styles.compactTitleContainer}>
|
||||
{displayMode === 0 && (
|
||||
<CompactTitle novelName={item.novelName} />
|
||||
)}
|
||||
</View>
|
||||
{comfortableTitle()}
|
||||
{displayMode === 1 && (
|
||||
<ComfortableTitle
|
||||
novelName={item.novelName}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</Pressable>
|
||||
</View>
|
||||
) : (
|
||||
<ListView
|
||||
item={item}
|
||||
downloadBadge={downloadBadge()}
|
||||
unreadBadge={unreadBadge()}
|
||||
inLibraryBadge={inLibraryBadge()}
|
||||
downloadBadge={
|
||||
showDownloadBadges &&
|
||||
item.chaptersDownloaded && (
|
||||
<DownloadBadge
|
||||
showUnreadBadges={showUnreadBadges}
|
||||
chaptersDownloaded={item.chaptersDownloaded}
|
||||
chaptersUnread={item.chaptersUnread}
|
||||
/>
|
||||
)
|
||||
}
|
||||
unreadBadge={
|
||||
showUnreadBadges &&
|
||||
item.chaptersUnread && (
|
||||
<UnreadBadge
|
||||
theme={theme}
|
||||
chaptersDownloaded={item.chaptersDownloaded}
|
||||
chaptersUnread={item.chaptersUnread}
|
||||
showDownloadBadges={showDownloadBadges}
|
||||
/>
|
||||
)
|
||||
}
|
||||
inLibraryBadge={libraryStatus && <InLibraryBadge theme={theme} />}
|
||||
theme={theme}
|
||||
onPress={onPress}
|
||||
/>
|
||||
@@ -182,6 +116,99 @@ const NovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
|
||||
export default NovelCover;
|
||||
|
||||
const ComfortableTitle = ({ theme, novelName }) => (
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.title,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
padding: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{novelName}
|
||||
</Text>
|
||||
);
|
||||
|
||||
const CompactTitle = ({ novelName }) => (
|
||||
<View style={styles.titleContainer}>
|
||||
<LinearGradient
|
||||
colors={["transparent", "rgba(0,0,0,0.7)"]}
|
||||
style={styles.linearGradient}
|
||||
>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[styles.title, { color: "rgba(255,255,255,1)" }]}
|
||||
>
|
||||
{novelName}
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
</View>
|
||||
);
|
||||
|
||||
const InLibraryBadge = ({ theme }) => (
|
||||
<Text
|
||||
style={[
|
||||
styles.inLibraryBadge,
|
||||
{
|
||||
backgroundColor: theme.colorAccent,
|
||||
color: theme.colorButtonText,
|
||||
borderRadius: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
In library
|
||||
</Text>
|
||||
);
|
||||
|
||||
const UnreadBadge = ({
|
||||
chaptersDownloaded,
|
||||
chaptersUnread,
|
||||
showDownloadBadges,
|
||||
theme,
|
||||
}) => (
|
||||
<Text
|
||||
style={[
|
||||
styles.unreadBadge,
|
||||
!chaptersDownloaded && {
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
},
|
||||
!showDownloadBadges && {
|
||||
borderRadius: 4,
|
||||
},
|
||||
{
|
||||
backgroundColor: theme.colorAccent,
|
||||
color: theme.colorButtonText,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{chaptersUnread}
|
||||
</Text>
|
||||
);
|
||||
|
||||
const DownloadBadge = ({
|
||||
chaptersDownloaded,
|
||||
showUnreadBadges,
|
||||
chaptersUnread,
|
||||
}) => (
|
||||
<Text
|
||||
style={[
|
||||
styles.downloadBadge,
|
||||
!chaptersUnread && {
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
},
|
||||
!showUnreadBadges && {
|
||||
borderRadius: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{chaptersDownloaded}
|
||||
</Text>
|
||||
);
|
||||
|
||||
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",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 = () => (
|
||||
<Stack.Screen name="SettingsStack" component={SettingsStack} />
|
||||
<Stack.Screen name="About" component={About} />
|
||||
<Stack.Screen name="DownloadQueue" component={DownloadQueue} />
|
||||
<Stack.Screen name="Downloads" component={Downloads} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -21,11 +21,7 @@ const ExtensionCard = ({ item, theme, isPinned, dispatch, navigation }) => {
|
||||
onPress={navigateToExtension}
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: icon }}
|
||||
style={styles.extensionIcon}
|
||||
// resizeMode="contain"
|
||||
/>
|
||||
<Image source={{ uri: icon }} style={styles.extensionIcon} />
|
||||
<View style={styles.extensionDetails}>
|
||||
<View>
|
||||
<Text
|
||||
|
||||
@@ -3,10 +3,8 @@ import {
|
||||
StyleSheet,
|
||||
View,
|
||||
Text,
|
||||
ActivityIndicator,
|
||||
StatusBar,
|
||||
ScrollView,
|
||||
Image,
|
||||
Pressable,
|
||||
} from "react-native";
|
||||
|
||||
@@ -46,7 +44,6 @@ import { parseChapterNumber } from "../../services/updates";
|
||||
import ChapterAppbar from "./components/ChapterAppbar";
|
||||
import ReaderSheet from "./components/ReaderSheet";
|
||||
import EmptyView from "../../components/EmptyView";
|
||||
import FitImage from "react-native-fit-image";
|
||||
|
||||
import ChapterFooter from "./components/ChapterFooter";
|
||||
import VerticalScrollbar from "./components/VerticalScrollbar";
|
||||
|
||||
@@ -29,7 +29,7 @@ const ReaderSeekbar = ({
|
||||
<View style={styles.verticalSliderContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
color: "#FFFFFF",
|
||||
marginLeft: 16,
|
||||
transform: [{ rotate: "-90deg" }],
|
||||
}}
|
||||
@@ -52,7 +52,7 @@ const ReaderSeekbar = ({
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
color: "#FFFFFF",
|
||||
marginRight: 16,
|
||||
transform: [{ rotate: "-90deg" }],
|
||||
}}
|
||||
@@ -66,7 +66,7 @@ const ReaderSeekbar = ({
|
||||
<View style={styles.horizontalSliderContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
color: "#FFFFFF",
|
||||
marginLeft: 16,
|
||||
}}
|
||||
>
|
||||
@@ -88,7 +88,7 @@ const ReaderSeekbar = ({
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
color: "#FFFFFF",
|
||||
marginRight: 16,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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,
|
||||
}) => (
|
||||
<Text
|
||||
style={[
|
||||
textStyle,
|
||||
@@ -87,8 +94,6 @@ const TextToComponents = ({
|
||||
|
||||
let parseText = text.trim().split(regex);
|
||||
|
||||
console.log("Rerendered");
|
||||
|
||||
return parseText.map((part, index) => {
|
||||
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 (
|
||||
<FitImage source={{ uri: text }} index={index} />
|
||||
);
|
||||
return <FitImage source={{ uri: text }} key={index} />;
|
||||
case "hr":
|
||||
return (
|
||||
<HorizontalRule
|
||||
index={index}
|
||||
textColor={textColor}
|
||||
/>
|
||||
<HorizontalRule key={index} textColor={textColor} />
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -218,7 +220,7 @@ const TextToComponents = ({
|
||||
text={part}
|
||||
textStyle={textStyle}
|
||||
textSelectable={textSelectable}
|
||||
index={index}
|
||||
key={index}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 = ({
|
||||
<View style={styles.verticalSliderContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
color: "#FFFFFF",
|
||||
marginLeft: 16,
|
||||
transform: [{ rotate: "-90deg" }],
|
||||
}}
|
||||
@@ -50,11 +48,11 @@ const VerticalScrollbar = ({
|
||||
onSlidingComplete={onSlidingComplete}
|
||||
thumbTintColor={theme.colorAccent}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor={theme.textColorPrimary}
|
||||
maximumTrackTintColor="#FFFFFF"
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
color: "#FFFFFF",
|
||||
marginRight: 16,
|
||||
transform: [{ rotate: "-90deg" }],
|
||||
}}
|
||||
@@ -68,7 +66,7 @@ const VerticalScrollbar = ({
|
||||
<View style={styles.horizontalSliderContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
color: "#FFFFFF",
|
||||
marginLeft: 16,
|
||||
}}
|
||||
>
|
||||
@@ -90,7 +88,7 @@ const VerticalScrollbar = ({
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
color: "#FFFFFF",
|
||||
marginRight: 16,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
|
||||
@@ -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 }) => {
|
||||
>
|
||||
<Menu.Item
|
||||
onPress={() => {
|
||||
dispatch(cancelDownload());
|
||||
showToast("Downloads cancelled.");
|
||||
BackgroundService.stop();
|
||||
if (downloadQueue.length > 0) {
|
||||
dispatch(cancelDownload());
|
||||
showToast("Downloads cancelled.");
|
||||
BackgroundService.stop();
|
||||
}
|
||||
closeMenu();
|
||||
}}
|
||||
title="Cancel downloads"
|
||||
|
||||
@@ -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 && <EmptyView icon="(˘・_・˘)" description="No downloads" />;
|
||||
|
||||
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 }) => (
|
||||
<UpdatesItem
|
||||
item={item}
|
||||
theme={theme}
|
||||
onPress={() => onPress(item)}
|
||||
onPressCover={() => onPressCover(item)}
|
||||
downloadQueue={downloadQueue}
|
||||
deleteChapter={deleteChapter}
|
||||
downloadChapter={downloadChapter}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<ScreenContainer theme={theme}>
|
||||
<Appbar title="Downloads" onBackAction={navigation.goBack} />
|
||||
{loading ? (
|
||||
<LoadingScreen theme={theme} />
|
||||
) : (
|
||||
<FlatList
|
||||
contentContainerStyle={styles.flatList}
|
||||
data={chapters}
|
||||
keyExtractor={(item) => item.chapterId.toString()}
|
||||
renderItem={renderItem}
|
||||
ListEmptyComponent={<ListEmptyComponent />}
|
||||
/>
|
||||
)}
|
||||
</ScreenContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Downloads;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1 },
|
||||
flatList: { flexGrow: 1, paddingVertical: 8 },
|
||||
});
|
||||
@@ -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}
|
||||
/>
|
||||
<ListSubHeader theme={theme}>Auto-download</ListSubHeader>
|
||||
<SwitchSetting
|
||||
label="Download new chapters"
|
||||
value={downloadNewChapters}
|
||||
onPress={() =>
|
||||
dispatch(
|
||||
setAppSettings(
|
||||
"downloadNewChapters",
|
||||
!downloadNewChapters
|
||||
)
|
||||
)
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<ListSubHeader theme={theme}>Theme</ListSubHeader>
|
||||
<ListItem
|
||||
title="Theme"
|
||||
|
||||
@@ -90,7 +90,7 @@ const MoreScreen = ({ navigation }) => {
|
||||
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}
|
||||
/>
|
||||
<ListItem
|
||||
title="Downloads"
|
||||
icon="folder-download"
|
||||
onPress={() =>
|
||||
navigation.navigate("MoreStack", {
|
||||
screen: "Downloads",
|
||||
})
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
|
||||
<ListItem
|
||||
title="Settings"
|
||||
icon="cog-outline"
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { StyleSheet, Text } from "react-native";
|
||||
import { Modal, Portal, TextInput } from "react-native-paper";
|
||||
|
||||
const AccentColorModal = ({
|
||||
accentColorModal,
|
||||
hideAccentColorModal,
|
||||
theme,
|
||||
onPress,
|
||||
color,
|
||||
}) => {
|
||||
const [text, setText] = useState(color);
|
||||
const [error, setError] = useState();
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={accentColorModal}
|
||||
onDismiss={() => {
|
||||
hideAccentColorModal();
|
||||
setText();
|
||||
setError();
|
||||
}}
|
||||
contentContainerStyle={[
|
||||
styles.containerStyle,
|
||||
{ backgroundColor: theme.colorPrimary },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.dialogTitle,
|
||||
{ color: theme.textColorPrimary },
|
||||
]}
|
||||
>
|
||||
Hex Color Code
|
||||
</Text>
|
||||
<TextInput
|
||||
value={text}
|
||||
defaultValue={color}
|
||||
onChangeText={(text) => 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}
|
||||
/>
|
||||
<Text style={{ color: "#FF0033", paddingTop: 8 }}>{error}</Text>
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccentColorModal;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
containerStyle: {
|
||||
margin: 30,
|
||||
padding: 20,
|
||||
borderRadius: 8,
|
||||
},
|
||||
dialogTitle: {
|
||||
fontSize: 18,
|
||||
marginBottom: 16,
|
||||
},
|
||||
});
|
||||
+53
-12
@@ -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 }) => (
|
||||
<ChapterItem
|
||||
theme={theme}
|
||||
chapter={item}
|
||||
index={index}
|
||||
novelUrl={novelUrl}
|
||||
dispatch={dispatch}
|
||||
sourceId={sourceId}
|
||||
novelData={{ sourceId, novelUrl, novelName }}
|
||||
navigation={navigation}
|
||||
lastReadChapter={lastReadChapter}
|
||||
position={position}
|
||||
downloading={downloading}
|
||||
selected={selected}
|
||||
novelName={novelName}
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
showChapterTitles={showChapterTitles}
|
||||
chapterActionsSheetRef={chapterActionsSheetRef}
|
||||
downloadQueue={downloadQueue}
|
||||
deleteChapter={deleteChapter}
|
||||
downloadChapter={downloadChapter}
|
||||
isSelected={isSelected}
|
||||
onSelectPress={onSelectPress}
|
||||
onSelectLongPress={onSelectLongPress}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -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 <ChapterDownloadingButton theme={theme} />;
|
||||
} else if (chapter.downloaded === 1) {
|
||||
return (
|
||||
<Menu
|
||||
visible={deleteChapterMenu}
|
||||
onDismiss={hideDeleteChapterMenu}
|
||||
anchor={
|
||||
<DeleteChapterButton
|
||||
theme={theme}
|
||||
onPress={showDeleteChapterMenu}
|
||||
/>
|
||||
}
|
||||
contentStyle={{ backgroundColor: theme.menuColor }}
|
||||
>
|
||||
<Menu.Item
|
||||
onPress={() =>
|
||||
deleteChapter(chapter.chapterId, chapter.chapterName)
|
||||
}
|
||||
title="Delete"
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<DownloadChapterButton
|
||||
theme={theme}
|
||||
onPress={() =>
|
||||
downloadChapter(
|
||||
chapter.chapterUrl,
|
||||
chapter.chapterName,
|
||||
chapter.chapterId
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const ChapterDownloadingButton = ({ theme }) => (
|
||||
<ActivityIndicator
|
||||
|
||||
@@ -1,36 +1,30 @@
|
||||
import React, { memo, useCallback, useState } from "react";
|
||||
import React, { memo, useState } from "react";
|
||||
import { View, Text, StyleSheet, Pressable } from "react-native";
|
||||
|
||||
import { Menu } from "react-native-paper";
|
||||
import { Row } from "../../../components/Common";
|
||||
|
||||
import {
|
||||
deleteChapterAction,
|
||||
downloadChapterAction,
|
||||
} from "../../../redux/novel/novel.actions";
|
||||
import { parseChapterNumber } from "../../../services/utils/helpers";
|
||||
import {
|
||||
ChapterBookmarkButton,
|
||||
ChapterDownloadingButton,
|
||||
DeleteChapterButton,
|
||||
DownloadChapterButton,
|
||||
DownloadButton,
|
||||
} from "./Chapter/ChapterDownloadButtons";
|
||||
|
||||
const ChapterItem = ({
|
||||
novelUrl,
|
||||
novelName,
|
||||
novelData,
|
||||
chapter,
|
||||
sourceId,
|
||||
dispatch,
|
||||
theme,
|
||||
navigation,
|
||||
index,
|
||||
position,
|
||||
selected,
|
||||
setSelected,
|
||||
showChapterTitles,
|
||||
downloadQueue,
|
||||
downloadChapter,
|
||||
deleteChapter,
|
||||
isSelected,
|
||||
onSelectPress,
|
||||
onSelectLongPress,
|
||||
}) => {
|
||||
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 <ChapterDownloadingButton theme={theme} />;
|
||||
} else if (downloaded === 1) {
|
||||
return (
|
||||
<Menu
|
||||
visible={deleteChapterMenu}
|
||||
onDismiss={hideDeleteChapterMenu}
|
||||
anchor={
|
||||
<DeleteChapterButton
|
||||
theme={theme}
|
||||
onPress={showDeleteChapterMenu}
|
||||
/>
|
||||
}
|
||||
contentStyle={{ backgroundColor: theme.menuColor }}
|
||||
>
|
||||
<Menu.Item
|
||||
onPress={deleteChapter}
|
||||
title="Delete"
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<DownloadChapterButton
|
||||
theme={theme}
|
||||
onPress={downloadChapter}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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 }}
|
||||
>
|
||||
<Row>
|
||||
@@ -208,7 +123,16 @@ const ChapterItem = ({
|
||||
</View>
|
||||
</View>
|
||||
</Row>
|
||||
<View>{renderDownloadIcon()}</View>
|
||||
<DownloadButton
|
||||
downloadQueue={downloadQueue}
|
||||
chapter={chapter}
|
||||
theme={theme}
|
||||
deleteChapter={deleteChapter}
|
||||
downloadChapter={downloadChapter}
|
||||
hideDeleteChapterMenu={hideDeleteChapterMenu}
|
||||
showDeleteChapterMenu={showDeleteChapterMenu}
|
||||
deleteChapterMenu={deleteChapterMenu}
|
||||
/>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 }) => (
|
||||
<UpdatesItem
|
||||
item={item}
|
||||
theme={theme}
|
||||
onPress={() => onPress(item)}
|
||||
onPressCover={() => onPressCover(item)}
|
||||
downloadQueue={downloadQueue}
|
||||
deleteChapter={deleteChapter}
|
||||
downloadChapter={downloadChapter}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -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 }) => (
|
||||
<MaterialAppbar.Header style={{ backgroundColor: theme.colorPrimary }}>
|
||||
<MaterialAppbar.Content
|
||||
title="Updates"
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
/>
|
||||
<MaterialAppbar.Action
|
||||
icon="reload"
|
||||
onPress={() => dispatch(updateLibraryAction())}
|
||||
/>
|
||||
</MaterialAppbar.Header>
|
||||
);
|
||||
@@ -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 <ChapterDownloadingButton theme={theme} />;
|
||||
} else if (chapter.downloaded === 1) {
|
||||
return (
|
||||
<Menu
|
||||
visible={deleteChapterMenu}
|
||||
onDismiss={hideDeleteChapterMenu}
|
||||
anchor={
|
||||
<DeleteChapterButton
|
||||
theme={theme}
|
||||
onPress={showDeleteChapterMenu}
|
||||
/>
|
||||
}
|
||||
contentStyle={{ backgroundColor: theme.menuColor }}
|
||||
>
|
||||
<Menu.Item
|
||||
onPress={() =>
|
||||
deleteChapter(chapter.chapterId, chapter.chapterName)
|
||||
}
|
||||
title="Delete"
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<DownloadChapterButton
|
||||
theme={theme}
|
||||
onPress={() =>
|
||||
downloadChapter(
|
||||
chapter.sourceId,
|
||||
chapter.novelUrl,
|
||||
chapter.chapterUrl,
|
||||
chapter.chapterName,
|
||||
chapter.chapterId
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -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 (
|
||||
<Pressable
|
||||
style={styles.updateCard}
|
||||
@@ -16,29 +30,48 @@ const UpdateCard = ({ item, theme, onPress, onPressCover }) => {
|
||||
style={styles.updateIcon}
|
||||
/>
|
||||
</Pressable>
|
||||
<View style={styles.chapterDetails}>
|
||||
<Text
|
||||
style={[
|
||||
{ color: theme.textColorPrimary, fontSize: 14 },
|
||||
item.read && {
|
||||
color: theme.textColorHint,
|
||||
},
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
{ color: theme.textColorPrimary, fontSize: 12 },
|
||||
item.read && {
|
||||
color: theme.textColorHint,
|
||||
},
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.chapterName}
|
||||
</Text>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<View style={styles.chapterDetails}>
|
||||
<Text
|
||||
style={[
|
||||
{ color: theme.textColorPrimary, fontSize: 14 },
|
||||
item.read && {
|
||||
color: theme.textColorHint,
|
||||
},
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
{ color: theme.textColorPrimary, fontSize: 12 },
|
||||
item.read && {
|
||||
color: theme.textColorHint,
|
||||
},
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.chapterName}
|
||||
</Text>
|
||||
</View>
|
||||
<DownloadButton
|
||||
chapter={item}
|
||||
deleteChapter={deleteChapter}
|
||||
downloadChapter={downloadChapter}
|
||||
downloadQueue={downloadQueue}
|
||||
showDeleteChapterMenu={showDeleteChapterMenu}
|
||||
deleteChapterMenu={deleteChapterMenu}
|
||||
hideDeleteChapterMenu={hideDeleteChapterMenu}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
</Pressable>
|
||||
|
||||
+19
-11
@@ -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({
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
@@ -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;
|
||||
@@ -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];
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user