Compare commits

...

26 Commits

Author SHA1 Message Date
Rajarshee Chatterjee 3fb35e2d32 v1.1.0 2021-06-20 23:06:06 +05:30
Rajarshee Chatterjee 35d5cf987a Fixes 2021-06-20 23:03:02 +05:30
Rajarshee Chatterjee 7e28d12a8a v1.1.0 2021-06-20 21:46:16 +05:30
Rajarshee Chatterjee ff575de2e8 Fixes 2021-06-20 21:44:58 +05:30
Rajarshee Chatterjee 962fcc534d Minor fixes 2021-06-20 18:19:50 +05:30
Rajarshee Chatterjee acd8843b0e Fix sources 2021-06-20 17:49:53 +05:30
Rajarshee Chatterjee c956d764c5 Add option to search all sources 2021-06-20 17:39:31 +05:30
Rajarshee Chatterjee ca0f62f118 Add pagination 2021-06-20 11:20:40 +05:30
Rajarshee Chatterjee 4d766555b7 Add incognito mode 2021-06-20 08:31:51 +05:30
Rajarshee Chatterjee edcc4c0bcd Use background service for downloads and updates (fixes #36) 2021-06-19 13:47:07 +05:30
Rajarshee Chatterjee 8e1a9274e5 Rename 2021-06-19 09:18:50 +05:30
Rajarshee Chatterjee d0b11c9b47 Rename folders 2021-06-19 09:17:52 +05:30
Rajarshee Chatterjee 3a9784a7a0 Fix launch crash on android 7 2021-06-15 09:46:50 +05:30
Rajarshee Chatterjee 66e3945427 Add option to disbale swipe gestures 2021-06-15 09:46:24 +05:30
Rajarshee Chatterjee 7773d50607 Filter sources by languages 2021-06-13 10:47:41 +05:30
Rajarshee Chatterjee 721d3842fb Fix swipe directions 2021-06-12 20:29:07 +05:30
Rajarshee Chatterjee 773c33f3bf Add swipe gestures 2021-06-12 15:52:42 +05:30
Rajarshee Chatterjee 1addb54690 Vertical Scrollbar 2021-06-12 14:32:34 +05:30
Rajarshee Chatterjee ce29a5456d Html to text 2021-06-11 17:54:07 +05:30
Rajarshee Chatterjee 4eda2d4624 Add action bar and use pressable 2021-06-11 17:53:33 +05:30
Rajarshee Chatterjee bae2fa40f3 Chapter footer 2021-06-11 17:52:34 +05:30
Rajarshee Chatterjee cf5d9e18bb Update all english sources 2021-06-10 21:53:59 +05:30
Rajarshee Chatterjee e87ecb48b4 Add more sources and fix sources 1-9 2021-06-10 18:29:13 +05:30
Rajarshee Chatterjee d4140fcc52 (~_~;) 2021-06-10 15:46:39 +05:30
Rajarshee Chatterjee 4858f22a88 Parse chapter 2021-06-10 11:51:22 +05:30
Rajarshee Chatterjee 6aa977a562 Add ReadLightNovel 2021-06-10 10:33:12 +05:30
162 changed files with 10207 additions and 2119 deletions
+3 -3
View File
@@ -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);
+1 -1
View File
@@ -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 {
+2
View File
@@ -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

+1 -1
View File
@@ -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": {
+18
View File
@@ -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
View File
@@ -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;
-66
View File
@@ -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 = () => {
+35
View File
@@ -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>
);
+2
View File
@@ -0,0 +1,2 @@
export const SET_DOWNLOAD_QUEUE = "SET_DOWNLOAD_QUEUE";
export const CANCEL_DOWNLOAD = "CANCEL_DOWNLOAD";
+8
View File
@@ -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 });
};
+35
View File
@@ -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;
+2 -2
View File
@@ -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 });
+1 -1
View File
@@ -26,7 +26,7 @@ const historyReducer = (state = initialState, action) => {
// ),
// };
case CLEAR_HISTORY:
return { ...state, history: [] };
return { ...state, loading: [] };
default:
return state;
}
+2 -2
View File
@@ -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);
+108 -35
View File
@@ -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);
}
+4 -1
View File
@@ -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) => {
+24 -9
View File
@@ -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,
});
};
+30 -1
View File
@@ -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;
}
+2
View File
@@ -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";
+2
View File
@@ -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,
})
);
+12 -13
View File
@@ -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 },
});
};
+3 -3
View File
@@ -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 }) => {
+143
View File
@@ -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;
@@ -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" }}
@@ -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";
@@ -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,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
@@ -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)"
: ""
}`}
@@ -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)}
/>
);
@@ -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
@@ -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,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)}
/>
);
@@ -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)
);
};
@@ -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,
},
});
@@ -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} />
) : (
@@ -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();
+76
View File
@@ -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();
+130
View File
@@ -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