Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fb35e2d32 | |||
| 35d5cf987a | |||
| 7e28d12a8a | |||
| ff575de2e8 | |||
| 962fcc534d | |||
| acd8843b0e | |||
| c956d764c5 | |||
| ca0f62f118 | |||
| 4d766555b7 | |||
| edcc4c0bcd | |||
| 8e1a9274e5 | |||
| d0b11c9b47 | |||
| 3a9784a7a0 | |||
| 66e3945427 | |||
| 7773d50607 | |||
| 721d3842fb | |||
| 773c33f3bf | |||
| 1addb54690 | |||
| ce29a5456d | |||
| 4eda2d4624 | |||
| bae2fa40f3 | |||
| cf5d9e18bb | |||
| e87ecb48b4 | |||
| d4140fcc52 | |||
| 4858f22a88 | |||
| 6aa977a562 |
@@ -7,10 +7,10 @@ import { Provider } from "react-redux";
|
||||
import { persistor, store } from "./src/redux/store";
|
||||
import { PersistGate } from "redux-persist/lib/integration/react";
|
||||
|
||||
import Main from "./src/Navigators/Main";
|
||||
import Main from "./src/navigators/Main";
|
||||
|
||||
import { createDB, deleteDb } from "./src/Database/DBHelper";
|
||||
import { fonts } from "./src/Theme/fonts";
|
||||
import { createDB, deleteDb } from "./src/database/DBHelper";
|
||||
import { fonts } from "./src/theme/fonts";
|
||||
|
||||
const App = () => {
|
||||
const [loaded] = useFonts(fonts);
|
||||
|
||||
@@ -136,7 +136,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0.21"
|
||||
versionName "1.1.0"
|
||||
}
|
||||
splits {
|
||||
abi {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
||||
@@ -32,5 +33,6 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
|
||||
<service android:name="com.asterinet.react.bgactions.RNBackgroundActionsTask" />
|
||||
</application>
|
||||
</manifest>
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
@@ -3,7 +3,7 @@
|
||||
"name": "LNReader",
|
||||
"slug": "LNReader",
|
||||
"scheme": "lnreader",
|
||||
"version": "1.0.21",
|
||||
"version": "1.1.0",
|
||||
"orientation": "default",
|
||||
"icon": "./assets/icon.png",
|
||||
"splash": {
|
||||
|
||||
Generated
+18
@@ -3534,6 +3534,11 @@
|
||||
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-1.0.5.tgz",
|
||||
"integrity": "sha1-+YNhBRexc3wLncZDvsqTiTwE3xg="
|
||||
},
|
||||
"eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
|
||||
},
|
||||
"exec-sh": {
|
||||
"version": "0.3.6",
|
||||
"resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz",
|
||||
@@ -6827,6 +6832,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-native-background-actions": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-background-actions/-/react-native-background-actions-2.6.1.tgz",
|
||||
"integrity": "sha512-4YQSEq7Y+qLKU9BxuzMG0DEIeZkvD54jTqA1IKai/w3sV+hc4cJNoSjZScRj1+663oWrEAZwLUk2I8P5QRvgiQ==",
|
||||
"requires": {
|
||||
"eventemitter3": "^4.0.7"
|
||||
}
|
||||
},
|
||||
"react-native-cheerio": {
|
||||
"version": "1.0.0-rc.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native-cheerio/-/react-native-cheerio-1.0.0-rc.4.tgz",
|
||||
@@ -7005,6 +7018,11 @@
|
||||
"ieee754": "^1.1.8"
|
||||
}
|
||||
},
|
||||
"react-native-swipe-gestures": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz",
|
||||
"integrity": "sha512-Ns7Bn9H/Tyw278+5SQx9oAblDZ7JixyzeOczcBK8dipQk2pD7Djkcfnf1nB/8RErAmMLL9iXgW0QHqiII8AhKw=="
|
||||
},
|
||||
"react-native-tab-view": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-3.0.1.tgz",
|
||||
|
||||
+71
-69
@@ -1,71 +1,73 @@
|
||||
{
|
||||
"scripts": {
|
||||
"start": "react-native start",
|
||||
"android": "react-native run-android",
|
||||
"ios": "react-native run-ios",
|
||||
"web": "expo start --web",
|
||||
"eject": "expo eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-async-storage/async-storage": "^1.13.0",
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
"@react-native-community/netinfo": "6.0.0",
|
||||
"@react-native-community/slider": "3.0.3",
|
||||
"@react-navigation/material-bottom-tabs": "^5.3.10",
|
||||
"@react-navigation/native": "^5.8.10",
|
||||
"@react-navigation/stack": "^5.12.8",
|
||||
"expo": "^41.0.1",
|
||||
"expo-background-fetch": "~9.1.0",
|
||||
"expo-document-picker": "^9.1.2",
|
||||
"expo-file-system": "~11.0.2",
|
||||
"expo-font": "~9.1.0",
|
||||
"expo-linear-gradient": "~9.1.0",
|
||||
"expo-linking": "~2.2.3",
|
||||
"expo-notifications": "~0.11.6",
|
||||
"expo-permissions": "^12.0.1",
|
||||
"expo-sharing": "~9.1.2",
|
||||
"expo-splash-screen": "~0.10.2",
|
||||
"expo-sqlite": "~9.1.0",
|
||||
"expo-status-bar": "~1.0.4",
|
||||
"expo-task-manager": "~9.1.0",
|
||||
"expo-updates": "~0.5.4",
|
||||
"expo-web-browser": "~9.1.0",
|
||||
"moment": "^2.29.1",
|
||||
"qs": "^6.10.1",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"react-native": "^0.64.2",
|
||||
"react-native-cheerio": "^1.0.0-rc.4",
|
||||
"react-native-collapsible-header-views": "^1.1.2",
|
||||
"react-native-device-info": "^8.1.3",
|
||||
"react-native-easing-gradient": "^0.1.2",
|
||||
"react-native-fast-image": "^8.3.4",
|
||||
"react-native-fit-image": "^1.5.5",
|
||||
"react-native-gesture-handler": "~1.10.2",
|
||||
"react-native-immersive-mode": "^2.0.0",
|
||||
"react-native-navigation-bar-color": "^2.0.1",
|
||||
"react-native-offline": "^5.7.0",
|
||||
"react-native-pager-view": "^5.1.10",
|
||||
"react-native-paper": "^4.5.0",
|
||||
"react-native-reanimated": "~2.1.0",
|
||||
"react-native-safe-area-context": "3.2.0",
|
||||
"react-native-screens": "~3.0.0",
|
||||
"react-native-tab-view": "^3.0.1",
|
||||
"react-native-unimodules": "~0.13.3",
|
||||
"react-native-vector-icons": "^7.1.0",
|
||||
"react-native-web": "^0.16.5",
|
||||
"react-native-webview": "^11.6.2",
|
||||
"react-redux": "^7.2.2",
|
||||
"redux": "^4.0.5",
|
||||
"redux-devtools-extension": "^2.13.9",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"rn-sliding-up-panel": "^2.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "~7.9.0"
|
||||
},
|
||||
"private": true,
|
||||
"name": "lnreader-eject",
|
||||
"version": "1.0.0"
|
||||
"scripts": {
|
||||
"start": "react-native start",
|
||||
"android": "react-native run-android",
|
||||
"ios": "react-native run-ios",
|
||||
"web": "expo start --web",
|
||||
"eject": "expo eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-async-storage/async-storage": "^1.13.0",
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
"@react-native-community/netinfo": "6.0.0",
|
||||
"@react-native-community/slider": "3.0.3",
|
||||
"@react-navigation/material-bottom-tabs": "^5.3.10",
|
||||
"@react-navigation/native": "^5.8.10",
|
||||
"@react-navigation/stack": "^5.12.8",
|
||||
"expo": "^41.0.1",
|
||||
"expo-background-fetch": "~9.1.0",
|
||||
"expo-document-picker": "^9.1.2",
|
||||
"expo-file-system": "~11.0.2",
|
||||
"expo-font": "~9.1.0",
|
||||
"expo-linear-gradient": "~9.1.0",
|
||||
"expo-linking": "~2.2.3",
|
||||
"expo-notifications": "~0.11.6",
|
||||
"expo-permissions": "^12.0.1",
|
||||
"expo-sharing": "~9.1.2",
|
||||
"expo-splash-screen": "~0.10.2",
|
||||
"expo-sqlite": "~9.1.0",
|
||||
"expo-status-bar": "~1.0.4",
|
||||
"expo-task-manager": "~9.1.0",
|
||||
"expo-updates": "~0.5.4",
|
||||
"expo-web-browser": "~9.1.0",
|
||||
"moment": "^2.29.1",
|
||||
"qs": "^6.10.1",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"react-native": "^0.64.2",
|
||||
"react-native-background-actions": "^2.6.1",
|
||||
"react-native-cheerio": "^1.0.0-rc.4",
|
||||
"react-native-collapsible-header-views": "^1.1.2",
|
||||
"react-native-device-info": "^8.1.3",
|
||||
"react-native-easing-gradient": "^0.1.2",
|
||||
"react-native-fast-image": "^8.3.4",
|
||||
"react-native-fit-image": "^1.5.5",
|
||||
"react-native-gesture-handler": "~1.10.2",
|
||||
"react-native-immersive-mode": "^2.0.0",
|
||||
"react-native-navigation-bar-color": "^2.0.1",
|
||||
"react-native-offline": "^5.7.0",
|
||||
"react-native-pager-view": "^5.1.10",
|
||||
"react-native-paper": "^4.5.0",
|
||||
"react-native-reanimated": "^2.1.0",
|
||||
"react-native-safe-area-context": "3.2.0",
|
||||
"react-native-screens": "~3.0.0",
|
||||
"react-native-swipe-gestures": "^1.0.5",
|
||||
"react-native-tab-view": "^3.0.1",
|
||||
"react-native-unimodules": "~0.13.3",
|
||||
"react-native-vector-icons": "^7.1.0",
|
||||
"react-native-web": "^0.16.5",
|
||||
"react-native-webview": "^11.6.2",
|
||||
"react-redux": "^7.2.2",
|
||||
"redux": "^4.0.5",
|
||||
"redux-devtools-extension": "^2.13.9",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"rn-sliding-up-panel": "^2.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "~7.9.0"
|
||||
},
|
||||
"private": true,
|
||||
"name": "lnreader-eject",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { Appbar } from "react-native-paper";
|
||||
import {
|
||||
getNextChapterFromDB,
|
||||
getPrevChapterFromDB,
|
||||
} from "../../../Database/queries/ChapterQueries";
|
||||
|
||||
const ChapterAppbar = ({
|
||||
navigation,
|
||||
sourceId,
|
||||
novelId,
|
||||
novelName,
|
||||
novelUrl,
|
||||
chapterId,
|
||||
chapterName,
|
||||
theme,
|
||||
readerSheetRef,
|
||||
}) => {
|
||||
const [nextChapter, setNextChapter] = useState({});
|
||||
const [prevChapter, setPrevChapter] = useState({});
|
||||
|
||||
const setPrevAndNextChap = async () => {
|
||||
const nextChap = await getNextChapterFromDB(novelId, chapterId);
|
||||
const prevChap = await getPrevChapterFromDB(novelId, chapterId);
|
||||
|
||||
setNextChapter(nextChap);
|
||||
setPrevChapter(prevChap);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setPrevAndNextChap();
|
||||
}, []);
|
||||
|
||||
const navigateToPrevChapter = () =>
|
||||
navigation.replace("Chapter", {
|
||||
chapterUrl: prevChapter.chapterUrl,
|
||||
chapterId: prevChapter.chapterId,
|
||||
sourceId,
|
||||
novelUrl,
|
||||
novelId,
|
||||
chapterName: prevChapter.chapterName,
|
||||
novelName,
|
||||
});
|
||||
|
||||
const navigateToNextChapter = () =>
|
||||
navigation.replace("Chapter", {
|
||||
chapterUrl: nextChapter.chapterUrl,
|
||||
sourceId,
|
||||
novelUrl,
|
||||
novelId,
|
||||
chapterId: nextChapter.chapterId,
|
||||
chapterName: nextChapter.chapterName,
|
||||
novelName,
|
||||
});
|
||||
|
||||
return (
|
||||
<Appbar.Header style={{ backgroundColor: "transparent", elevation: 0 }}>
|
||||
<Appbar.BackAction
|
||||
onPress={() => navigation.goBack()}
|
||||
color="#FFFFFF"
|
||||
size={26}
|
||||
style={{ marginRight: 0 }}
|
||||
/>
|
||||
<Appbar.Content
|
||||
title={novelName}
|
||||
titleStyle={{ color: "#FFFFFF" }}
|
||||
subtitle={chapterName}
|
||||
subtitleStyle={{ color: "rgba(255, 255, 255, 0.7)" }}
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon="chevron-left"
|
||||
size={26}
|
||||
disabled={!prevChapter}
|
||||
onPress={navigateToPrevChapter}
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon="chevron-right"
|
||||
size={26}
|
||||
disabled={!nextChapter}
|
||||
onPress={navigateToNextChapter}
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon="dots-vertical"
|
||||
size={26}
|
||||
onPress={() => readerSheetRef.current.show()}
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
</Appbar.Header>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChapterAppbar;
|
||||
@@ -1,66 +0,0 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View, Image } from "react-native";
|
||||
|
||||
import { Appbar } from "../../Components/Appbar";
|
||||
import { ListItem, ListSection } from "../../Components/List";
|
||||
|
||||
import { useTheme } from "../../Hooks/reduxHooks";
|
||||
|
||||
const MoreScreen = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar title="More" style={{ elevation: 0 }} />
|
||||
<View style={{ flex: 1, backgroundColor: theme.colorPrimaryDark }}>
|
||||
<View style={{ overflow: "hidden", paddingBottom: 4 }}>
|
||||
<View
|
||||
style={{
|
||||
paddingTop: 10,
|
||||
paddingBottom: 25,
|
||||
alignItems: "center",
|
||||
backgroundColor: theme.colorPrimary,
|
||||
elevation: 4,
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={require("../../../assets/logo.png")}
|
||||
style={{
|
||||
height: 80,
|
||||
width: 80,
|
||||
tintColor: theme.textColorPrimary,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<ListSection>
|
||||
<ListItem
|
||||
title="Settings"
|
||||
icon="cog-outline"
|
||||
onPress={() =>
|
||||
navigation.navigate("MoreStack", {
|
||||
screen: "SettingsStack",
|
||||
})
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
|
||||
<ListItem
|
||||
title="About"
|
||||
icon="information-outline"
|
||||
onPress={() =>
|
||||
navigation.navigate("MoreStack", {
|
||||
screen: "About",
|
||||
})
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
</ListSection>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MoreScreen;
|
||||
|
||||
const styles = StyleSheet.create({});
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { StyleSheet, View } from "react-native";
|
||||
|
||||
import { List, Divider as PaperDivider } from "react-native-paper";
|
||||
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
@@ -34,15 +34,19 @@ const ListItem = ({
|
||||
description={description}
|
||||
descriptionStyle={{ color: theme.textColorSecondary }}
|
||||
descriptionNumberOfLines={1}
|
||||
left={() =>
|
||||
icon && (
|
||||
<List.Icon
|
||||
color={theme.colorAccent}
|
||||
icon={icon}
|
||||
style={{ marginVertical: 0 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
left={() => (
|
||||
<View style={{ justifyContent: "center" }}>
|
||||
{icon && (
|
||||
<List.Icon
|
||||
color={theme.colorAccent}
|
||||
icon={icon}
|
||||
style={{
|
||||
marginVertical: 0,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
right={() =>
|
||||
right && (
|
||||
<MaterialCommunityIcons
|
||||
@@ -2,7 +2,7 @@ import React, { useState } from "react";
|
||||
import { StyleSheet, View, Text } from "react-native";
|
||||
import { Portal, Modal, Button } from "react-native-paper";
|
||||
import * as Linking from "expo-linking";
|
||||
import { useTheme } from "../Hooks/reduxHooks";
|
||||
import { useTheme } from "../hooks/reduxHooks";
|
||||
|
||||
const NewUpdateDialog = ({ newVersion }) => {
|
||||
const [newUpdateDialog, showNewUpdateDialog] = useState(true);
|
||||
@@ -4,10 +4,10 @@ import { LinearGradient } from "expo-linear-gradient";
|
||||
import { TouchableRipple } from "react-native-paper";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
import { useSettings, useTheme } from "../Hooks/reduxHooks";
|
||||
import { useSettings, useTheme } from "../hooks/reduxHooks";
|
||||
import ListView from "./ListView";
|
||||
import FastImage from "react-native-fast-image";
|
||||
import { getDeviceOrientation } from "../Services/utils/helpers";
|
||||
import { getDeviceOrientation } from "../services/utils/helpers";
|
||||
|
||||
const NovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
const theme = useTheme();
|
||||
@@ -1,14 +1,17 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, FlatList } from "react-native";
|
||||
|
||||
import { useSettings } from "../Hooks/reduxHooks";
|
||||
import { getDeviceOrientation } from "../Services/utils/helpers";
|
||||
import { useSettings } from "../hooks/reduxHooks";
|
||||
import { getDeviceOrientation } from "../services/utils/helpers";
|
||||
|
||||
const NovelList = ({
|
||||
data,
|
||||
onScroll,
|
||||
onEndReached,
|
||||
renderItem,
|
||||
refreshControl,
|
||||
ListEmptyComponent,
|
||||
ListFooterComponent,
|
||||
}) => {
|
||||
const { displayMode, novelsPerRow } = useSettings();
|
||||
|
||||
@@ -36,6 +39,9 @@ const NovelList = ({
|
||||
renderItem={renderItem}
|
||||
refreshControl={refreshControl}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
ListFooterComponent={ListFooterComponent}
|
||||
onScroll={onScroll}
|
||||
onEndReached={onEndReached}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as SQLite from "expo-sqlite";
|
||||
import { getSource } from "../../sources/sources";
|
||||
const db = SQLite.openDatabase("lnreader.db");
|
||||
|
||||
const insertChaptersQuery = `INSERT INTO chapters (chapterUrl, chapterName, releaseDate, novelId) values (?, ?, ?, ?)`;
|
||||
@@ -157,10 +158,9 @@ export const downloadChapter = async (
|
||||
chapterUrl,
|
||||
chapterId
|
||||
) => {
|
||||
const downloadUrl = `https://lnreader-extensions.vercel.app/api/${sourceId}/novel/${novelUrl}${chapterUrl}`;
|
||||
const source = getSource(sourceId);
|
||||
|
||||
const response = await fetch(downloadUrl);
|
||||
const chapter = await response.json();
|
||||
const chapter = await source.parseChapter(novelUrl, chapterUrl);
|
||||
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as SQLite from "expo-sqlite";
|
||||
const db = SQLite.openDatabase("lnreader.db");
|
||||
|
||||
import { fetchChapters, fetchNovel } from "../../Services/Source/source";
|
||||
import { fetchChapters, fetchNovel } from "../../services/Source/source";
|
||||
import { insertChapters } from "./ChapterQueries";
|
||||
|
||||
const insertNovelQuery = `INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
||||
@@ -15,4 +15,4 @@ export const createChapterTableQuery = `
|
||||
|
||||
export const createNovelIdIndexQuery = `CREATE INDEX IF NOT EXISTS chapterNovelIdIndex ON chapters(novelId)`;
|
||||
|
||||
export const createUnreadChaptersIndexQuery = `CREATE INDEX IF NOT EXISTS chapterUnreadByNovelIndex ON chapters(novelId, \`read\`)`;
|
||||
export const createUnreadChaptersIndexQuery = `CREATE INDEX IF NOT EXISTS chapterUnreadByNovelIndex ON chapters(novelId, \`read\`) WHERE \`read\`=0`;
|
||||
@@ -22,7 +22,7 @@ export const githubUpdateChecker = () => {
|
||||
};
|
||||
|
||||
const isNewVersion = (versionTag) => {
|
||||
let currentVersion = "v1.0.21";
|
||||
let currentVersion = "v1.1.0";
|
||||
const regex = /[^\\d.]/;
|
||||
|
||||
let newVersion = versionTag.replace(regex, "");
|
||||
@@ -1,5 +1,5 @@
|
||||
import { setStatusBarStyle as setExpoStatusBarStyle } from "expo-status-bar";
|
||||
import { useTheme } from "../Hooks/reduxHooks";
|
||||
import { useTheme } from "../hooks/reduxHooks";
|
||||
import changeNavigationBarColor from "react-native-navigation-bar-color";
|
||||
|
||||
export const setStatusBarStyle = () => {
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { getSource } from "../sources/sources";
|
||||
|
||||
const usePopularNovels = (sourceId) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [pageNumber, setPage] = useState(1);
|
||||
const [novels, setNovels] = useState([]);
|
||||
const [error, setError] = useState();
|
||||
|
||||
const source = getSource(sourceId);
|
||||
|
||||
useEffect(() => {
|
||||
const getNextPage = async (p) => {
|
||||
try {
|
||||
if (p === 1) setLoading(true);
|
||||
const res = await source.popularNovels(p);
|
||||
setHasMoreNovels(res.totalPages > pageNumber);
|
||||
setNovels((novels) => [...novels, ...data.novels]);
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
getNextPage(pageNumber);
|
||||
}, [pageNumber, sourceId]);
|
||||
|
||||
const incrementPage = () => {
|
||||
setPage((pageNumber) => pageNumber + 1);
|
||||
};
|
||||
|
||||
return { novels, incrementPage, loading, error };
|
||||
};
|
||||
|
||||
export { usePopularNovels };
|
||||
@@ -4,13 +4,13 @@ import { View } from "react-native";
|
||||
import { createMaterialBottomTabNavigator } from "@react-navigation/material-bottom-tabs";
|
||||
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
|
||||
import Library from "../Screens/Library/Library";
|
||||
import Updates from "../Screens/Updates/Updates";
|
||||
import History from "../Screens/History/History";
|
||||
import Browse from "../Screens/Browse/Browse";
|
||||
import More from "../Screens/More/More";
|
||||
import Library from "../screens/Library/Library";
|
||||
import Updates from "../screens/Updates/Updates";
|
||||
import History from "../screens/History/History";
|
||||
import Browse from "../screens/Browse/Browse";
|
||||
import More from "../screens/More/More";
|
||||
|
||||
import { useTheme } from "../Hooks/reduxHooks";
|
||||
import { useTheme } from "../hooks/reduxHooks";
|
||||
|
||||
const Tab = createMaterialBottomTabNavigator();
|
||||
|
||||
@@ -5,22 +5,23 @@ import {
|
||||
} from "@react-navigation/stack";
|
||||
|
||||
import BottomNavigator from "./BottomNavigator";
|
||||
import Novel from "../Screens/Novel/Novel";
|
||||
import Chapter from "../Screens/Chapter/Chapter";
|
||||
import Extension from "../Screens/Extension/Extension";
|
||||
import GlobalSearch from "../Screens/Browse/globalsearch/GlobalSearch";
|
||||
import Migration from "../Screens/Browse/migration/Migration";
|
||||
import SourceNovels from "../Screens/Browse/SourceNovels";
|
||||
import MigrateNovel from "../Screens/Browse/migration/MigrationNovels";
|
||||
import Novel from "../screens/Novel/Novel";
|
||||
import Chapter from "../screens/Chapter/Chapter";
|
||||
import Extension from "../screens/Extension/Extension";
|
||||
import GlobalSearch from "../screens/Browse/globalsearch/GlobalSearch";
|
||||
import Migration from "../screens/Browse/migration/Migration";
|
||||
import SourceNovels from "../screens/Browse/SourceNovels";
|
||||
import MigrateNovel from "../screens/Browse/migration/MigrationNovels";
|
||||
import MoreStack from "./More";
|
||||
import MalTopNovels from "../Screens/Browse/discover/MalTopNovels";
|
||||
import { setStatusBarStyle } from "../Hooks/setStatusBarStyle";
|
||||
import MalTopNovels from "../screens/Browse/discover/MalTopNovels";
|
||||
import { setStatusBarStyle } from "../hooks/setStatusBarStyle";
|
||||
import { View } from "react-native";
|
||||
import { useTheme } from "../Hooks/reduxHooks";
|
||||
import { githubUpdateChecker } from "../Hooks/githubUpdateChecker";
|
||||
import NewUpdateDialog from "../Components/NewUpdateDialog";
|
||||
import { useTheme } from "../hooks/reduxHooks";
|
||||
import { githubUpdateChecker } from "../hooks/githubUpdateChecker";
|
||||
import NewUpdateDialog from "../components/NewUpdateDialog";
|
||||
import { NavigationContainer } from "@react-navigation/native";
|
||||
import BrowseNovelUpdates from "../Screens/Browse/discover/BrowseNovelUpdates";
|
||||
import BrowseNovelUpdates from "../screens/Browse/discover/BrowseNovelUpdates";
|
||||
import BrowseSettings from "../screens/Browse/BrowseSettings";
|
||||
|
||||
const Stack = createStackNavigator();
|
||||
|
||||
@@ -44,21 +45,15 @@ const MainNavigator = () => {
|
||||
name="BottomNavigator"
|
||||
component={BottomNavigator}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Novel"
|
||||
component={Novel}
|
||||
options={{
|
||||
...TransitionPresets.RevealFromBottomAndroid,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Chapter"
|
||||
component={Chapter}
|
||||
options={{ ...TransitionPresets.SlideFromRightIOS }}
|
||||
/>
|
||||
<Stack.Screen name="Novel" component={Novel} />
|
||||
<Stack.Screen name="Chapter" component={Chapter} />
|
||||
<Stack.Screen name="MoreStack" component={MoreStack} />
|
||||
<Stack.Screen name="Extension" component={Extension} />
|
||||
<Stack.Screen name="BrowseMal" component={MalTopNovels} />
|
||||
<Stack.Screen
|
||||
name="BrowseSettings"
|
||||
component={BrowseSettings}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="BrowseNovelUpdates"
|
||||
component={BrowseNovelUpdates}
|
||||
@@ -1,16 +1,17 @@
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useTheme } from "../Hooks/reduxHooks";
|
||||
import { useTheme } from "../hooks/reduxHooks";
|
||||
|
||||
import { createStackNavigator } from "@react-navigation/stack";
|
||||
|
||||
import About from "../Screens/More/About";
|
||||
import Settings from "../Screens/More/Settings";
|
||||
import TrackerSettings from "../Screens/More/TrackerSettings";
|
||||
import ReaderSettings from "../Screens/More/ReaderSettings";
|
||||
import BackupSettings from "../Screens/More/BackupSettings";
|
||||
import AdvancedSettings from "../Screens/More/AdvancedSettings";
|
||||
import GeneralSettings from "../Screens/More/GeneralSettings";
|
||||
import About from "../screens/More/About";
|
||||
import Settings from "../screens/More/Settings";
|
||||
import TrackerSettings from "../screens/More/TrackerSettings";
|
||||
import ReaderSettings from "../screens/More/ReaderSettings";
|
||||
import BackupSettings from "../screens/More/BackupSettings";
|
||||
import AdvancedSettings from "../screens/More/AdvancedSettings";
|
||||
import GeneralSettings from "../screens/More/GeneralSettings";
|
||||
import DownloadQueue from "../screens/More/DownloadQueue";
|
||||
|
||||
const Stack = createStackNavigator();
|
||||
|
||||
@@ -56,6 +57,7 @@ const MoreStack = () => {
|
||||
<Stack.Navigator screenOptions={stackNavigatorConfig}>
|
||||
<Stack.Screen name="SettingsStack" component={SettingsStack} />
|
||||
<Stack.Screen name="About" component={About} />
|
||||
<Stack.Screen name="DownloadQueue" component={DownloadQueue} />
|
||||
</Stack.Navigator>
|
||||
</View>
|
||||
);
|
||||
@@ -0,0 +1,2 @@
|
||||
export const SET_DOWNLOAD_QUEUE = "SET_DOWNLOAD_QUEUE";
|
||||
export const CANCEL_DOWNLOAD = "CANCEL_DOWNLOAD";
|
||||
@@ -0,0 +1,8 @@
|
||||
import { SET_DOWNLOAD_QUEUE, CANCEL_DOWNLOAD } from "./donwloads.types";
|
||||
import BackgroundService from "react-native-background-actions";
|
||||
|
||||
export const cancelDownload = () => async (dispatch) => {
|
||||
await BackgroundService.stop();
|
||||
|
||||
dispatch({ type: CANCEL_DOWNLOAD });
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import { CHAPTER_DOWNLOADED } from "../novel/novel.types";
|
||||
import { CANCEL_DOWNLOAD, SET_DOWNLOAD_QUEUE } from "./donwloads.types";
|
||||
|
||||
const initialState = {
|
||||
downloadQueue: [],
|
||||
};
|
||||
|
||||
const downloadsReducer = (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
switch (type) {
|
||||
case SET_DOWNLOAD_QUEUE:
|
||||
return {
|
||||
...state,
|
||||
downloadQueue: [...state.downloadQueue, ...payload],
|
||||
};
|
||||
|
||||
case CHAPTER_DOWNLOADED:
|
||||
return {
|
||||
...state,
|
||||
downloadQueue: state.downloadQueue.filter(
|
||||
(chapter) => chapter.chapterId !== payload
|
||||
),
|
||||
};
|
||||
case CANCEL_DOWNLOAD:
|
||||
return {
|
||||
...state,
|
||||
downloadQueue: [],
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default downloadsReducer;
|
||||
@@ -9,9 +9,9 @@ import {
|
||||
insertHistory,
|
||||
deleteHistory,
|
||||
deleteAllHistory,
|
||||
} from "../../Database/queries/HistoryQueries";
|
||||
} from "../../database/queries/HistoryQueries";
|
||||
import { SET_LAST_READ } from "../preferences/preference.types";
|
||||
import { showToast } from "../../Hooks/showToast";
|
||||
import { showToast } from "../../hooks/showToast";
|
||||
|
||||
export const getHistoryAction = () => async (dispatch) => {
|
||||
dispatch({ type: LOAD_HISTORY });
|
||||
|
||||
@@ -26,7 +26,7 @@ const historyReducer = (state = initialState, action) => {
|
||||
// ),
|
||||
// };
|
||||
case CLEAR_HISTORY:
|
||||
return { ...state, history: [] };
|
||||
return { ...state, loading: [] };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
import {
|
||||
getLibrary,
|
||||
searchLibrary,
|
||||
} from "../../Database/queries/LibraryQueries";
|
||||
import { restoreBackup } from "../../Services/backup";
|
||||
} from "../../database/queries/LibraryQueries";
|
||||
import { restoreBackup } from "../../services/backup";
|
||||
|
||||
export const getLibraryAction = (sort, filter) => async (dispatch) => {
|
||||
// console.log("1.", sort, filter);
|
||||
|
||||
@@ -20,13 +20,13 @@ import {
|
||||
MARK_PREVIOUS_CHAPTERS_UNREAD,
|
||||
} from "./novel.types";
|
||||
|
||||
import { updateNovel } from "../../Services/updates";
|
||||
import { fetchNovel } from "../../Services/Source/source";
|
||||
import { updateNovel } from "../../services/updates";
|
||||
import { fetchNovel } from "../../services/Source/source";
|
||||
import {
|
||||
followNovel,
|
||||
insertNovel,
|
||||
getNovel,
|
||||
} from "../../Database/queries/NovelQueries";
|
||||
} from "../../database/queries/NovelQueries";
|
||||
import {
|
||||
getChapters,
|
||||
insertChapters,
|
||||
@@ -38,18 +38,34 @@ import {
|
||||
markPreviuschaptersRead,
|
||||
markPreviousChaptersUnread,
|
||||
getNextChapterFromDB,
|
||||
} from "../../Database/queries/ChapterQueries";
|
||||
import { deleteNovelUpdates } from "../../Database/queries/UpdateQueries";
|
||||
} from "../../database/queries/ChapterQueries";
|
||||
import { deleteNovelUpdates } from "../../database/queries/UpdateQueries";
|
||||
import {
|
||||
SAVE_SCROLL_POSITION,
|
||||
SET_CHAPTER_LIST_PREF,
|
||||
SET_LAST_READ,
|
||||
} from "../preferences/preference.types";
|
||||
import { GET_LIBRARY_NOVELS } from "../library/library.types";
|
||||
import { getLibrary } from "../../Database/queries/LibraryQueries";
|
||||
import { showToast } from "../../Hooks/showToast";
|
||||
import { getLibrary } from "../../database/queries/LibraryQueries";
|
||||
import { showToast } from "../../hooks/showToast";
|
||||
import { store } from "../store";
|
||||
|
||||
import * as Notifications from "expo-notifications";
|
||||
|
||||
Notifications.setNotificationHandler({
|
||||
handleNotification: async () => {
|
||||
return {
|
||||
shouldShowAlert: true,
|
||||
shouldPlaySound: true,
|
||||
shouldSetBadge: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
import BackgroundService from "react-native-background-actions";
|
||||
import { setDownloadQueue } from "../downloads/downloads.actions";
|
||||
import { SET_DOWNLOAD_QUEUE } from "../downloads/donwloads.types";
|
||||
|
||||
export const setNovel = (novel) => async (dispatch) => {
|
||||
dispatch({ type: SET_NOVEL, payload: novel });
|
||||
};
|
||||
@@ -265,14 +281,18 @@ export const markChapterUnreadAction =
|
||||
};
|
||||
|
||||
export const downloadChapterAction =
|
||||
(extensionId, novelUrl, chapterUrl, chapterName, chapterId) =>
|
||||
(sourceId, novelUrl, chapterUrl, chapterName, chapterId) =>
|
||||
async (dispatch) => {
|
||||
// dispatch({
|
||||
// type: CHAPTER_DOWNLOADING,
|
||||
// payload: chapterId,
|
||||
// });
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADING,
|
||||
payload: chapterId,
|
||||
type: SET_DOWNLOAD_QUEUE,
|
||||
payload: [{ chapterId, chapterName }],
|
||||
});
|
||||
|
||||
await downloadChapter(extensionId, novelUrl, chapterUrl, chapterId);
|
||||
await downloadChapter(sourceId, novelUrl, chapterUrl, chapterId);
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADED,
|
||||
@@ -283,34 +303,87 @@ export const downloadChapterAction =
|
||||
};
|
||||
|
||||
export const downloadAllChaptersAction =
|
||||
(extensionId, novelUrl, chapters) => async (dispatch) => {
|
||||
(sourceId, novelUrl, chaps) => async (dispatch) => {
|
||||
try {
|
||||
await chapters.map((chapter, index) => {
|
||||
setTimeout(async () => {
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADING,
|
||||
payload: chapter.chapterId,
|
||||
});
|
||||
let chapters = chaps.filter((chapter) => chapter.downloaded === 0);
|
||||
|
||||
if (!chapter.downloaded) {
|
||||
await downloadChapter(
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapter.chapterUrl,
|
||||
chapter.chapterId
|
||||
);
|
||||
dispatch({ type: SET_DOWNLOAD_QUEUE, payload: chapters });
|
||||
const options = {
|
||||
taskName: "Library Update",
|
||||
taskTitle: chapters[0].chapterName,
|
||||
taskDesc: "0/" + chapters.length,
|
||||
taskIcon: {
|
||||
name: "notification_icon",
|
||||
type: "drawable",
|
||||
},
|
||||
color: "#00adb5",
|
||||
parameters: {
|
||||
delay: 1000,
|
||||
},
|
||||
progressBar: {
|
||||
max: chapters.length,
|
||||
value: 0,
|
||||
},
|
||||
};
|
||||
|
||||
const sleep = (time) =>
|
||||
new Promise((resolve) => setTimeout(() => resolve(), time));
|
||||
|
||||
const veryIntensiveTask = async (taskData) => {
|
||||
await new Promise(async (resolve) => {
|
||||
for (
|
||||
let i = 0;
|
||||
BackgroundService.isRunning() && i < chapters.length;
|
||||
i++
|
||||
) {
|
||||
if (BackgroundService.isRunning()) {
|
||||
// dispatch({
|
||||
// type: CHAPTER_DOWNLOADING,
|
||||
// payload: chapters[i].chapterId,
|
||||
// });
|
||||
|
||||
if (!chapters[i].downloaded) {
|
||||
await downloadChapter(
|
||||
sourceId,
|
||||
novelUrl,
|
||||
chapters[i].chapterUrl,
|
||||
chapters[i].chapterId
|
||||
);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADED,
|
||||
payload: chapters[i].chapterId,
|
||||
});
|
||||
|
||||
await BackgroundService.updateNotification({
|
||||
taskTitle: chapters[i].chapterName,
|
||||
taskDesc: i + 1 + "/" + chapters.length,
|
||||
progressBar: {
|
||||
max: chapters.length,
|
||||
value: i + 1,
|
||||
},
|
||||
});
|
||||
|
||||
if (i + 1 === chapters.length) {
|
||||
resolve();
|
||||
await BackgroundService.stop();
|
||||
|
||||
Notifications.scheduleNotificationAsync({
|
||||
content: {
|
||||
title: "Downloader",
|
||||
body: "Download completed",
|
||||
},
|
||||
trigger: null,
|
||||
});
|
||||
}
|
||||
await sleep(taskData.delay);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADED,
|
||||
payload: chapter.chapterId,
|
||||
});
|
||||
|
||||
if (index + 1 === chapters.length) {
|
||||
showToast(`Download completed `);
|
||||
}
|
||||
}, 1000 * index);
|
||||
});
|
||||
await BackgroundService.start(veryIntensiveTask, options);
|
||||
} catch (error) {
|
||||
showToast(error.message);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
midnightDuskTheme,
|
||||
limeTheme,
|
||||
irisBlueTheme,
|
||||
} from "../../Theme/theme";
|
||||
} from "../../theme/theme";
|
||||
|
||||
const themes = {
|
||||
0: amoledDarkTheme,
|
||||
@@ -56,6 +56,9 @@ const initialState = {
|
||||
showUnreadBadges: true,
|
||||
showNumberOfNovels: false,
|
||||
showScrollPercentage: true,
|
||||
swipeGestures: true,
|
||||
incognitoMode: false,
|
||||
searchAllSources: false,
|
||||
};
|
||||
|
||||
const settingsReducer = (state = initialState, action) => {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { GET_SOURCES, PIN_SOURCES, SEARCH_SOURCES } from "./source.types";
|
||||
import { fetchSources } from "../../Services/Source/source";
|
||||
import { showToast } from "../../Hooks/showToast";
|
||||
import {
|
||||
ENABLE_DISCOVER,
|
||||
FILTER_LANGUAGE,
|
||||
GET_SOURCES,
|
||||
PIN_SOURCES,
|
||||
SEARCH_SOURCES,
|
||||
} from "./source.types";
|
||||
import { showToast } from "../../hooks/showToast";
|
||||
import { sources } from "../../sources/sources";
|
||||
|
||||
export const getSourcesAction = () => async (dispatch) => {
|
||||
try {
|
||||
const res = await fetchSources();
|
||||
|
||||
dispatch({
|
||||
type: GET_SOURCES,
|
||||
payload: res,
|
||||
});
|
||||
dispatch({ type: GET_SOURCES, payload: sources });
|
||||
} catch (error) {
|
||||
showToast(error.message);
|
||||
}
|
||||
@@ -30,3 +31,17 @@ export const pinSourceAction = (sourceId) => async (dispatch) => {
|
||||
payload: sourceId,
|
||||
});
|
||||
};
|
||||
|
||||
export const filterLanguage = (language) => async (dispatch) => {
|
||||
dispatch({
|
||||
type: FILTER_LANGUAGE,
|
||||
payload: language,
|
||||
});
|
||||
};
|
||||
|
||||
export const enableDiscover = (key) => async (dispatch) => {
|
||||
dispatch({
|
||||
type: ENABLE_DISCOVER,
|
||||
payload: key,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import { GET_SOURCES, SEARCH_SOURCES, PIN_SOURCES } from "./source.types";
|
||||
import {
|
||||
GET_SOURCES,
|
||||
SEARCH_SOURCES,
|
||||
PIN_SOURCES,
|
||||
FILTER_LANGUAGE,
|
||||
ENABLE_DISCOVER,
|
||||
} from "./source.types";
|
||||
|
||||
const initialState = {
|
||||
sources: [],
|
||||
pinnedSources: [],
|
||||
search: [],
|
||||
pinned: [],
|
||||
filters: [],
|
||||
showNovelUpdates: true,
|
||||
showMyAnimeList: true,
|
||||
loading: true,
|
||||
};
|
||||
|
||||
@@ -30,6 +39,26 @@ const sourceReducer = (state = initialState, action) => {
|
||||
source.sourceName.toLowerCase().includes(payload)
|
||||
),
|
||||
};
|
||||
|
||||
case FILTER_LANGUAGE:
|
||||
if (!state.filters) {
|
||||
state.filters = [];
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
filters:
|
||||
state.filters.indexOf(payload) === -1
|
||||
? [...state.filters, payload]
|
||||
: state.filters.filter(
|
||||
(language) => language !== payload
|
||||
),
|
||||
};
|
||||
case ENABLE_DISCOVER:
|
||||
return {
|
||||
...state,
|
||||
[payload]: !state[payload],
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export const GET_SOURCES = "GET_SOURCES";
|
||||
export const SEARCH_SOURCES = "SEARCH_SOURCES";
|
||||
export const PIN_SOURCES = "PIN_SOURCES";
|
||||
export const FILTER_LANGUAGE = "FILTER_LANGUAGE";
|
||||
export const ENABLE_DISCOVER = "ENABLE_DISCOVER";
|
||||
|
||||
@@ -11,6 +11,7 @@ import historyReducer from "./history/history.reducer";
|
||||
import updatesReducer from "./updates/updates.reducer";
|
||||
import trackerReducer from "./tracker/tracker.reducer";
|
||||
import preferenceReducer from "./preferences/preference.reducer";
|
||||
import downloadsReducer from "./downloads/downloads.reducer";
|
||||
|
||||
const persistConfig = {
|
||||
key: "root",
|
||||
@@ -29,6 +30,7 @@ const persistedReducer = persistReducer(
|
||||
updatesReducer,
|
||||
trackerReducer,
|
||||
preferenceReducer,
|
||||
downloadsReducer,
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { findListItem, updateItem } from "../../Services/Trackers/myAnimeList";
|
||||
import { findListItem, updateItem } from "../../services/Trackers/myAnimeList";
|
||||
import {
|
||||
REMOVE_TRACKER,
|
||||
SET_TRACKER,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
UPDATE_CHAPTERS_READ,
|
||||
} from "./tracker.types";
|
||||
|
||||
import { updateMalChaptersRead } from "../../Services/Trackers/myAnimeList";
|
||||
import { updateMalChaptersRead } from "../../services/Trackers/myAnimeList";
|
||||
|
||||
export const setTracker = (res) => async (dispatch) => {
|
||||
dispatch({
|
||||
@@ -43,15 +43,14 @@ export const untrackNovel = (id) => async (dispatch) => {
|
||||
dispatch({ type: UNTRACK_NOVEL, payload: id });
|
||||
};
|
||||
|
||||
export const updateChaptersRead = (malId, accessToken, chaptersRead) => async (
|
||||
dispatch
|
||||
) => {
|
||||
let res = await updateMalChaptersRead(malId, accessToken, {
|
||||
num_chapters_read: chaptersRead,
|
||||
});
|
||||
export const updateChaptersRead =
|
||||
(malId, accessToken, chaptersRead) => async (dispatch) => {
|
||||
let res = await updateMalChaptersRead(malId, accessToken, {
|
||||
num_chapters_read: chaptersRead,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_CHAPTERS_READ,
|
||||
payload: { malId, chaptersRead: res },
|
||||
});
|
||||
};
|
||||
dispatch({
|
||||
type: UPDATE_CHAPTERS_READ,
|
||||
payload: { malId, chaptersRead: res },
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { GET_UPDATES } from "./updates.types";
|
||||
|
||||
import { getUpdates } from "../../Database/queries/UpdateQueries";
|
||||
import { updateAllNovels } from "../../Services/updates";
|
||||
import { getUpdates } from "../../database/queries/UpdateQueries";
|
||||
import { updateAllNovels } from "../../services/updates";
|
||||
|
||||
import { showToast } from "../../Hooks/showToast";
|
||||
import { showToast } from "../../hooks/showToast";
|
||||
|
||||
export const getUpdatesAction = () => async (dispatch) => {
|
||||
const updates = await getUpdates();
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
ActivityIndicator,
|
||||
RefreshControl,
|
||||
} from "react-native";
|
||||
import { useTheme } from "../../Hooks/reduxHooks";
|
||||
import { useTheme } from "../../hooks/reduxHooks";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import {
|
||||
@@ -16,25 +16,34 @@ import {
|
||||
} from "../../redux/source/source.actions";
|
||||
|
||||
import SourceCard from "./components/SourceCard";
|
||||
import { showToast } from "../../Hooks/showToast";
|
||||
import { Searchbar } from "../../Components/Searchbar";
|
||||
import { showToast } from "../../hooks/showToast";
|
||||
import { Searchbar } from "../../components/Searchbar";
|
||||
import MalCard from "./components/MalCard";
|
||||
import NovelUpdatesCard from "./components/NovelUpdatesCard";
|
||||
import EmptyView from "../../components/EmptyView";
|
||||
|
||||
const Browse = ({ navigation }) => {
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
// const [globalSearch, setGlobalSearch] = useState(false);
|
||||
// const [searchResult, setSearchResults] = useState();
|
||||
const { sources, search, loading, pinned } = useSelector(
|
||||
(state) => state.sourceReducer
|
||||
);
|
||||
let {
|
||||
sources,
|
||||
search,
|
||||
loading,
|
||||
pinned,
|
||||
filters = [],
|
||||
showNovelUpdates = true,
|
||||
showMyAnimeList = true,
|
||||
} = useSelector((state) => state.sourceReducer);
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isPinned = (sourceId) =>
|
||||
pinned.indexOf(sourceId) === -1 ? false : true;
|
||||
|
||||
sources = sources.filter((source) => filters.indexOf(source.lang) === -1);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getSourcesAction());
|
||||
}, [getSourcesAction]);
|
||||
@@ -88,6 +97,10 @@ const Browse = ({ navigation }) => {
|
||||
icon: "swap-vertical-variant",
|
||||
onPress: () => navigation.navigate("Migration"),
|
||||
},
|
||||
{
|
||||
icon: "cog-outline",
|
||||
onPress: () => navigation.navigate("BrowseSettings"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -99,17 +112,23 @@ const Browse = ({ navigation }) => {
|
||||
extraData={pinned}
|
||||
ListHeaderComponent={
|
||||
<View>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
Discover
|
||||
</Text>
|
||||
<NovelUpdatesCard theme={theme} />
|
||||
<MalCard theme={theme} />
|
||||
{(showNovelUpdates || showMyAnimeList) && (
|
||||
<>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
Discover
|
||||
</Text>
|
||||
{showNovelUpdates && (
|
||||
<NovelUpdatesCard theme={theme} />
|
||||
)}
|
||||
{showMyAnimeList && <MalCard theme={theme} />}
|
||||
</>
|
||||
)}
|
||||
{pinned.length > 0 && (
|
||||
<FlatList
|
||||
contentContainerStyle={{ paddingBottom: 16 }}
|
||||
@@ -134,24 +153,33 @@ const Browse = ({ navigation }) => {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
Sources
|
||||
</Text>
|
||||
{sources.length > 0 && (
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
Sources
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
ListEmptyComponent={
|
||||
loading && (
|
||||
loading ? (
|
||||
<ActivityIndicator
|
||||
size="small"
|
||||
color={theme.colorAccent}
|
||||
style={{ marginTop: 16 }}
|
||||
/>
|
||||
) : (
|
||||
sources.length === 0 && (
|
||||
<EmptyView
|
||||
icon="(・Д・。"
|
||||
description="Enable languages from settings"
|
||||
/>
|
||||
)
|
||||
)
|
||||
}
|
||||
refreshControl={
|
||||
@@ -3,13 +3,13 @@ import { StyleSheet, View, ActivityIndicator, FlatList } from "react-native";
|
||||
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
|
||||
import NovelCover from "../../Components/NovelCover";
|
||||
import { Searchbar } from "../../Components/Searchbar";
|
||||
import ErrorView from "../../Components/ErrorView";
|
||||
import NovelCover from "../../components/NovelCover";
|
||||
import { Searchbar } from "../../components/Searchbar";
|
||||
import ErrorView from "../../components/ErrorView";
|
||||
|
||||
import { useTheme, useSettings } from "../../Hooks/reduxHooks";
|
||||
import { showToast } from "../../Hooks/showToast";
|
||||
import { getDeviceOrientation } from "../../Services/utils/helpers";
|
||||
import { useTheme, useSettings } from "../../hooks/reduxHooks";
|
||||
import { showToast } from "../../hooks/showToast";
|
||||
import { getDeviceOrientation } from "../../services/utils/helpers";
|
||||
import { scrapeSearchResults } from "./discover/MyAnimeListScraper";
|
||||
|
||||
const BrowseMalScreen = ({ navigation, route }) => {
|
||||
@@ -0,0 +1,143 @@
|
||||
import React, { useState } from "react";
|
||||
import { FlatList, Pressable, Text, View } from "react-native";
|
||||
|
||||
import { Switch } from "react-native-paper";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
import { ScreenContainer } from "../../components/Common";
|
||||
import { ListSubHeader } from "../../components/List";
|
||||
|
||||
import { useSettings, useTheme } from "../../hooks/reduxHooks";
|
||||
import { setAppSettings } from "../../redux/settings/settings.actions";
|
||||
import {
|
||||
enableDiscover,
|
||||
filterLanguage,
|
||||
} from "../../redux/source/source.actions";
|
||||
|
||||
const BrowseSettings = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
filters = [],
|
||||
showNovelUpdates = true,
|
||||
showMyAnimeList = true,
|
||||
} = useSelector((state) => state.sourceReducer);
|
||||
const languages = ["English", "Spanish"];
|
||||
const { searchAllSources = false } = useSettings();
|
||||
|
||||
const renderItem = ({ item }) => {
|
||||
return (
|
||||
<Pressable
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
style={{
|
||||
padding: 16,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
onPress={() => dispatch(filterLanguage(item))}
|
||||
>
|
||||
<Text style={{ color: theme.textColorPrimary }}>{item}</Text>
|
||||
<Switch
|
||||
color={theme.colorAccent}
|
||||
value={filters.indexOf(item) === -1 ? true : false}
|
||||
onValueChange={() => dispatch(filterLanguage(item))}
|
||||
/>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ScreenContainer theme={theme}>
|
||||
<Appbar onBackAction={navigation.goBack} title="Sources" />
|
||||
<View>
|
||||
<ListSubHeader theme={theme}>Global Search</ListSubHeader>
|
||||
<Pressable
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
style={{
|
||||
padding: 16,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
onPress={() =>
|
||||
dispatch(
|
||||
setAppSettings(
|
||||
"searchAllSources",
|
||||
!searchAllSources
|
||||
)
|
||||
)
|
||||
}
|
||||
>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Search all sources
|
||||
</Text>
|
||||
<Switch
|
||||
color={theme.colorAccent}
|
||||
value={searchAllSources}
|
||||
onValueChange={() =>
|
||||
dispatch(
|
||||
setAppSettings(
|
||||
"searchAllSources",
|
||||
!searchAllSources
|
||||
)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
<ListSubHeader theme={theme}>Discover</ListSubHeader>
|
||||
<Pressable
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
style={{
|
||||
padding: 16,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
onPress={() => dispatch(enableDiscover("showNovelUpdates"))}
|
||||
>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Novel Updates
|
||||
</Text>
|
||||
<Switch
|
||||
color={theme.colorAccent}
|
||||
value={showNovelUpdates}
|
||||
onValueChange={() =>
|
||||
dispatch(enableDiscover("showNovelUpdates"))
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
style={{
|
||||
padding: 16,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
onPress={() => dispatch(enableDiscover("showMyAnimeList"))}
|
||||
>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
MyAnimeList
|
||||
</Text>
|
||||
<Switch
|
||||
color={theme.colorAccent}
|
||||
value={showMyAnimeList}
|
||||
onValueChange={() =>
|
||||
dispatch(enableDiscover("showMyAnimeList"))
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
<ListSubHeader theme={theme}>Languages</ListSubHeader>
|
||||
<FlatList
|
||||
data={languages}
|
||||
keyExtractor={(item) => item}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
</View>
|
||||
</ScreenContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default BrowseSettings;
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View, FlatList, Text } from "react-native";
|
||||
import { useLibrary, useTheme } from "../../Hooks/reduxHooks";
|
||||
import { useLibrary, useTheme } from "../../hooks/reduxHooks";
|
||||
|
||||
import EmptyView from "../../Components/EmptyView";
|
||||
import { Appbar } from "../../Components/Appbar";
|
||||
import ListView from "../../Components/ListView";
|
||||
import EmptyView from "../../components/EmptyView";
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
import ListView from "../../components/ListView";
|
||||
|
||||
const SourceNovels = ({ navigation, route }) => {
|
||||
const sourceId = route.params;
|
||||
+11
-16
@@ -7,7 +7,7 @@ import { pinSourceAction } from "../../../redux/source/source.actions";
|
||||
import FastImage from "react-native-fast-image";
|
||||
|
||||
const ExtensionCard = ({ item, theme, isPinned }) => {
|
||||
const { sourceId, sourceName, sourceCover, sourceLanguage, status } = item;
|
||||
const { sourceId, sourceName, icon, lang } = item;
|
||||
|
||||
const navigation = useNavigation();
|
||||
const dispatch = useDispatch();
|
||||
@@ -16,7 +16,7 @@ const ExtensionCard = ({ item, theme, isPinned }) => {
|
||||
navigation.navigate("Extension", {
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName,
|
||||
sourceUrl: item.sourceUrl,
|
||||
sourceUrl: item.url,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -27,7 +27,7 @@ const ExtensionCard = ({ item, theme, isPinned }) => {
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
source={{ uri: sourceCover }}
|
||||
source={{ uri: icon }}
|
||||
style={styles.extensionIcon}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
@@ -41,19 +41,14 @@ const ExtensionCard = ({ item, theme, isPinned }) => {
|
||||
>
|
||||
{sourceName}
|
||||
</Text>
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{sourceLanguage}
|
||||
</Text>
|
||||
<Text style={styles.sourceStatus}>
|
||||
{status === 0 && "• Down"}
|
||||
</Text>
|
||||
</View>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{lang}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={{ flexDirection: "row", alignItems: "center" }}
|
||||
+3
-3
@@ -10,10 +10,10 @@ import {
|
||||
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
|
||||
import { ScreenContainer } from "../../../Components/Common";
|
||||
import { Searchbar } from "../../../Components/Searchbar";
|
||||
import { ScreenContainer } from "../../../components/Common";
|
||||
import { Searchbar } from "../../../components/Searchbar";
|
||||
|
||||
import { useTheme } from "../../../Hooks/reduxHooks";
|
||||
import { useTheme } from "../../../hooks/reduxHooks";
|
||||
import { scrapeSearchResults, scrapeTopNovels } from "./NovelUpdatesScraper";
|
||||
import NovelUpdatesNovelCard from "./NovelUpdatesNovelCard";
|
||||
|
||||
+6
-6
@@ -9,13 +9,13 @@ import {
|
||||
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
|
||||
import NovelCover from "../../../Components/NovelCover";
|
||||
import { Searchbar } from "../../../Components/Searchbar";
|
||||
import ErrorView from "../../../Components/ErrorView";
|
||||
import NovelCover from "../../../components/NovelCover";
|
||||
import { Searchbar } from "../../../components/Searchbar";
|
||||
import ErrorView from "../../../components/ErrorView";
|
||||
|
||||
import { useTheme, useSettings } from "../../../Hooks/reduxHooks";
|
||||
import { showToast } from "../../../Hooks/showToast";
|
||||
import { getDeviceOrientation } from "../../../Services/utils/helpers";
|
||||
import { useTheme, useSettings } from "../../../hooks/reduxHooks";
|
||||
import { showToast } from "../../../hooks/showToast";
|
||||
import { getDeviceOrientation } from "../../../services/utils/helpers";
|
||||
import { scrapeSearchResults, scrapeTopNovels } from "./MyAnimeListScraper";
|
||||
|
||||
const BrowseMalScreen = ({ navigation, route }) => {
|
||||
@@ -0,0 +1,5 @@
|
||||
import React from "react";
|
||||
|
||||
const NovelUpdatesBottomSheet = () => {};
|
||||
|
||||
export default NovelUpdatesBottomSheet;
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, Pressable, Text, View, FlatList } from "react-native";
|
||||
import FastImage from "react-native-fast-image";
|
||||
import { Row } from "../../../Components/Common";
|
||||
import { Row } from "../../../components/Common";
|
||||
|
||||
const GenreChip = ({ children, theme }) => (
|
||||
<Text
|
||||
+52
-50
@@ -4,14 +4,15 @@ import { StyleSheet, View, FlatList, ActivityIndicator } from "react-native";
|
||||
import { ProgressBar } from "react-native-paper";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { Searchbar } from "../../../Components/Searchbar";
|
||||
import EmptyView from "../../../Components/EmptyView";
|
||||
import { Searchbar } from "../../../components/Searchbar";
|
||||
import EmptyView from "../../../components/EmptyView";
|
||||
|
||||
import { ScreenContainer } from "../../../Components/Common";
|
||||
import { ScreenContainer } from "../../../components/Common";
|
||||
import GlobalSearchSourceItem from "./GlobalSearchSourceItem";
|
||||
|
||||
import { showToast } from "../../../Hooks/showToast";
|
||||
import { useLibrary, useTheme } from "../../../Hooks/reduxHooks";
|
||||
import { showToast } from "../../../hooks/showToast";
|
||||
import { useLibrary, useSettings, useTheme } from "../../../hooks/reduxHooks";
|
||||
import { getSource } from "../../../sources/sources";
|
||||
|
||||
const GlobalSearch = ({ route, navigation }) => {
|
||||
const theme = useTheme();
|
||||
@@ -21,10 +22,17 @@ const GlobalSearch = ({ route, navigation }) => {
|
||||
novelName = route.params.novelName;
|
||||
}
|
||||
|
||||
const { sources, pinned } = useSelector((state) => state.sourceReducer);
|
||||
let {
|
||||
sources,
|
||||
pinned,
|
||||
filters = [],
|
||||
} = useSelector((state) => state.sourceReducer);
|
||||
sources = sources.filter((source) => filters.indexOf(source.lang) === -1);
|
||||
|
||||
const pinnedSources = sources.filter(
|
||||
(source) => pinned.indexOf(source.sourceId) !== -1
|
||||
);
|
||||
const { searchAllSources = false } = useSettings();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchText, setSearchText] = useState(novelName);
|
||||
@@ -40,52 +48,44 @@ const GlobalSearch = ({ route, navigation }) => {
|
||||
const clearSearchbar = () => setSearchText("");
|
||||
const onChangeText = (text) => setSearchText(text);
|
||||
|
||||
const onSubmitEditing = () => {
|
||||
const onSubmitEditing = async () => {
|
||||
setSearchResults([]);
|
||||
|
||||
const getSearchUrl = (sourceId) => {
|
||||
if (sourceId === 1) {
|
||||
return `https://lnreader-extensions.vercel.app/api/1/search/?s=${searchText}&?o=rating`;
|
||||
} else {
|
||||
return `https://lnreader-extensions.vercel.app/api/${sourceId}/search/?s=${searchText}`;
|
||||
}
|
||||
};
|
||||
let globalSearchSources = searchAllSources ? sources : pinnedSources;
|
||||
|
||||
pinnedSources.map((source, index) =>
|
||||
setTimeout(() => {
|
||||
setLoading(true);
|
||||
fetch(getSearchUrl(source.sourceId))
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: source.sourceId,
|
||||
sourceName: source.sourceName,
|
||||
sourceLanguage: source.sourceLanguage,
|
||||
novels: json,
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
showToast(error.message);
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: source.sourceId,
|
||||
sourceName: source.sourceName,
|
||||
sourceLanguage: source.sourceLanguage,
|
||||
novels: [],
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
})
|
||||
.finally(() =>
|
||||
setProgress(
|
||||
(progress) => progress + 1 / pinnedSources.length
|
||||
)
|
||||
globalSearchSources.map((item, index) =>
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const source = getSource(item.sourceId);
|
||||
const data = await source.searchNovels(
|
||||
encodeURI(searchText)
|
||||
);
|
||||
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName,
|
||||
lang: item.lang,
|
||||
novels: data,
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
showToast(error.message);
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName,
|
||||
lang: item.lang,
|
||||
novels: [],
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
}
|
||||
}, 1000 * index)
|
||||
);
|
||||
};
|
||||
@@ -135,8 +135,10 @@ const GlobalSearch = ({ route, navigation }) => {
|
||||
{!loading && (
|
||||
<EmptyView
|
||||
icon="__φ(..)"
|
||||
description={`Search a novel in your pinned sources ${
|
||||
pinned.length === 0
|
||||
description={`Search a novel in your pinned sources\n${
|
||||
searchAllSources
|
||||
? "(Search all sources)"
|
||||
: pinned.length === 0
|
||||
? "(No sources pinned)"
|
||||
: ""
|
||||
}`}
|
||||
+2
-2
@@ -12,7 +12,7 @@ const GlobalSearchNovelList = ({ data, theme, library, navigation }) => {
|
||||
const navigateToNovel = (item) =>
|
||||
navigation.navigate("Novel", {
|
||||
...item,
|
||||
sourceId: item.extensionId,
|
||||
sourceId: item.sourceId,
|
||||
});
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
@@ -20,7 +20,7 @@ const GlobalSearchNovelList = ({ data, theme, library, navigation }) => {
|
||||
novel={item}
|
||||
theme={theme}
|
||||
onPress={() => navigateToNovel(item)}
|
||||
inLibrary={inLibrary(item.extensionId, item.novelUrl)}
|
||||
inLibrary={inLibrary(item.sourceId, item.novelUrl)}
|
||||
/>
|
||||
);
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ import { StyleSheet, View, Text } from "react-native";
|
||||
import GlobalSearchNovelList from "./GlobalSearchNovelList";
|
||||
|
||||
const GlobalSearchSourceItem = ({ source, library, theme, navigation }) => {
|
||||
const { sourceName, sourceLanguage, novels } = source;
|
||||
const { sourceName, lang, novels } = source;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -13,7 +13,7 @@ const GlobalSearchSourceItem = ({ source, library, theme, navigation }) => {
|
||||
{sourceName}
|
||||
</Text>
|
||||
<Text style={{ color: theme.textColorSecondary, fontSize: 12 }}>
|
||||
{sourceLanguage}
|
||||
{lang}
|
||||
</Text>
|
||||
</View>
|
||||
<GlobalSearchNovelList
|
||||
+2
-2
@@ -3,10 +3,10 @@ import { StyleSheet, View, FlatList, Text } from "react-native";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { Appbar } from "../../../Components/Appbar";
|
||||
import { Appbar } from "../../../components/Appbar";
|
||||
import MigrationSourceCard from "./MigrationSourceCard";
|
||||
|
||||
import { useLibrary, useTheme } from "../../../Hooks/reduxHooks";
|
||||
import { useLibrary, useTheme } from "../../../hooks/reduxHooks";
|
||||
|
||||
const GlobalSearch = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
+5
-5
@@ -5,8 +5,8 @@ import { Portal, Modal, Button } from "react-native-paper";
|
||||
|
||||
import GlobalSearchNovelCover from "../globalsearch/GlobalSearchNovelCover";
|
||||
|
||||
import { migrateNovel } from "../../../Database/queries/NovelQueries";
|
||||
import { showToast } from "../../../Hooks/showToast";
|
||||
import { migrateNovel } from "../../../database/queries/NovelQueries";
|
||||
import { showToast } from "../../../hooks/showToast";
|
||||
|
||||
const MigrationNovelList = ({ data, theme, library, navigation }) => {
|
||||
const [selectedNovel, setSelectedNovel] = useState(false);
|
||||
@@ -26,15 +26,15 @@ const MigrationNovelList = ({ data, theme, library, navigation }) => {
|
||||
novel={item}
|
||||
theme={theme}
|
||||
onPress={() =>
|
||||
showModal(item.extensionId, item.novelUrl, item.novelName)
|
||||
showModal(item.sourceId, item.novelUrl, item.novelName)
|
||||
}
|
||||
onLongPress={() =>
|
||||
navigation.navigate("Novel", {
|
||||
...item,
|
||||
sourceId: item.extensionId,
|
||||
sourceId: item.sourceId,
|
||||
})
|
||||
}
|
||||
inLibrary={inLibrary(item.extensionId, item.novelUrl)}
|
||||
inLibrary={inLibrary(item.sourceId, item.novelUrl)}
|
||||
/>
|
||||
);
|
||||
|
||||
+38
-48
@@ -7,16 +7,17 @@ import {
|
||||
ActivityIndicator,
|
||||
} from "react-native";
|
||||
import { ProgressBar } from "react-native-paper";
|
||||
import { useLibrary, useTheme } from "../../../Hooks/reduxHooks";
|
||||
import { useLibrary, useTheme } from "../../../hooks/reduxHooks";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import EmptyView from "../../../Components/EmptyView";
|
||||
import EmptyView from "../../../components/EmptyView";
|
||||
import MigrationNovelList from "./MigrationNovelList";
|
||||
import { Appbar } from "../../../Components/Appbar";
|
||||
import { Appbar } from "../../../components/Appbar";
|
||||
|
||||
import { showToast } from "../../../Hooks/showToast";
|
||||
import { migrateNovel } from "../../../Database/queries/NovelQueries";
|
||||
import { ScreenContainer } from "../../../Components/Common";
|
||||
import { showToast } from "../../../hooks/showToast";
|
||||
import { migrateNovel } from "../../../database/queries/NovelQueries";
|
||||
import { ScreenContainer } from "../../../components/Common";
|
||||
import { getSource } from "../../../sources/sources";
|
||||
|
||||
const MigrationNovels = ({ navigation, route }) => {
|
||||
const { sourceId, novelName } = route.params;
|
||||
@@ -36,49 +37,38 @@ const MigrationNovels = ({ navigation, route }) => {
|
||||
);
|
||||
|
||||
const getSearchResults = () => {
|
||||
const getSearchUrl = (sourceId) => {
|
||||
if (sourceId === 1) {
|
||||
return `https://lnreader-extensions.vercel.app/api/1/search/?s=${novelName}&?o=rating`;
|
||||
} else {
|
||||
return `https://lnreader-extensions.vercel.app/api/${sourceId}/search/?s=${novelName}`;
|
||||
}
|
||||
};
|
||||
pinnedSources.map((item, index) =>
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
pinnedSources.map((source, index) =>
|
||||
setTimeout(() => {
|
||||
setLoading(true);
|
||||
fetch(getSearchUrl(source.sourceId))
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: source.sourceId,
|
||||
sourceName: source.sourceName,
|
||||
sourceLanguage: source.sourceLanguage,
|
||||
novels: json,
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
showToast(error.message);
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: source.sourceId,
|
||||
sourceName: source.sourceName,
|
||||
sourceLanguage: source.sourceLanguage,
|
||||
novels: [],
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
})
|
||||
.finally(() =>
|
||||
setProgress(
|
||||
(progress) => progress + 1 / pinnedSources.length
|
||||
)
|
||||
);
|
||||
const source = getSource(item.sourceId);
|
||||
const data = await source.searchNovels(novelName);
|
||||
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName,
|
||||
sourceLanguage: item.sourceLanguage,
|
||||
novels: data,
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
showToast(error.message);
|
||||
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName,
|
||||
sourceLanguage: item.sourceLanguage,
|
||||
novels: [],
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
}
|
||||
}, 1000 * index)
|
||||
);
|
||||
};
|
||||
+2
-2
@@ -3,7 +3,7 @@ import { View, Text, Image, StyleSheet } from "react-native";
|
||||
import { TouchableRipple } from "react-native-paper";
|
||||
|
||||
const MigrationSourceCard = ({ item, theme, noOfNovels, onPress }) => {
|
||||
const { sourceName, sourceCover, sourceLanguage } = item;
|
||||
const { sourceName, icon, sourceLanguage } = item;
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
@@ -13,7 +13,7 @@ const MigrationSourceCard = ({ item, theme, noOfNovels, onPress }) => {
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
source={{ uri: sourceCover }}
|
||||
source={{ uri: icon }}
|
||||
style={styles.sourceIcon}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
@@ -5,22 +5,30 @@ import {
|
||||
Text,
|
||||
ActivityIndicator,
|
||||
StatusBar,
|
||||
ScrollView,
|
||||
} from "react-native";
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import Constants from "expo-constants";
|
||||
import { IconButton, Portal } from "react-native-paper";
|
||||
import { CollapsibleHeaderScrollView } from "react-native-collapsible-header-views";
|
||||
import {
|
||||
hideNavigationBar,
|
||||
showNavigationBar,
|
||||
} from "react-native-navigation-bar-color";
|
||||
|
||||
import { getChapterFromDB } from "../../Database/queries/ChapterQueries";
|
||||
import { fetchChapter } from "../../Services/Source/source";
|
||||
import { showToast } from "../../Hooks/showToast";
|
||||
import {
|
||||
getChapterFromDB,
|
||||
getNextChapterFromDB,
|
||||
getPrevChapterFromDB,
|
||||
} from "../../database/queries/ChapterQueries";
|
||||
import { fetchChapter } from "../../services/Source/source";
|
||||
import { showToast } from "../../hooks/showToast";
|
||||
import {
|
||||
usePosition,
|
||||
useReaderSettings,
|
||||
useSettings,
|
||||
useTheme,
|
||||
useTrackingStatus,
|
||||
} from "../../Hooks/reduxHooks";
|
||||
} from "../../hooks/reduxHooks";
|
||||
import { updateChaptersRead } from "../../redux/tracker/tracker.actions";
|
||||
import { insertHistoryAction } from "../../redux/history/history.actions";
|
||||
import {
|
||||
@@ -31,17 +39,16 @@ import {
|
||||
} from "./readerStyleController";
|
||||
import { markChapterReadAction } from "../../redux/novel/novel.actions";
|
||||
import { saveScrollPosition } from "../../redux/preferences/preference.actions";
|
||||
import { parseChapterNumber } from "../../Services/updates";
|
||||
import { parseChapterNumber } from "../../services/updates";
|
||||
|
||||
import ChapterAppbar from "./components/ChapterAppbar";
|
||||
import ReaderSheet from "./components/ReaderSheet";
|
||||
import EmptyView from "../../Components/EmptyView";
|
||||
import EmptyView from "../../components/EmptyView";
|
||||
import FitImage from "react-native-fit-image";
|
||||
|
||||
import {
|
||||
hideNavigationBar,
|
||||
showNavigationBar,
|
||||
} from "react-native-navigation-bar-color";
|
||||
import ChapterFooter from "./components/ChapterFooter";
|
||||
import VerticalScrollbar from "./components/VerticalScrollbar";
|
||||
import GestureRecognizer from "react-native-swipe-gestures";
|
||||
|
||||
const Chapter = ({ route, navigation }) => {
|
||||
const {
|
||||
@@ -52,8 +59,9 @@ const Chapter = ({ route, navigation }) => {
|
||||
novelUrl,
|
||||
novelName,
|
||||
chapterName,
|
||||
bookmark,
|
||||
} = route.params;
|
||||
let scrollViewRef;
|
||||
let scrollViewRef = useRef(null);
|
||||
let readerSheetRef = useRef(null);
|
||||
|
||||
const theme = useTheme();
|
||||
@@ -62,6 +70,9 @@ const Chapter = ({ route, navigation }) => {
|
||||
const showScrollPercentage = useSelector(
|
||||
(state) => state.settingsReducer.showScrollPercentage
|
||||
);
|
||||
const { swipeGestures = true, incognitoMode = false } = useSettings();
|
||||
|
||||
const [hidden, setHidden] = useState(true);
|
||||
|
||||
const { tracker, trackedNovels } = useTrackingStatus();
|
||||
const position = usePosition(novelId, chapterId);
|
||||
@@ -76,6 +87,8 @@ const Chapter = ({ route, navigation }) => {
|
||||
const [firstLayout, setFirstLayout] = useState(true);
|
||||
const [selectText, setSelectText] = useState(false);
|
||||
|
||||
const [contentSize, setContentSize] = useState(0);
|
||||
|
||||
const [images, setImages] = useState([]);
|
||||
|
||||
const getChapter = async (chapterId) => {
|
||||
@@ -120,9 +133,21 @@ const Chapter = ({ route, navigation }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const [nextChapter, setNextChapter] = useState({});
|
||||
const [prevChapter, setPrevChapter] = useState({});
|
||||
|
||||
const setPrevAndNextChap = async () => {
|
||||
const nextChap = await getNextChapterFromDB(novelId, chapterId);
|
||||
const prevChap = await getPrevChapterFromDB(novelId, chapterId);
|
||||
|
||||
setNextChapter(nextChap);
|
||||
setPrevChapter(prevChap);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getChapter(chapterId);
|
||||
dispatch(insertHistoryAction(novelId, chapterId));
|
||||
setPrevAndNextChap();
|
||||
!incognitoMode && dispatch(insertHistoryAction(novelId, chapterId));
|
||||
return () => {
|
||||
StatusBar.setHidden(false);
|
||||
showNavigationBar();
|
||||
@@ -165,22 +190,26 @@ const Chapter = ({ route, navigation }) => {
|
||||
const percentage = Math.round(
|
||||
(position / nativeEvent.contentSize.height) * 100
|
||||
);
|
||||
|
||||
setScrollPercentage(percentage);
|
||||
dispatch(saveScrollPosition(offsetY, percentage, chapterId, novelId));
|
||||
|
||||
if (isCloseToBottom(nativeEvent)) {
|
||||
if (!incognitoMode) {
|
||||
dispatch(
|
||||
saveScrollPosition(offsetY, percentage, chapterId, novelId)
|
||||
);
|
||||
}
|
||||
|
||||
if (!incognitoMode && isCloseToBottom(nativeEvent)) {
|
||||
dispatch(markChapterReadAction(chapterId, novelId));
|
||||
updateTracker();
|
||||
}
|
||||
};
|
||||
|
||||
const scrollToSavedProgress = () => {
|
||||
const scrollToSavedProgress = (event) => {
|
||||
StatusBar.setHidden(true);
|
||||
hideNavigationBar();
|
||||
if (position && firstLayout) {
|
||||
position.percentage < 100 &&
|
||||
scrollViewRef.getNode().scrollTo({
|
||||
scrollViewRef.current.scrollTo({
|
||||
x: 0,
|
||||
y: position.position,
|
||||
animated: false,
|
||||
@@ -204,83 +233,124 @@ const Chapter = ({ route, navigation }) => {
|
||||
},
|
||||
];
|
||||
|
||||
const config = {
|
||||
velocityThreshold: 0.3,
|
||||
directionalOffsetThreshold: 80,
|
||||
};
|
||||
|
||||
const navigateToPrevChapter = () =>
|
||||
prevChapter
|
||||
? navigation.replace("Chapter", {
|
||||
chapterUrl: prevChapter.chapterUrl,
|
||||
chapterId: prevChapter.chapterId,
|
||||
sourceId,
|
||||
novelUrl,
|
||||
novelId,
|
||||
chapterName: prevChapter.chapterName,
|
||||
novelName,
|
||||
bookmark: prevChapter.bookmark,
|
||||
})
|
||||
: showToast("'There's no previous chapter");
|
||||
|
||||
const navigateToNextChapter = () =>
|
||||
nextChapter
|
||||
? navigation.replace("Chapter", {
|
||||
chapterUrl: nextChapter.chapterUrl,
|
||||
sourceId,
|
||||
novelUrl,
|
||||
novelId,
|
||||
chapterId: nextChapter.chapterId,
|
||||
chapterName: nextChapter.chapterName,
|
||||
novelName,
|
||||
bookmark: nextChapter.bookmark,
|
||||
})
|
||||
: showToast("'There's no next chapter");
|
||||
|
||||
return (
|
||||
<>
|
||||
<CollapsibleHeaderScrollView
|
||||
headerContainerBackgroundColor="rgba(0,0,0,0.5)"
|
||||
ref={(ref) => (scrollViewRef = ref)}
|
||||
CollapsibleHeaderComponent={
|
||||
<ChapterAppbar
|
||||
navigation={navigation}
|
||||
sourceId={sourceId}
|
||||
novelId={novelId}
|
||||
novelUrl={novelUrl}
|
||||
novelName={novelName}
|
||||
chapterName={chapterName}
|
||||
chapterId={chapterId}
|
||||
chapter={chapter}
|
||||
theme={theme}
|
||||
readerSheetRef={readerSheetRef}
|
||||
/>
|
||||
}
|
||||
headerHeight={Constants.statusBarHeight + 56}
|
||||
<ChapterAppbar
|
||||
novelName={novelName}
|
||||
chapterName={chapterName}
|
||||
chapterId={chapterId}
|
||||
bookmark={bookmark}
|
||||
readerSheetRef={readerSheetRef}
|
||||
hide={hidden}
|
||||
navigation={navigation}
|
||||
dispatch={dispatch}
|
||||
/>
|
||||
<ScrollView
|
||||
ref={(ref) => (scrollViewRef.current = ref)}
|
||||
contentContainerStyle={[
|
||||
styles.screenContainer,
|
||||
{ backgroundColor: readerBackground(reader.theme) },
|
||||
]}
|
||||
onScroll={onScroll}
|
||||
onContentSizeChange={(x, y) => setContentSize(y)}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{error ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<EmptyView
|
||||
icon="(-_-;)・・・"
|
||||
description={error}
|
||||
style={{ color: errorTextColor(reader.theme) }}
|
||||
>
|
||||
<IconButton
|
||||
icon="reload"
|
||||
size={25}
|
||||
style={{ margin: 0, marginTop: 16 }}
|
||||
color={errorTextColor(reader.theme)}
|
||||
onPress={() => {
|
||||
getChapter(chapterId);
|
||||
setLoading(true);
|
||||
setError();
|
||||
}}
|
||||
/>
|
||||
<Text
|
||||
<GestureRecognizer
|
||||
onSwipeRight={swipeGestures && navigateToPrevChapter}
|
||||
onSwipeLeft={swipeGestures && navigateToNextChapter}
|
||||
config={config}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
{error ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<EmptyView
|
||||
icon="(-_-;)・・・"
|
||||
description={error}
|
||||
style={{ color: errorTextColor(reader.theme) }}
|
||||
>
|
||||
Retry
|
||||
</Text>
|
||||
</EmptyView>
|
||||
</View>
|
||||
) : loading ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<ActivityIndicator
|
||||
size={50}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<View style={{ flex: 1 }}>
|
||||
{images.length > 0 &&
|
||||
images.map((image, index) => (
|
||||
<FitImage source={{ uri: image }} />
|
||||
))}
|
||||
<IconButton
|
||||
icon="reload"
|
||||
size={25}
|
||||
style={{ margin: 0, marginTop: 16 }}
|
||||
color={errorTextColor(reader.theme)}
|
||||
onPress={() => {
|
||||
getChapter(chapterId);
|
||||
setLoading(true);
|
||||
setError();
|
||||
}}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: errorTextColor(reader.theme),
|
||||
}}
|
||||
>
|
||||
Retry
|
||||
</Text>
|
||||
</EmptyView>
|
||||
</View>
|
||||
) : loading ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<ActivityIndicator
|
||||
size={50}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<View style={{ flex: 1 }}>
|
||||
{images.length > 0 &&
|
||||
images.map((image, index) => (
|
||||
<FitImage
|
||||
source={{ uri: image }}
|
||||
key={index}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Text
|
||||
style={readerStyles}
|
||||
onLayout={scrollToSavedProgress}
|
||||
selectable={selectText}
|
||||
>
|
||||
{chapter.chapterText
|
||||
.trim()
|
||||
.replace(/\[https.*?\]/g, "")}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<Text
|
||||
style={readerStyles}
|
||||
onLayout={scrollToSavedProgress}
|
||||
selectable={selectText}
|
||||
onPress={() => setHidden(!hidden)}
|
||||
>
|
||||
{chapter.chapterText
|
||||
.trim()
|
||||
.replace(/\[https.*?\]/g, "")}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</GestureRecognizer>
|
||||
<Portal>
|
||||
<ReaderSheet
|
||||
theme={theme}
|
||||
@@ -292,7 +362,28 @@ const Chapter = ({ route, navigation }) => {
|
||||
showScrollPercentage={showScrollPercentage}
|
||||
/>
|
||||
</Portal>
|
||||
</CollapsibleHeaderScrollView>
|
||||
</ScrollView>
|
||||
<VerticalScrollbar
|
||||
theme={theme}
|
||||
hide={hidden}
|
||||
setLoading={setLoading}
|
||||
contentSize={contentSize}
|
||||
scrollViewRef={scrollViewRef}
|
||||
scrollPercentage={scrollPercentage}
|
||||
setScrollPercentage={setScrollPercentage}
|
||||
/>
|
||||
<ChapterFooter
|
||||
theme={theme}
|
||||
swipeGestures={swipeGestures}
|
||||
dispatch={dispatch}
|
||||
nextChapter={nextChapter}
|
||||
prevChapter={prevChapter}
|
||||
hide={hidden}
|
||||
navigateToNextChapter={navigateToNextChapter}
|
||||
navigateToPrevChapter={navigateToPrevChapter}
|
||||
readerSheetRef={readerSheetRef}
|
||||
scrollViewRef={scrollViewRef}
|
||||
/>
|
||||
{showScrollPercentage && (
|
||||
<Text
|
||||
style={[
|
||||
@@ -315,7 +406,7 @@ export default Chapter;
|
||||
const styles = StyleSheet.create({
|
||||
screenContainer: {
|
||||
flexGrow: 1,
|
||||
paddingVertical: 10,
|
||||
paddingTop: StatusBar.currentHeight,
|
||||
},
|
||||
scrollPercentage: {
|
||||
width: "100%",
|
||||
@@ -0,0 +1,68 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Dimensions, View } from "react-native";
|
||||
|
||||
import { Appbar } from "react-native-paper";
|
||||
import {
|
||||
getNextChapterFromDB,
|
||||
getPrevChapterFromDB,
|
||||
} from "../../../database/queries/ChapterQueries";
|
||||
import { bookmarkChapterAction } from "../../../redux/novel/novel.actions";
|
||||
|
||||
const ChapterAppbar = ({
|
||||
navigation,
|
||||
bookmark,
|
||||
novelName,
|
||||
chapterId,
|
||||
chapterName,
|
||||
hide,
|
||||
dispatch,
|
||||
}) => {
|
||||
const [bookmarked, setBookmarked] = useState(bookmark);
|
||||
|
||||
if (hide) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
flex: 1,
|
||||
backgroundColor: "rgba(0,0,0,0.6)",
|
||||
width: "100%",
|
||||
top: 0,
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
<Appbar.Header
|
||||
style={{ backgroundColor: "transparent", elevation: 0 }}
|
||||
>
|
||||
<Appbar.BackAction
|
||||
onPress={navigation.goBack}
|
||||
color="#FFFFFF"
|
||||
size={26}
|
||||
style={{ marginRight: 0 }}
|
||||
/>
|
||||
<Appbar.Content
|
||||
title={novelName}
|
||||
titleStyle={{ color: "#FFFFFF" }}
|
||||
subtitle={chapterName}
|
||||
subtitleStyle={{ color: "rgba(255, 255, 255, 0.7)" }}
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon={bookmarked ? "bookmark" : "bookmark-outline"}
|
||||
size={26}
|
||||
onPress={() => {
|
||||
dispatch(
|
||||
bookmarkChapterAction([{ bookmark, chapterId }])
|
||||
);
|
||||
setBookmarked(!bookmarked);
|
||||
}}
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
</Appbar.Header>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default ChapterAppbar;
|
||||
@@ -0,0 +1,119 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Dimensions, Pressable, StyleSheet, Text, View } from "react-native";
|
||||
import { IconButton } from "react-native-paper";
|
||||
import {
|
||||
getNextChapterFromDB,
|
||||
getPrevChapterFromDB,
|
||||
} from "../../../database/queries/ChapterQueries";
|
||||
import { useSettings } from "../../../hooks/reduxHooks";
|
||||
import { showToast } from "../../../hooks/showToast";
|
||||
import { setAppSettings } from "../../../redux/settings/settings.actions";
|
||||
|
||||
const ChapterFooter = ({
|
||||
hide,
|
||||
theme,
|
||||
swipeGestures,
|
||||
dispatch,
|
||||
readerSheetRef,
|
||||
scrollViewRef,
|
||||
navigateToNextChapter,
|
||||
navigateToPrevChapter,
|
||||
nextChapter,
|
||||
prevChapter,
|
||||
}) => {
|
||||
const rippleConfig = {
|
||||
color: theme.rippleColor,
|
||||
borderless: true,
|
||||
radius: 50,
|
||||
};
|
||||
|
||||
const enableSwipeGestures = () => {
|
||||
dispatch(setAppSettings("swipeGestures", !swipeGestures));
|
||||
showToast(
|
||||
swipeGestures ? "Swipe gestures disabled" : "Swipe gestured enabled"
|
||||
);
|
||||
};
|
||||
|
||||
if (hide) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
zIndex: 2,
|
||||
bottom: 0,
|
||||
width: "100%",
|
||||
backgroundColor: "rgba(0,0,0,0.6)",
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={navigateToPrevChapter}
|
||||
>
|
||||
<IconButton
|
||||
icon="chevron-left"
|
||||
size={26}
|
||||
disabled={!prevChapter}
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={enableSwipeGestures}
|
||||
>
|
||||
<IconButton
|
||||
icon="gesture-swipe"
|
||||
disabled={!swipeGestures}
|
||||
size={26}
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={() => scrollViewRef.current.scrollTo({})}
|
||||
>
|
||||
<IconButton
|
||||
icon="format-vertical-align-top"
|
||||
size={26}
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={() => readerSheetRef.current.show()}
|
||||
>
|
||||
<IconButton icon="cog-outline" size={26} color="#FFFFFF" />
|
||||
</Pressable>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={navigateToNextChapter}
|
||||
>
|
||||
<IconButton
|
||||
icon="chevron-right"
|
||||
size={26}
|
||||
disabled={!nextChapter}
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default ChapterFooter;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttonStyles: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingVertical: 8,
|
||||
},
|
||||
});
|
||||
+7
-6
@@ -3,14 +3,14 @@ import { StyleSheet, View, Text, Dimensions } from "react-native";
|
||||
import { ToggleButton, IconButton, Menu, Switch } from "react-native-paper";
|
||||
import Slider from "@react-native-community/slider";
|
||||
import Bottomsheet from "rn-sliding-up-panel";
|
||||
import { fonts } from "../../../Services/utils/constants";
|
||||
import { fonts } from "../../../services/utils/constants";
|
||||
|
||||
import {
|
||||
setAppSettings,
|
||||
setReaderSettings,
|
||||
} from "../../../redux/settings/settings.actions";
|
||||
import BottomSheetHandle from "../../../Components/BottomSheetHandle";
|
||||
import { Row } from "../../../Components/Common";
|
||||
import BottomSheetHandle from "../../../components/BottomSheetHandle";
|
||||
import { Row } from "../../../components/Common";
|
||||
|
||||
const ReaderSheet = ({
|
||||
theme,
|
||||
@@ -39,8 +39,8 @@ const ReaderSheet = ({
|
||||
return (
|
||||
<Bottomsheet
|
||||
ref={bottomSheetRef}
|
||||
draggableRange={{ top: 450, bottom: 0 }}
|
||||
snappingPoints={[0, 450]}
|
||||
draggableRange={{ top: 470, bottom: 0 }}
|
||||
snappingPoints={[0, 470]}
|
||||
showBackdrop={true}
|
||||
backdropOpacity={0}
|
||||
>
|
||||
@@ -60,7 +60,7 @@ const ReaderSheet = ({
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor="#000000"
|
||||
thumbTintColor={theme.colorAccent}
|
||||
onValueChange={(value) =>
|
||||
onSlidingComplete={(value) =>
|
||||
dispatch(setReaderSettings("textSize", value))
|
||||
}
|
||||
/>
|
||||
@@ -97,6 +97,7 @@ const ReaderSheet = ({
|
||||
style={{
|
||||
backgroundColor: "#000000",
|
||||
marginHorizontal: 10,
|
||||
borderWidth: 0,
|
||||
borderRadius: 50,
|
||||
borderTopRightRadius: 50,
|
||||
borderBottomRightRadius: 50,
|
||||
@@ -0,0 +1,131 @@
|
||||
import React from "react";
|
||||
import { Dimensions, View, Text, StyleSheet } from "react-native";
|
||||
|
||||
import Slider from "@react-native-community/slider";
|
||||
import { getDeviceOrientation } from "../../../services/utils/helpers";
|
||||
|
||||
const VerticalScrollbar = ({
|
||||
theme,
|
||||
hide,
|
||||
contentSize,
|
||||
scrollPercentage,
|
||||
setLoading,
|
||||
scrollViewRef,
|
||||
setScrollPercentage,
|
||||
}) => {
|
||||
const onSlidingComplete = (value) => {
|
||||
setLoading(true);
|
||||
// setScrollPercentage(value);
|
||||
scrollViewRef.current.scrollTo({
|
||||
x: 0,
|
||||
y: Math.round((value * contentSize) / 100),
|
||||
animated: false,
|
||||
});
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
if (hide) {
|
||||
return null;
|
||||
} else if (getDeviceOrientation() === "potrait") {
|
||||
return (
|
||||
<View style={styles.verticalSliderContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
marginLeft: 16,
|
||||
transform: [{ rotate: "-90deg" }],
|
||||
}}
|
||||
>
|
||||
{scrollPercentage}
|
||||
</Text>
|
||||
<Slider
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 40,
|
||||
}}
|
||||
minimumValue={0}
|
||||
maximumValue={100}
|
||||
step={1}
|
||||
value={scrollPercentage}
|
||||
onSlidingComplete={onSlidingComplete}
|
||||
thumbTintColor={theme.colorAccent}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor={theme.textColorPrimary}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
marginRight: 16,
|
||||
transform: [{ rotate: "-90deg" }],
|
||||
}}
|
||||
>
|
||||
100
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<View style={styles.horizontalSliderContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
marginLeft: 16,
|
||||
}}
|
||||
>
|
||||
{scrollPercentage}
|
||||
</Text>
|
||||
<Slider
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 40,
|
||||
}}
|
||||
minimumValue={0}
|
||||
maximumValue={100}
|
||||
step={1}
|
||||
value={scrollPercentage}
|
||||
onSlidingComplete={onSlidingComplete}
|
||||
thumbTintColor={theme.colorAccent}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor={theme.textColorPrimary}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
marginRight: 16,
|
||||
}}
|
||||
>
|
||||
100
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default VerticalScrollbar;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
verticalSliderContainer: {
|
||||
position: "absolute",
|
||||
zIndex: 2,
|
||||
right: -(Dimensions.get("window").width / 2) + 40,
|
||||
bottom: 300,
|
||||
backgroundColor: "rgba(0,0,0,0.7)",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 8,
|
||||
transform: [{ rotate: "90deg" }],
|
||||
},
|
||||
horizontalSliderContainer: {
|
||||
position: "absolute",
|
||||
zIndex: 2,
|
||||
bottom: 100,
|
||||
backgroundColor: "rgba(0,0,0,0.7)",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 8,
|
||||
},
|
||||
});
|
||||
@@ -1,20 +1,22 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { StyleSheet, View, ActivityIndicator } from "react-native";
|
||||
import { StyleSheet, View, ActivityIndicator, Text } from "react-native";
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
|
||||
import NovelCover from "../../Components/NovelCover";
|
||||
import EmptyView from "../../Components/EmptyView";
|
||||
import NovelCover from "../../components/NovelCover";
|
||||
import EmptyView from "../../components/EmptyView";
|
||||
|
||||
import { Searchbar } from "../../Components/Searchbar";
|
||||
import { useTheme, useLibrary, useSettings } from "../../Hooks/reduxHooks";
|
||||
import NovelList from "../../Components/NovelList";
|
||||
import { showToast } from "../../Hooks/showToast";
|
||||
import ErrorView from "../../Components/ErrorView";
|
||||
import { Searchbar } from "../../components/Searchbar";
|
||||
import { useTheme, useLibrary, useSettings } from "../../hooks/reduxHooks";
|
||||
import NovelList from "../../components/NovelList";
|
||||
import { showToast } from "../../hooks/showToast";
|
||||
import ErrorView from "../../components/ErrorView";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { setAppSettings } from "../../redux/settings/settings.actions";
|
||||
|
||||
import { getSource } from "../../sources/sources";
|
||||
|
||||
const Extension = ({ navigation, route }) => {
|
||||
const { sourceId, sourceName, sourceUrl } = route.params;
|
||||
const { sourceId, sourceName, url } = route.params;
|
||||
|
||||
const theme = useTheme();
|
||||
const library = useLibrary();
|
||||
@@ -22,63 +24,52 @@ const Extension = ({ navigation, route }) => {
|
||||
const { displayMode } = useSettings();
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [novels, setNovels] = useState();
|
||||
const [novels, setNovels] = useState([]);
|
||||
const [page, setPage] = useState(1);
|
||||
const [error, setError] = useState();
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const getSourceUrl = () => {
|
||||
if (sourceId === 1) {
|
||||
return `https://lnreader-extensions.vercel.app/api/1/novels/1/?o=rating`;
|
||||
} else {
|
||||
return `https://lnreader-extensions.vercel.app/api/${sourceId}/novels/`;
|
||||
}
|
||||
};
|
||||
|
||||
const getSearchUrl = () => {
|
||||
if (sourceId === 1) {
|
||||
return `https://lnreader-extensions.vercel.app/api/1/search/?s=${searchText}&?o=rating`;
|
||||
} else {
|
||||
return `https://lnreader-extensions.vercel.app/api/${sourceId}/search/?s=${searchText}`;
|
||||
}
|
||||
};
|
||||
|
||||
const getNovels = async () => {
|
||||
try {
|
||||
const url = getSourceUrl();
|
||||
|
||||
const res = await fetch(url);
|
||||
const data = await res.json();
|
||||
|
||||
setNovels(data);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setError(error.message);
|
||||
setNovels([]);
|
||||
setLoading(false);
|
||||
showToast(error.message);
|
||||
}
|
||||
};
|
||||
const incrementPage = () => setPage((page) => page + 1);
|
||||
|
||||
const clearSearchbar = () => {
|
||||
getNovels();
|
||||
setNovels([]);
|
||||
setPage(1);
|
||||
setLoading(true);
|
||||
setSearchText("");
|
||||
};
|
||||
|
||||
const getSearchResults = () => {
|
||||
setLoading(true);
|
||||
fetch(getSearchUrl())
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
setNovels(json);
|
||||
const getNovels = async () => {
|
||||
try {
|
||||
if (page <= totalPages) {
|
||||
const source = getSource(sourceId);
|
||||
const res = await source.popularNovels(page);
|
||||
setNovels((novels) => [...novels, ...res.novels]);
|
||||
setTotalPages(res.totalPages);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
setNovels([]);
|
||||
setLoading(false);
|
||||
showToast(error.message);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
setError(error.message);
|
||||
showToast(error.message);
|
||||
setNovels([]);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getSearchResults = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const source = getSource(sourceId);
|
||||
const res = await source.searchNovels(searchText);
|
||||
setNovels(res);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setError(error.message);
|
||||
showToast(error.message);
|
||||
setNovels([]);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const checkIFInLibrary = (sourceId, novelUrl) => {
|
||||
@@ -88,8 +79,29 @@ const Extension = ({ navigation, route }) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
getNovels();
|
||||
}, []);
|
||||
|
||||
return () => (mounted = false);
|
||||
}, [page]);
|
||||
|
||||
const isCloseToBottom = ({
|
||||
layoutMeasurement,
|
||||
contentOffset,
|
||||
contentSize,
|
||||
}) => {
|
||||
const paddingToBottom = 20;
|
||||
return (
|
||||
layoutMeasurement.height + contentOffset.y >=
|
||||
contentSize.height - paddingToBottom
|
||||
);
|
||||
};
|
||||
|
||||
const onScroll = ({ nativeEvent }) => {
|
||||
if (!searchText && isCloseToBottom(nativeEvent)) {
|
||||
incrementPage();
|
||||
}
|
||||
};
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<NovelCover
|
||||
@@ -99,10 +111,10 @@ const Extension = ({ navigation, route }) => {
|
||||
novelName: item.novelName,
|
||||
novelCover: item.novelCover,
|
||||
novelUrl: item.novelUrl,
|
||||
sourceId: item.extensionId,
|
||||
sourceId,
|
||||
})
|
||||
}
|
||||
libraryStatus={checkIFInLibrary(item.extensionId, item.novelUrl)}
|
||||
libraryStatus={checkIFInLibrary(item.sourceId, item.novelUrl)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -114,7 +126,7 @@ const Extension = ({ navigation, route }) => {
|
||||
setLoading(true);
|
||||
setError();
|
||||
}}
|
||||
openWebView={() => WebBrowser.openBrowserAsync(sourceUrl)}
|
||||
openWebView={() => WebBrowser.openBrowserAsync(url)}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
@@ -160,7 +172,7 @@ const Extension = ({ navigation, route }) => {
|
||||
{
|
||||
icon: "earth",
|
||||
color: theme.textColorSecondary,
|
||||
onPress: () => WebBrowser.openBrowserAsync(sourceUrl),
|
||||
onPress: () => WebBrowser.openBrowserAsync(url),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -174,6 +186,15 @@ const Extension = ({ navigation, route }) => {
|
||||
data={novels}
|
||||
renderItem={renderItem}
|
||||
ListEmptyComponent={listEmptyComponent()}
|
||||
onScroll={onScroll}
|
||||
ListFooterComponent={
|
||||
!searchText &&
|
||||
page < totalPages && (
|
||||
<View style={{ paddingVertical: 16 }}>
|
||||
<ActivityIndicator color={theme.colorAccent} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
@@ -4,15 +4,15 @@ import { StyleSheet, FlatList, ActivityIndicator, Text } from "react-native";
|
||||
import moment from "moment";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import EmptyView from "../../Components/EmptyView";
|
||||
import EmptyView from "../../components/EmptyView";
|
||||
import HistoryItem from "./HistoryItem";
|
||||
import RemoveHistoryDialog from "./RemoveHistoryDialog";
|
||||
import { ScreenContainer } from "../../Components/Common";
|
||||
import { Searchbar } from "../../Components/Searchbar";
|
||||
import { ScreenContainer } from "../../components/Common";
|
||||
import { Searchbar } from "../../components/Searchbar";
|
||||
|
||||
import { getHistoryAction } from "../../redux/history/history.actions";
|
||||
import { dateFormat } from "../../Services/utils/constants";
|
||||
import { useTheme } from "../../Hooks/reduxHooks";
|
||||
import { dateFormat } from "../../services/utils/constants";
|
||||
import { useTheme } from "../../hooks/reduxHooks";
|
||||
|
||||
const History = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
@@ -80,7 +80,7 @@ const History = ({ navigation }) => {
|
||||
theme={theme}
|
||||
actions={[
|
||||
{
|
||||
icon: "trash-can-outline",
|
||||
icon: "delete-sweep",
|
||||
color: theme.textColorSecondary,
|
||||
onPress: showDialog,
|
||||
},
|
||||
@@ -4,8 +4,9 @@ import { Pressable, StyleSheet, View, Image, Text } from "react-native";
|
||||
import moment from "moment";
|
||||
import { IconButton } from "react-native-paper";
|
||||
|
||||
import { parseChapterNumber } from "../../Services/utils/helpers";
|
||||
import { parseChapterNumber } from "../../services/utils/helpers";
|
||||
import { deleteHistoryAction } from "../../redux/history/history.actions";
|
||||
import { setNovel } from "../../redux/novel/novel.actions";
|
||||
|
||||
const HistoryItem = ({ history, theme, dispatch, navigation }) => {
|
||||
const {
|
||||
@@ -19,6 +20,7 @@ const HistoryItem = ({ history, theme, dispatch, navigation }) => {
|
||||
novelCover,
|
||||
chapterName,
|
||||
chapterUrl,
|
||||
bookmark,
|
||||
} = history;
|
||||
|
||||
const getChapterNumber = () =>
|
||||
@@ -26,7 +28,10 @@ const HistoryItem = ({ history, theme, dispatch, navigation }) => {
|
||||
.format("h:mm a")
|
||||
.toUpperCase()}`;
|
||||
|
||||
const navigateToNovel = () => navigation.navigate("Novel", history);
|
||||
const navigateToNovel = () => {
|
||||
navigation.navigate("Novel", history);
|
||||
dispatch(setNovel(history));
|
||||
};
|
||||
|
||||
const navigateToChapter = () =>
|
||||
navigation.navigate("Chapter", {
|
||||
@@ -37,6 +42,7 @@ const HistoryItem = ({ history, theme, dispatch, navigation }) => {
|
||||
novelId,
|
||||
chapterName,
|
||||
novelName,
|
||||
bookmark,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -9,18 +9,19 @@ import {
|
||||
} from "react-native";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
|
||||
import { Searchbar } from "../../Components/Searchbar";
|
||||
import NovelList from "../../Components/NovelList";
|
||||
import NovelCover from "../../Components/NovelCover";
|
||||
import EmptyView from "../../Components/EmptyView";
|
||||
import { Searchbar } from "../../components/Searchbar";
|
||||
import NovelList from "../../components/NovelList";
|
||||
import NovelCover from "../../components/NovelCover";
|
||||
import EmptyView from "../../components/EmptyView";
|
||||
|
||||
import {
|
||||
getLibraryAction,
|
||||
searchLibraryAction,
|
||||
} from "../../redux/library/library.actions";
|
||||
import { updateLibraryAction } from "../../redux/updates/updates.actions";
|
||||
import { useSettings, useTheme } from "../../Hooks/reduxHooks";
|
||||
import { useSettings, useTheme } from "../../hooks/reduxHooks";
|
||||
import { setNovel } from "../../redux/novel/novel.actions";
|
||||
import { Portal, TouchableRipple } from "react-native-paper";
|
||||
import LibraryFilterSheet from "./components/LibraryFilterSheet";
|
||||
@@ -28,7 +29,7 @@ import LibraryFilterSheet from "./components/LibraryFilterSheet";
|
||||
const LibraryScreen = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const { showNumberOfNovels } = useSettings();
|
||||
const { showNumberOfNovels, incognitoMode = false } = useSettings();
|
||||
|
||||
const libraryFilterSheetRef = useRef(null);
|
||||
|
||||
@@ -122,6 +123,17 @@ const LibraryScreen = ({ navigation }) => {
|
||||
]}
|
||||
theme={theme}
|
||||
/>
|
||||
{incognitoMode && (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "#424242",
|
||||
paddingVertical: 4,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: "#FFFFFF" }}>Incognito Mode</Text>
|
||||
</View>
|
||||
)}
|
||||
{loading ? (
|
||||
<ActivityIndicator size="small" color={theme.colorAccent} />
|
||||
) : (
|
||||
+2
-2
@@ -11,9 +11,9 @@ import { TabView, SceneMap, TabBar } from "react-native-tab-view";
|
||||
import { Checkbox } from "react-native-paper";
|
||||
|
||||
import Bottomsheet from "rn-sliding-up-panel";
|
||||
import { ListItem } from "../../../Components/List";
|
||||
import { ListItem } from "../../../components/List";
|
||||
import { filterLibrary } from "../../../redux/library/library.actions";
|
||||
import { useSettings } from "../../../Hooks/reduxHooks";
|
||||
import { useSettings } from "../../../hooks/reduxHooks";
|
||||
import { setAppSettings } from "../../../redux/settings/settings.actions";
|
||||
|
||||
const LibraryFilterSheet = ({
|
||||
@@ -2,10 +2,10 @@ import React from "react";
|
||||
import { View } from "react-native";
|
||||
import * as Linking from "expo-linking";
|
||||
|
||||
import { Appbar } from "../../Components/Appbar";
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
import { Divider, ListItem, ListSection } from "../../Components/List";
|
||||
import { Divider, ListItem, ListSection } from "../../components/List";
|
||||
|
||||
const AboutScreen = ({ navigation }) => {
|
||||
const theme = useSelector((state) => state.settingsReducer.theme);
|
||||
@@ -17,12 +17,12 @@ const AboutScreen = ({ navigation }) => {
|
||||
<ListSection>
|
||||
<ListItem
|
||||
title="Version"
|
||||
description="Stable 1.0.21"
|
||||
description="Stable 1.1.0"
|
||||
theme={theme}
|
||||
/>
|
||||
<ListItem
|
||||
title="Build Time"
|
||||
description="09-06-21 14:43 PM"
|
||||
description="20-06-21 21:45 PM"
|
||||
theme={theme}
|
||||
/>
|
||||
<ListItem
|
||||
@@ -1,14 +1,14 @@
|
||||
import React, { useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
import { Appbar } from "../../Components/Appbar";
|
||||
import { ScreenContainer } from "../../Components/Common";
|
||||
import { ListItem, ListSection, ListSubHeader } from "../../Components/List";
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
import { ScreenContainer } from "../../components/Common";
|
||||
import { ListItem, ListSection, ListSubHeader } from "../../components/List";
|
||||
|
||||
import { useTheme } from "../../Hooks/reduxHooks";
|
||||
import { useTheme } from "../../hooks/reduxHooks";
|
||||
|
||||
import { clearAllHistoryAction } from "../../redux/history/history.actions";
|
||||
import { deleteNovelCache } from "../../Database/queries/NovelQueries";
|
||||
import { deleteNovelCache } from "../../database/queries/NovelQueries";
|
||||
import { Button, Dialog, Portal } from "react-native-paper";
|
||||
|
||||
const AdvancedSettings = ({ navigation }) => {
|
||||
@@ -1,18 +1,18 @@
|
||||
import React from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
import { Appbar } from "../../Components/Appbar";
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
import {
|
||||
InfoItem,
|
||||
ListItem,
|
||||
ListSection,
|
||||
ListSubHeader,
|
||||
} from "../../Components/List";
|
||||
import { ScreenContainer } from "../../Components/Common";
|
||||
} from "../../components/List";
|
||||
import { ScreenContainer } from "../../components/Common";
|
||||
|
||||
import { useTheme } from "../../Hooks/reduxHooks";
|
||||
import { useTheme } from "../../hooks/reduxHooks";
|
||||
import { restoreLibraryAction } from "../../redux/library/library.actions";
|
||||
import { createBackup } from "../../Services/backup";
|
||||
import { createBackup } from "../../services/backup";
|
||||
|
||||
const BackupSettings = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
@@ -0,0 +1,76 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { FlatList, View, Text, StyleSheet } from "react-native";
|
||||
import { FAB, ProgressBar } from "react-native-paper";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
import { ScreenContainer } from "../../components/Common";
|
||||
import EmptyView from "../../components/EmptyView";
|
||||
|
||||
import { useTheme } from "../../hooks/reduxHooks";
|
||||
|
||||
import { cancelDownload } from "../../redux/downloads/downloads.actions";
|
||||
|
||||
import BackgroundService from "react-native-background-actions";
|
||||
import { downloadAllChaptersAction } from "../../redux/novel/novel.actions";
|
||||
|
||||
const DownloadQueue = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
const { downloadQueue } = useSelector((state) => state.downloadsReducer);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (!BackgroundService.isRunning() && downloadQueue.length > 0) {
|
||||
dispatch(cancelDownload());
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ScreenContainer theme={theme}>
|
||||
<Appbar title="Download queue" onBackAction={navigation.goBack} />
|
||||
<FlatList
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
keyExtractor={(item) => item.chapterId.toString()}
|
||||
data={downloadQueue}
|
||||
renderItem={({ item }) => (
|
||||
<View style={{ padding: 16 }}>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
{item.chapterName}
|
||||
</Text>
|
||||
<ProgressBar
|
||||
indeterminate={true}
|
||||
color={theme.colorAccent}
|
||||
style={{ marginTop: 8 }}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
ListEmptyComponent={
|
||||
<EmptyView icon="(・o・;)" description="No downloads" />
|
||||
}
|
||||
/>
|
||||
{downloadQueue.length > 0 && (
|
||||
<FAB
|
||||
style={[styles.fab, { backgroundColor: theme.colorAccent }]}
|
||||
color={theme.textColorPrimary}
|
||||
label="Cancel"
|
||||
uppercase={false}
|
||||
small
|
||||
icon="close"
|
||||
onPress={() => dispatch(cancelDownload())}
|
||||
/>
|
||||
)}
|
||||
</ScreenContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default DownloadQueue;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
fab: {
|
||||
position: "absolute",
|
||||
margin: 16,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
});
|
||||
@@ -1,22 +1,21 @@
|
||||
import React, { useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import { Appbar } from "../../Components/Appbar";
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
import {
|
||||
Divider,
|
||||
ListItem,
|
||||
ListSection,
|
||||
ListSubHeader,
|
||||
} from "../../Components/List";
|
||||
import { ScreenContainer } from "../../Components/Common";
|
||||
} from "../../components/List";
|
||||
import { ScreenContainer } from "../../components/Common";
|
||||
|
||||
import { useTheme } from "../../Hooks/reduxHooks";
|
||||
import { useTheme } from "../../hooks/reduxHooks";
|
||||
import DisplayModeModal from "./components/DisplayModeModal";
|
||||
import GridSizeModal from "./components/GridSizeModal";
|
||||
import ThemeModal from "./components/ThemeModal";
|
||||
import AccentColorModal from "./components/AccentColorModal";
|
||||
import { setAccentColor } from "../../redux/settings/settings.actions";
|
||||
import ColorPickerModal from "../../Components/ColorPickerModal";
|
||||
import ColorPickerModal from "../../components/ColorPickerModal";
|
||||
|
||||
const GenralSettings = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
@@ -0,0 +1,130 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View, Image, Pressable, Text } from "react-native";
|
||||
import { List, Switch } from "react-native-paper";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
import { Divider, ListItem, ListSection } from "../../components/List";
|
||||
|
||||
import { useSettings, useTheme } from "../../hooks/reduxHooks";
|
||||
import { setAppSettings } from "../../redux/settings/settings.actions";
|
||||
|
||||
const MoreScreen = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
const { downloadQueue } = useSelector((state) => state.downloadsReducer);
|
||||
const { incognitoMode } = useSettings();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const enableIncognitoMode = () => {
|
||||
dispatch(setAppSettings("incognitoMode", !incognitoMode));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar title="More" style={{ elevation: 0 }} />
|
||||
<View style={{ flex: 1, backgroundColor: theme.colorPrimaryDark }}>
|
||||
<View style={{ overflow: "hidden", paddingBottom: 4 }}>
|
||||
<View
|
||||
style={{
|
||||
paddingTop: 10,
|
||||
paddingBottom: 25,
|
||||
alignItems: "center",
|
||||
backgroundColor: theme.colorPrimary,
|
||||
elevation: 4,
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={require("../../../assets/logo.png")}
|
||||
style={{
|
||||
height: 80,
|
||||
width: 80,
|
||||
tintColor: theme.textColorPrimary,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<ListSection>
|
||||
<Pressable
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
style={{
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
onPress={enableIncognitoMode}
|
||||
>
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
<List.Icon
|
||||
color={theme.colorAccent}
|
||||
icon="incognito"
|
||||
style={{ margin: 0 }}
|
||||
/>
|
||||
<View style={{ marginLeft: 16 }}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 16,
|
||||
}}
|
||||
>
|
||||
Incognito Mode
|
||||
</Text>
|
||||
<Text
|
||||
style={{ color: theme.textColorSecondary }}
|
||||
>
|
||||
Pauses reading history
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Switch
|
||||
value={incognitoMode}
|
||||
onValueChange={enableIncognitoMode}
|
||||
color={theme.colorAccent}
|
||||
style={{ marginRight: 8 }}
|
||||
/>
|
||||
</Pressable>
|
||||
|
||||
<ListItem
|
||||
title="Download queue"
|
||||
description={
|
||||
downloadQueue.length > 0 &&
|
||||
downloadQueue.length + " remaining"
|
||||
}
|
||||
icon="download-outline"
|
||||
onPress={() =>
|
||||
navigation.navigate("MoreStack", {
|
||||
screen: "DownloadQueue",
|
||||
})
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<ListItem
|
||||
title="Settings"
|
||||
icon="cog-outline"
|
||||
onPress={() =>
|
||||
navigation.navigate("MoreStack", {
|
||||
screen: "SettingsStack",
|
||||
})
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
|
||||
<ListItem
|
||||
title="About"
|
||||
icon="information-outline"
|
||||
onPress={() =>
|
||||
navigation.navigate("MoreStack", {
|
||||
screen: "About",
|
||||
})
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
</ListSection>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MoreScreen;
|
||||
|
||||
const styles = StyleSheet.create({});
|
||||
@@ -3,12 +3,12 @@ import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
|
||||
import { ToggleButton } from "react-native-paper";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
import { Appbar } from "../../Components/Appbar";
|
||||
import ColorPickerModal from "../../Components/ColorPickerModal";
|
||||
import { Row, ScreenContainer } from "../../Components/Common";
|
||||
import { ListItem, ListSection, ListSubHeader } from "../../Components/List";
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
import ColorPickerModal from "../../components/ColorPickerModal";
|
||||
import { Row, ScreenContainer } from "../../components/Common";
|
||||
import { ListItem, ListSection, ListSubHeader } from "../../components/List";
|
||||
|
||||
import { useReaderSettings, useTheme } from "../../Hooks/reduxHooks";
|
||||
import { useReaderSettings, useTheme } from "../../hooks/reduxHooks";
|
||||
import { setReaderSettings } from "../../redux/settings/settings.actions";
|
||||
import {
|
||||
readerBackground,
|
||||
@@ -1,32 +1,17 @@
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { connect, useDispatch } from "react-redux";
|
||||
|
||||
import { setAppTheme } from "../../redux/settings/settings.actions";
|
||||
import {
|
||||
setAppSettings,
|
||||
setItemsPerRow,
|
||||
} from "../../redux/settings/settings.actions";
|
||||
import { deleteNovelCache } from "../../Database/queries/NovelQueries";
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
import { ListItem } from "../../components/List";
|
||||
|
||||
import { Appbar } from "../../Components/Appbar";
|
||||
import {
|
||||
Divider,
|
||||
ListItem,
|
||||
ListSection,
|
||||
ListSubHeader,
|
||||
} from "../../Components/List";
|
||||
import { useTheme } from "../../Hooks/reduxHooks";
|
||||
import { clearAllHistoryAction } from "../../redux/history/history.actions";
|
||||
import { useTheme } from "../../hooks/reduxHooks";
|
||||
|
||||
const SettingsScreen = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar title="Settings" onBackAction={() => navigation.goBack()} />
|
||||
<Appbar title="Settings" onBackAction={navigation.goBack} />
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
myAnimeListConfig,
|
||||
getAccessToken,
|
||||
malTokenWatcher,
|
||||
} from "../../Services/Trackers/myAnimeList";
|
||||
import { Appbar } from "../../Components/Appbar";
|
||||
} from "../../services/Trackers/myAnimeList";
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import {
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
ListItem,
|
||||
ListSection,
|
||||
ListSubHeader,
|
||||
} from "../../Components/List";
|
||||
} from "../../components/List";
|
||||
import { removeTracker, setTracker } from "../../redux/tracker/tracker.actions";
|
||||
|
||||
const TrackerScreen = ({ navigation }) => {
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user