Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| daf9444e87 | |||
| e4b1b757e6 | |||
| b01e63791e | |||
| ffe4950966 | |||
| caac2c3227 | |||
| 489d5ba714 | |||
| 4251ec7f20 | |||
| c1e0b01df8 | |||
| 66dc1c4bdb | |||
| 1ba88a16ff |
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
@@ -1,5 +1,4 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { NavigationContainer } from "@react-navigation/native";
|
||||
import { useFonts } from "expo-font";
|
||||
import { Provider as PaperProvider } from "react-native-paper";
|
||||
import Main from "./src/Navigators/Main";
|
||||
@@ -7,18 +6,11 @@ import Main from "./src/Navigators/Main";
|
||||
import { Provider } from "react-redux";
|
||||
import { persistor, store } from "./src/redux/store";
|
||||
import { PersistGate } from "redux-persist/lib/integration/react";
|
||||
import { createDB, deleteDb } from "./src/database/DBHelper";
|
||||
import { createDB, deleteDb } from "./src/Database/DBHelper";
|
||||
import { fonts } from "./src/Theme/fonts";
|
||||
|
||||
const App = () => {
|
||||
const [loaded] = useFonts({
|
||||
"pt-sans-bold": require("./assets/fonts/PTSansNarrow-Bold.ttf"),
|
||||
"arbutus-slab": require("./assets/fonts/ArbutusSlab-Regular.ttf"),
|
||||
domine: require("./assets/fonts/Domine-VariableFont_wght.ttf"),
|
||||
lato: require("./assets/fonts/Lato-Regular.ttf"),
|
||||
"noto-sans": require("./assets/fonts/NotoSans-Regular.ttf"),
|
||||
"open-sans": require("./assets/fonts/OpenSans-Regular.ttf"),
|
||||
"pt-serif": require("./assets/fonts/PTSerif-Regular.ttf"),
|
||||
});
|
||||
const [loaded] = useFonts(fonts);
|
||||
|
||||
useEffect(() => createDB(), []);
|
||||
|
||||
@@ -30,9 +22,7 @@ const App = () => {
|
||||
<Provider store={store}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<PaperProvider>
|
||||
<NavigationContainer>
|
||||
<Main />
|
||||
</NavigationContainer>
|
||||
<Main />
|
||||
</PaperProvider>
|
||||
</PersistGate>
|
||||
</Provider>
|
||||
|
||||
Generated
+117
@@ -1357,6 +1357,11 @@
|
||||
"@hapi/hoek": "^8.3.0"
|
||||
}
|
||||
},
|
||||
"@ide/backoff": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@ide/backoff/-/backoff-1.0.0.tgz",
|
||||
"integrity": "sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g=="
|
||||
},
|
||||
"@jest/console": {
|
||||
"version": "24.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz",
|
||||
@@ -2589,6 +2594,17 @@
|
||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
|
||||
},
|
||||
"assert": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz",
|
||||
"integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==",
|
||||
"requires": {
|
||||
"es6-object-assign": "^1.1.0",
|
||||
"is-nan": "^1.2.1",
|
||||
"object-is": "^1.0.1",
|
||||
"util": "^0.12.0"
|
||||
}
|
||||
},
|
||||
"assign-symbols": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
|
||||
@@ -2735,6 +2751,11 @@
|
||||
"babel-plugin-syntax-trailing-function-commas": "^7.0.0-beta.0"
|
||||
}
|
||||
},
|
||||
"badgin": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/badgin/-/badgin-1.2.2.tgz",
|
||||
"integrity": "sha512-XtoSjNhy2D09qGiLhFWBJmBwBlmleQuwyYyjddWNCJ3gqGRBOBR25VGcd8CAOSghpEUmghB3LD4NpHrUG89zCg=="
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -3656,6 +3677,11 @@
|
||||
"is-symbol": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"es6-object-assign": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz",
|
||||
"integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw="
|
||||
},
|
||||
"escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
@@ -3818,6 +3844,15 @@
|
||||
"url-parse": "^1.4.4"
|
||||
}
|
||||
},
|
||||
"expo-background-fetch": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/expo-background-fetch/-/expo-background-fetch-9.1.0.tgz",
|
||||
"integrity": "sha512-vBA+qTugxTYhAlNcXQxUUXTR6Qm2H/6QA3OzfgZUFL2bvnATuJ6BLoA29mrxHRUtSIcPDjHD9PVBnGBOEyCnwA==",
|
||||
"requires": {
|
||||
"@expo/config-plugins": "^1.0.18",
|
||||
"expo-task-manager": "~9.1.0"
|
||||
}
|
||||
},
|
||||
"expo-constants": {
|
||||
"version": "10.1.3",
|
||||
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-10.1.3.tgz",
|
||||
@@ -3888,6 +3923,42 @@
|
||||
"unimodules-permissions-interface": "~6.1.0"
|
||||
}
|
||||
},
|
||||
"expo-notifications": {
|
||||
"version": "0.11.6",
|
||||
"resolved": "https://registry.npmjs.org/expo-notifications/-/expo-notifications-0.11.6.tgz",
|
||||
"integrity": "sha512-skVEOHXKXrXBWofCQ/Zn7yKhAFA2EN0fd9mXqpkbF6nE4NKE7pn63gTWpyCy6sXC9M3YUal8yT0vp9GkD2FsmA==",
|
||||
"requires": {
|
||||
"@expo/config-plugins": "^1.0.18",
|
||||
"@expo/image-utils": "^0.3.10",
|
||||
"@ide/backoff": "^1.0.0",
|
||||
"abort-controller": "^3.0.0",
|
||||
"assert": "^2.0.0",
|
||||
"badgin": "^1.1.5",
|
||||
"expo-application": "~3.1.2",
|
||||
"expo-constants": "10.1.3",
|
||||
"fs-extra": "^9.0.1",
|
||||
"unimodules-permissions-interface": "~6.1.0",
|
||||
"uuid": "^3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-extra": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
|
||||
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"expo-permissions": {
|
||||
"version": "12.0.1",
|
||||
"resolved": "https://registry.npmjs.org/expo-permissions/-/expo-permissions-12.0.1.tgz",
|
||||
@@ -3920,6 +3991,15 @@
|
||||
"resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-1.0.4.tgz",
|
||||
"integrity": "sha512-s7nc496D/Zn1NGiMJ5wu6HyIdXxbgGtmZZtbHm7rpbcmLdf28GmMSNHDx7M0t00BMhky7VAurTCUo+BJs8ugsw=="
|
||||
},
|
||||
"expo-task-manager": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/expo-task-manager/-/expo-task-manager-9.1.0.tgz",
|
||||
"integrity": "sha512-mCWpjsBrlP/8B97UUpEoimSlFY67qNlblL5RQoL07UTYG14CCVmoIwMwOgxkE3lw+078gPOiwDuDQvYibbLyRw==",
|
||||
"requires": {
|
||||
"@expo/config-plugins": "^1.0.18",
|
||||
"unimodules-app-loader": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"expo-web-browser": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-9.1.0.tgz",
|
||||
@@ -4739,11 +4819,25 @@
|
||||
"resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz",
|
||||
"integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ=="
|
||||
},
|
||||
"is-generator-function": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.9.tgz",
|
||||
"integrity": "sha512-ZJ34p1uvIfptHCN7sFTjGibB9/oBg17sHqzDLfuwhvmN/qLVvIQXRQ8licZQ35WJ8KuEQt/etnnzQFI9C9Ue/A=="
|
||||
},
|
||||
"is-map": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz",
|
||||
"integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg=="
|
||||
},
|
||||
"is-nan": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz",
|
||||
"integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.0",
|
||||
"define-properties": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"is-negative-zero": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz",
|
||||
@@ -7335,6 +7429,11 @@
|
||||
"fast-memoize": "^2.5.1"
|
||||
}
|
||||
},
|
||||
"react-native-easing-gradient": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-easing-gradient/-/react-native-easing-gradient-0.1.2.tgz",
|
||||
"integrity": "sha512-oeSJ8nSrCxiCO5mLEVc59Evva4unjvH6AQ8vJFyJ7PIKRybIV5JC+OBU8VnF+V7oQ9dnDBpx2qT5Mh+oxd+/tw=="
|
||||
},
|
||||
"react-native-gesture-handler": {
|
||||
"version": "1.10.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.10.3.tgz",
|
||||
@@ -8569,6 +8668,11 @@
|
||||
"resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz",
|
||||
"integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg=="
|
||||
},
|
||||
"unimodules-app-loader": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/unimodules-app-loader/-/unimodules-app-loader-2.1.0.tgz",
|
||||
"integrity": "sha512-W+D+hVXq6jOvBm7QVwODPENz6Lupj73QequNNG+6GCTkqn4ybq/lba9IQvJQT2QzdL3luVHin+eym18cDblMlg=="
|
||||
},
|
||||
"unimodules-permissions-interface": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/unimodules-permissions-interface/-/unimodules-permissions-interface-6.1.0.tgz",
|
||||
@@ -8679,6 +8783,19 @@
|
||||
"pako": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"util": {
|
||||
"version": "0.12.3",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.12.3.tgz",
|
||||
"integrity": "sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"is-arguments": "^1.0.4",
|
||||
"is-generator-function": "^1.0.7",
|
||||
"is-typed-array": "^1.1.3",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"which-typed-array": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
||||
+5
-1
@@ -34,6 +34,7 @@
|
||||
"react-dom": "16.13.1",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-41.0.0.tar.gz",
|
||||
"react-native-collapsible-header-views": "^1.1.2",
|
||||
"react-native-easing-gradient": "^0.1.2",
|
||||
"react-native-gesture-handler": "~1.10.2",
|
||||
"react-native-offline": "^5.7.0",
|
||||
"react-native-paper": "^4.5.0",
|
||||
@@ -49,7 +50,10 @@
|
||||
"redux-devtools-extension": "^2.13.9",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"rn-sliding-up-panel": "^2.4.4"
|
||||
"rn-sliding-up-panel": "^2.4.4",
|
||||
"expo-notifications": "~0.11.6",
|
||||
"expo-background-fetch": "~9.1.0",
|
||||
"expo-task-manager": "~9.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "~7.9.0"
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useNavigation } from "@react-navigation/native";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
export const Appbar = ({ title, onBackAction, style }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
const theme = useSelector((state) => state.settingsReducer.theme);
|
||||
|
||||
return (
|
||||
<MaterialAppbar.Header
|
||||
@@ -42,7 +42,7 @@ export const SearchAppbar = ({
|
||||
|
||||
const navigation = useNavigation();
|
||||
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
const theme = useSelector((state) => state.settingsReducer.theme);
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
@@ -3,12 +3,12 @@ import { View } from "react-native";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const BottomSheetHandle = () => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
const theme = useSelector((state) => state.settingsReducer.theme);
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: theme.textColorHintDark,
|
||||
backgroundColor: theme.textColorHint,
|
||||
height: 5,
|
||||
width: 30,
|
||||
borderRadius: 50,
|
||||
@@ -3,7 +3,7 @@ import { StyleSheet, Text, View } from "react-native";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const EmptyView = ({ icon, description }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
const theme = useSelector((state) => state.settingsReducer.theme);
|
||||
|
||||
return (
|
||||
<View style={styles.emptyViewContainer}>
|
||||
@@ -8,7 +8,7 @@ const ListSection = ({ children }) => (
|
||||
|
||||
const ListSubHeader = ({ children, theme }) => (
|
||||
<List.Subheader
|
||||
style={[styles.listSubHeader, { color: theme.colorAccentDark }]}
|
||||
style={[styles.listSubHeader, { color: theme.colorAccent }]}
|
||||
>
|
||||
{children}
|
||||
</List.Subheader>
|
||||
@@ -24,7 +24,7 @@ const ListItem = ({ title, description, icon, onPress, theme, right }) => (
|
||||
left={() =>
|
||||
icon && (
|
||||
<List.Icon
|
||||
color={theme.colorAccentDark}
|
||||
color={theme.colorAccent}
|
||||
icon={icon}
|
||||
style={{ marginVertical: 0 }}
|
||||
/>
|
||||
@@ -45,15 +45,10 @@ const ListItem = ({ title, description, icon, onPress, theme, right }) => (
|
||||
);
|
||||
|
||||
const Divider = ({ theme }) => (
|
||||
<PaperDivider
|
||||
style={{
|
||||
backgroundColor: theme.dividerColor,
|
||||
height: 1,
|
||||
}}
|
||||
/>
|
||||
<PaperDivider style={{ backgroundColor: theme.dividerColor, height: 1 }} />
|
||||
);
|
||||
|
||||
const InfoItem = ({ title, description, icon, onPress, theme, right }) => (
|
||||
const InfoItem = ({ title, icon, theme }) => (
|
||||
<List.Item
|
||||
title={title}
|
||||
titleStyle={{ color: theme.textColorSecondary, fontSize: 14 }}
|
||||
@@ -0,0 +1,64 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View, Text, Image } from "react-native";
|
||||
import { TouchableRipple } from "react-native-paper";
|
||||
|
||||
const ListView = ({ item, downloadBadge, unreadBadge, theme, onPress }) => {
|
||||
return (
|
||||
<TouchableRipple
|
||||
borderless
|
||||
centered
|
||||
rippleColor={theme.rippleColor}
|
||||
style={styles.listView}
|
||||
onPress={onPress}
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
source={{ uri: item.novelCover }}
|
||||
style={styles.extensionIcon}
|
||||
/>
|
||||
<Text
|
||||
style={[
|
||||
{ color: theme.textColorPrimary },
|
||||
styles.novelName,
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
<View style={styles.badgeContainer}>
|
||||
{downloadBadge}
|
||||
{unreadBadge}
|
||||
</View>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListView;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
listView: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
extensionIcon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 4,
|
||||
},
|
||||
novelName: {
|
||||
flex: 1,
|
||||
marginLeft: 16,
|
||||
fontSize: 15,
|
||||
paddingRight: 8,
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
badgeContainer: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "flex-end",
|
||||
},
|
||||
});
|
||||
@@ -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/useTheme";
|
||||
import { useTheme } from "../Hooks/reduxHooks";
|
||||
|
||||
const NewUpdateDialog = ({ newVersion }) => {
|
||||
const [newUpdateDialog, showNewUpdateDialog] = useState(true);
|
||||
@@ -39,22 +39,22 @@ const NewUpdateDialog = ({ newVersion }) => {
|
||||
>
|
||||
<Button
|
||||
labelStyle={{
|
||||
color: theme.colorAccentDark,
|
||||
color: theme.colorAccent,
|
||||
letterSpacing: 0,
|
||||
textTransform: "none",
|
||||
}}
|
||||
theme={{ colors: { primary: theme.colorAccentDark } }}
|
||||
theme={{ colors: { primary: theme.colorAccent } }}
|
||||
onPress={() => showNewUpdateDialog(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
labelStyle={{
|
||||
color: theme.colorAccentDark,
|
||||
color: theme.colorAccent,
|
||||
letterSpacing: 0,
|
||||
textTransform: "none",
|
||||
}}
|
||||
theme={{ colors: { primary: theme.colorAccentDark } }}
|
||||
theme={{ colors: { primary: theme.colorAccent } }}
|
||||
onPress={() => Linking.openURL(newVersion.downloadUrl)}
|
||||
>
|
||||
Install
|
||||
@@ -0,0 +1,179 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View, Text, ImageBackground, Image } from "react-native";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import { TouchableRipple } from "react-native-paper";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
import { useSettings, useTheme } from "../Hooks/reduxHooks";
|
||||
import ListView from "./ListView";
|
||||
|
||||
const NovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
const theme = useTheme();
|
||||
const { displayMode, itemsPerRow } = useSettings();
|
||||
|
||||
const height = {
|
||||
1: 550,
|
||||
2: 280,
|
||||
3: 180,
|
||||
4: 140,
|
||||
5: 100,
|
||||
};
|
||||
|
||||
const comfortableTitle = () =>
|
||||
displayMode === 1 && (
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.title,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
padding: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
);
|
||||
|
||||
const compactTitle = () =>
|
||||
displayMode === 0 && (
|
||||
<View style={styles.titleContainer}>
|
||||
<LinearGradient
|
||||
colors={["transparent", "rgba(0,0,0,0.6)"]}
|
||||
style={styles.linearGradient}
|
||||
>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[styles.title, { color: "rgba(255,255,255,1)" }]}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
</View>
|
||||
);
|
||||
|
||||
const unreadBadge = () =>
|
||||
item.chaptersUnread && (
|
||||
<Text
|
||||
style={[
|
||||
styles.unreadBadge,
|
||||
!item.chaptersDownloaded && {
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.chaptersUnread}
|
||||
</Text>
|
||||
);
|
||||
|
||||
const downloadBadge = () =>
|
||||
item.chaptersDownloaded && (
|
||||
<Text
|
||||
style={[
|
||||
styles.downloadBadge,
|
||||
!item.chaptersUnread && {
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.chaptersDownloaded}
|
||||
</Text>
|
||||
);
|
||||
|
||||
return displayMode !== 2 ? (
|
||||
<View style={{ flex: 1 / itemsPerRow }}>
|
||||
<TouchableRipple
|
||||
borderless
|
||||
centered
|
||||
rippleColor={theme.rippleColor}
|
||||
style={styles.opac}
|
||||
onPress={onPress}
|
||||
>
|
||||
<>
|
||||
<ImageBackground
|
||||
source={{ uri: item.novelCover }}
|
||||
style={{ height: height[itemsPerRow] }}
|
||||
imageStyle={[
|
||||
{ borderRadius: 4 },
|
||||
libraryStatus && { opacity: 0.5 },
|
||||
]}
|
||||
progressiveRenderingEnabled={true}
|
||||
>
|
||||
<View style={{ flexDirection: "row", margin: 4 }}>
|
||||
{downloadBadge()}
|
||||
{unreadBadge()}
|
||||
</View>
|
||||
{compactTitle()}
|
||||
</ImageBackground>
|
||||
{comfortableTitle()}
|
||||
</>
|
||||
</TouchableRipple>
|
||||
</View>
|
||||
) : (
|
||||
<ListView
|
||||
item={item}
|
||||
downloadBadge={downloadBadge()}
|
||||
unreadBadge={unreadBadge()}
|
||||
theme={theme}
|
||||
onPress={onPress}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default NovelCover;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
titleContainer: {
|
||||
flex: 1,
|
||||
justifyContent: "flex-end",
|
||||
borderRadius: 4,
|
||||
},
|
||||
title: {
|
||||
fontFamily: "pt-sans-bold",
|
||||
fontSize: 14,
|
||||
padding: 8,
|
||||
},
|
||||
linearGradient: {
|
||||
bottom: -1,
|
||||
borderRadius: 4,
|
||||
},
|
||||
opac: {
|
||||
paddingHorizontal: 4.5,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 4,
|
||||
},
|
||||
extensionIcon: {
|
||||
width: 42,
|
||||
height: 42,
|
||||
borderRadius: 4,
|
||||
},
|
||||
listView: {
|
||||
flex: 1,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
borderRadius: 4,
|
||||
},
|
||||
|
||||
downloadBadge: {
|
||||
backgroundColor: "#47a84a",
|
||||
color: "#FFFFFF",
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
paddingTop: 2,
|
||||
paddingHorizontal: 4,
|
||||
fontSize: 12,
|
||||
},
|
||||
unreadBadge: {
|
||||
backgroundColor: "#2979FF",
|
||||
color: "#FFFFFF",
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
paddingTop: 2,
|
||||
paddingHorizontal: 4,
|
||||
fontSize: 12,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from "react";
|
||||
import { RefreshControl, StyleSheet, FlatList } from "react-native";
|
||||
import { useSettings } from "../Hooks/reduxHooks";
|
||||
|
||||
const NovelList = ({
|
||||
data,
|
||||
renderItem,
|
||||
refreshControl,
|
||||
ListEmptyComponent,
|
||||
}) => {
|
||||
const { displayMode, itemsPerRow } = useSettings();
|
||||
const getItemsPerRow = () => {
|
||||
if (displayMode === 2) {
|
||||
return 1;
|
||||
} else {
|
||||
return itemsPerRow;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
contentContainerStyle={styles.flatListCont}
|
||||
numColumns={getItemsPerRow()}
|
||||
key={getItemsPerRow()}
|
||||
data={data}
|
||||
keyExtractor={(item) => item.novelUrl}
|
||||
renderItem={renderItem}
|
||||
refreshControl={refreshControl}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default NovelList;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
flatListCont: {
|
||||
flexGrow: 1,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
});
|
||||
@@ -12,7 +12,7 @@ export const RadioButton = ({ value, label, theme }) => (
|
||||
<View style={styles.radioButtonContainer}>
|
||||
<MaterialRadioButton
|
||||
value={value}
|
||||
color={theme.colorAccentDark}
|
||||
color={theme.colorAccent}
|
||||
uncheckedColor={theme.textColorSecondary}
|
||||
/>
|
||||
<Text
|
||||
@@ -2,6 +2,9 @@ import React, { useRef } from "react";
|
||||
import { View, TextInput, StyleSheet } from "react-native";
|
||||
import { TouchableRipple, IconButton } from "react-native-paper";
|
||||
import Constants from "expo-constants";
|
||||
import { useSettings } from "../Hooks/reduxHooks";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { setDisplayMode } from "../redux/settings/settings.actions";
|
||||
|
||||
export const Searchbar = ({
|
||||
theme,
|
||||
@@ -10,12 +13,28 @@ export const Searchbar = ({
|
||||
left,
|
||||
onPressLeft,
|
||||
right,
|
||||
displayMenu,
|
||||
onPressRight,
|
||||
clearSearchbar,
|
||||
onChangeText,
|
||||
onSubmitEditing,
|
||||
}) => {
|
||||
const searchRef = useRef(null);
|
||||
const dispatch = useDispatch();
|
||||
const { displayMode } = useSettings();
|
||||
|
||||
const displayMenuIcon = () => {
|
||||
const icons = {
|
||||
0: "view-module",
|
||||
1: "view-list",
|
||||
2: "view-module",
|
||||
};
|
||||
|
||||
return icons[displayMode];
|
||||
};
|
||||
|
||||
const getDisplayMode = () =>
|
||||
displayMode === 0 ? 1 : displayMode === 1 ? 2 : displayMode === 2 && 0;
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
@@ -25,6 +44,7 @@ export const Searchbar = ({
|
||||
styles.searchAppbarContainer,
|
||||
{ backgroundColor: theme.searchBarColor },
|
||||
]}
|
||||
rippleColor={theme.rippleColor}
|
||||
>
|
||||
<View style={styles.container}>
|
||||
<View style={{ flex: 1, flexDirection: "row" }}>
|
||||
@@ -59,9 +79,20 @@ export const Searchbar = ({
|
||||
onPress={clearSearchbar}
|
||||
/>
|
||||
)}
|
||||
{displayMenu && (
|
||||
<IconButton
|
||||
icon={displayMenuIcon()}
|
||||
color={theme.textColorSecondary}
|
||||
style={{ marginRight: 0 }}
|
||||
size={23}
|
||||
onPress={() =>
|
||||
dispatch(setDisplayMode(getDisplayMode()))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{right && (
|
||||
<IconButton
|
||||
icon="earth"
|
||||
icon={right}
|
||||
color={theme.textColorSecondary}
|
||||
style={{ marginRight: 0 }}
|
||||
size={23}
|
||||
@@ -75,7 +106,7 @@ export const Searchbar = ({
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
searchAppbarContainer: {
|
||||
marginTop: Constants.statusBarHeight + 8,
|
||||
marginTop: Constants.statusBarHeight + 4,
|
||||
height: 48,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import { View, Text as RNText, Image, StyleSheet } from "react-native";
|
||||
|
||||
const Text = () => {};
|
||||
|
||||
export default UpdateCard;
|
||||
|
||||
const styles = StyleSheet.create({});
|
||||
@@ -35,8 +35,8 @@ export const insertChapters = async (novelId, chapters) => {
|
||||
};
|
||||
|
||||
const getChaptersQuery = (sort, filter) =>
|
||||
`SELECT * FROM chapters WHERE novelId = ? ${filter ?? ""} ${
|
||||
sort ?? "ORDER BY chapterId ASC"
|
||||
`SELECT * FROM chapters WHERE novelId = ? ${filter || ""} ${
|
||||
sort || "ORDER BY chapterId ASC"
|
||||
}`;
|
||||
|
||||
export const getChapters = (novelId, sort, filter) => {
|
||||
@@ -115,8 +115,6 @@ export const downloadChapter = async (
|
||||
) => {
|
||||
const downloadUrl = `https://lnreader-extensions.vercel.app/api/${extensionId}/novel/${novelUrl}${chapterUrl}`;
|
||||
|
||||
// console.log(downloadUrl);
|
||||
|
||||
const response = await fetch(downloadUrl);
|
||||
const chapter = await response.json();
|
||||
|
||||
@@ -161,26 +159,6 @@ export const deleteChapter = async (chapterId) => {
|
||||
});
|
||||
};
|
||||
|
||||
// export const deleteAllChapters = async (novelId) => {
|
||||
// const updateIsDownloadedQuery = `UPDATE chapters SET downloaded = 0 WHERE novelId=?`;
|
||||
// const deleteChapterQuery = `DELETE FROM downloads WHERE downloadChapterId=?`;
|
||||
|
||||
// db.transaction((tx) => {
|
||||
// tx.executeSql(
|
||||
// updateIsDownloadedQuery,
|
||||
// [chapterId],
|
||||
// (tx, res) => console.log(`Chaptermarked not downapdeod deleted`),
|
||||
// (txObj, error) => console.log("Error ", error)
|
||||
// );
|
||||
// tx.executeSql(
|
||||
// deleteChapterQuery,
|
||||
// [chapterId],
|
||||
// (tx, res) => console.log(`Chapter deleted`),
|
||||
// (txObj, error) => console.log("Error ", error)
|
||||
// );
|
||||
// });
|
||||
// };
|
||||
|
||||
const getLastReadChapterQuery = `
|
||||
SELECT chapters.*
|
||||
FROM history
|
||||
@@ -63,12 +63,12 @@ export const checkNovelInCache = (novelUrl) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const getNovel = async (novelUrl) => {
|
||||
export const getNovel = async (sourceId, novelUrl) => {
|
||||
return new Promise((resolve, reject) =>
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
`SELECT * FROM novels WHERE novelUrl = ?`,
|
||||
[novelUrl],
|
||||
`SELECT * FROM novels WHERE novelUrl = ? AND sourceId = ?`,
|
||||
[novelUrl, sourceId],
|
||||
(txObj, { rows }) => resolve(rows.item(0)),
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
@@ -22,7 +22,7 @@ export const githubUpdateChecker = () => {
|
||||
};
|
||||
|
||||
const isNewVersion = (versionTag) => {
|
||||
let currentVersion = "v1.0.16";
|
||||
let currentVersion = "v1.0.17";
|
||||
const regex = /[^\\d.]/;
|
||||
|
||||
let newVersion = versionTag.replace(regex, "");
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const useTheme = () => {
|
||||
const theme = useSelector((state) => state.settingsReducer.theme);
|
||||
|
||||
return theme;
|
||||
};
|
||||
|
||||
const useReaderSettings = () => {
|
||||
const readerSettings = useSelector((state) => state.settingsReducer.reader);
|
||||
|
||||
return readerSettings;
|
||||
};
|
||||
|
||||
const useSettings = () => {
|
||||
const { displayMode, itemsPerRow } = useSelector(
|
||||
(state) => state.settingsReducer
|
||||
);
|
||||
|
||||
return { displayMode, itemsPerRow };
|
||||
};
|
||||
|
||||
const useLibrary = () => {
|
||||
const library = useSelector((state) => state.libraryReducer.novels);
|
||||
|
||||
return library;
|
||||
};
|
||||
|
||||
const useNovel = () => {
|
||||
const novel = useSelector((state) => state.novelReducer);
|
||||
|
||||
return novel;
|
||||
};
|
||||
|
||||
const findNovel = (novelId) => {
|
||||
let novel = useSelector((state) => state.preferenceReducer.novelSettings);
|
||||
novel = novel.find((novel) => novel.novelId === novelId);
|
||||
|
||||
return novel;
|
||||
};
|
||||
|
||||
const usePreferences = (novelId) => {
|
||||
let sort, filter;
|
||||
|
||||
const novel = findNovel(novelId);
|
||||
|
||||
if (novel) {
|
||||
sort = novel.sort ?? "";
|
||||
filter = novel.filter ?? "";
|
||||
}
|
||||
|
||||
return { sort, filter };
|
||||
};
|
||||
|
||||
const useContinueReading = (chapters, novelId) => {
|
||||
let chapter, chapterId, novel;
|
||||
|
||||
novel = findNovel(novelId);
|
||||
|
||||
if (novel) {
|
||||
chapterId = novel.lastRead;
|
||||
}
|
||||
|
||||
if (chapterId) {
|
||||
chapter = chapters.find((obj) => obj.chapterId === chapterId);
|
||||
}
|
||||
|
||||
if (!chapter) {
|
||||
chapter = chapters.find((obj) => obj.read === 0);
|
||||
}
|
||||
|
||||
return chapter;
|
||||
};
|
||||
|
||||
export {
|
||||
useTheme,
|
||||
useReaderSettings,
|
||||
useLibrary,
|
||||
useSettings,
|
||||
usePreferences,
|
||||
useContinueReading,
|
||||
useNovel,
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { setStatusBarStyle as setExpoStatusBarStyle } from "expo-status-bar";
|
||||
import { useTheme } from "../Hooks/useTheme";
|
||||
import { useTheme } from "../Hooks/reduxHooks";
|
||||
|
||||
export const setStatusBarStyle = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
export const useLibrary = () => {
|
||||
const library = useSelector((state) => state.libraryReducer.novels);
|
||||
|
||||
return library;
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
export const useReaderSettings = () => {
|
||||
const readerSettings = useSelector((state) => state.settingsReducer.reader);
|
||||
|
||||
return readerSettings;
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
export const useTheme = () => {
|
||||
const currentTheme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
return currentTheme;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { createMaterialBottomTabNavigator } from "@react-navigation/material-bottom-tabs";
|
||||
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useTheme } from "../Hooks/useTheme";
|
||||
import { useTheme } from "../Hooks/reduxHooks";
|
||||
|
||||
import Library from "../Screens/Library/Library";
|
||||
import Updates from "../Screens/Updates/Updates";
|
||||
@@ -16,9 +16,9 @@ const BottomNavigator = () => {
|
||||
|
||||
return (
|
||||
<Tab.Navigator
|
||||
shifting={false}
|
||||
// shifting={false}
|
||||
barStyle={{ backgroundColor: theme.colorPrimary }}
|
||||
activeColor={theme.colorAccentDark}
|
||||
activeColor={theme.colorAccent}
|
||||
>
|
||||
<Tab.Screen
|
||||
name="Library"
|
||||
|
||||
+36
-28
@@ -8,13 +8,15 @@ 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";
|
||||
import MoreStack from "./More";
|
||||
|
||||
import { setStatusBarStyle } from "../Hooks/setStatusBarStyle";
|
||||
import { View } from "react-native";
|
||||
import { useTheme } from "../Hooks/useTheme";
|
||||
import { useTheme } from "../Hooks/reduxHooks";
|
||||
import { githubUpdateChecker } from "../Hooks/githubUpdateChecker";
|
||||
import NewUpdateDialog from "../Components/NewUpdateDialog";
|
||||
import { NavigationContainer } from "@react-navigation/native";
|
||||
|
||||
const Stack = createStackNavigator();
|
||||
|
||||
@@ -28,33 +30,39 @@ const MainNavigator = () => {
|
||||
const { isNewVersion, latestRelease } = githubUpdateChecker() || {};
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: theme.colorPrimary }}>
|
||||
{isNewVersion && <NewUpdateDialog newVersion={latestRelease} />}
|
||||
<Stack.Navigator screenOptions={stackNavigatorConfig}>
|
||||
<Stack.Screen
|
||||
name="BottomNavigator"
|
||||
component={BottomNavigator}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Novel"
|
||||
component={Novel}
|
||||
options={{
|
||||
headerTitle: "",
|
||||
headerShown: true,
|
||||
headerTransparent: true,
|
||||
headerTintColor: "white",
|
||||
...TransitionPresets.RevealFromBottomAndroid,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Chapter"
|
||||
component={Chapter}
|
||||
options={{ ...TransitionPresets.SlideFromRightIOS }}
|
||||
/>
|
||||
<Stack.Screen name="MoreStack" component={MoreStack} />
|
||||
<Stack.Screen name="Extension" component={Extension} />
|
||||
</Stack.Navigator>
|
||||
</View>
|
||||
<NavigationContainer>
|
||||
<View style={{ flex: 1, backgroundColor: theme.colorPrimary }}>
|
||||
{isNewVersion && <NewUpdateDialog newVersion={latestRelease} />}
|
||||
<Stack.Navigator screenOptions={stackNavigatorConfig}>
|
||||
<Stack.Screen
|
||||
name="BottomNavigator"
|
||||
component={BottomNavigator}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Novel"
|
||||
component={Novel}
|
||||
options={{
|
||||
headerTitle: "",
|
||||
headerShown: true,
|
||||
headerTransparent: true,
|
||||
headerTintColor: "white",
|
||||
...TransitionPresets.RevealFromBottomAndroid,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Chapter"
|
||||
component={Chapter}
|
||||
options={{ ...TransitionPresets.SlideFromRightIOS }}
|
||||
/>
|
||||
<Stack.Screen name="MoreStack" component={MoreStack} />
|
||||
<Stack.Screen name="Extension" component={Extension} />
|
||||
<Stack.Screen
|
||||
name="GlobalSearch"
|
||||
component={GlobalSearch}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
</View>
|
||||
</NavigationContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import About from "../Screens/More/About";
|
||||
import Settings from "../Screens/More/Settings";
|
||||
import Trackers from "../Screens/More/Trackers";
|
||||
import { View } from "react-native";
|
||||
import { useTheme } from "../Hooks/useTheme";
|
||||
import { useTheme } from "../Hooks/reduxHooks";
|
||||
|
||||
const Stack = createStackNavigator();
|
||||
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
ScrollView,
|
||||
Text,
|
||||
FlatList,
|
||||
ActivityIndicator,
|
||||
RefreshControl,
|
||||
} from "react-native";
|
||||
import { useTheme } from "../../Hooks/reduxHooks";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import {
|
||||
getSourcesAction,
|
||||
searchSourcesAction,
|
||||
} from "../../redux/source/source.actions";
|
||||
|
||||
import SourceCard from "./components/SourceCard";
|
||||
import { showToast } from "../../Hooks/showToast";
|
||||
import { Searchbar } from "../../Components/Searchbar";
|
||||
|
||||
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
|
||||
);
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isPinned = (sourceId) =>
|
||||
pinned.indexOf(sourceId) === -1 ? false : true;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getSourcesAction());
|
||||
}, [getSourcesAction]);
|
||||
|
||||
const onRefresh = () => {
|
||||
showToast("Updating extension list");
|
||||
setRefreshing(true);
|
||||
getSourcesAction();
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
const clearSearchbar = () => {
|
||||
setSearchText("");
|
||||
};
|
||||
|
||||
const onChangeText = (text) => {
|
||||
setSearchText(text);
|
||||
dispatch(searchSourcesAction(text));
|
||||
};
|
||||
|
||||
// const onSubmitEditing = () => {};
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<SourceCard
|
||||
item={item}
|
||||
isPinned={isPinned(item.sourceId)}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<Searchbar
|
||||
theme={theme}
|
||||
placeholder="Search Source"
|
||||
left={"magnify"}
|
||||
onPressLeft={clearSearchbar}
|
||||
searchText={searchText}
|
||||
onChangeText={onChangeText}
|
||||
clearSearchbar={clearSearchbar}
|
||||
right="book-search"
|
||||
onPressRight={() => navigation.navigate("GlobalSearch")}
|
||||
/>
|
||||
|
||||
<FlatList
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
data={!searchText ? sources : search}
|
||||
keyExtractor={(item) => item.sourceId.toString()}
|
||||
renderItem={renderItem}
|
||||
extraData={pinned}
|
||||
ListHeaderComponent={
|
||||
<View>
|
||||
{pinned.length > 0 && (
|
||||
<FlatList
|
||||
contentContainerStyle={{ paddingBottom: 16 }}
|
||||
data={sources.filter((source) =>
|
||||
isPinned(source.sourceId)
|
||||
)}
|
||||
keyExtractor={(item) =>
|
||||
item.sourceId.toString()
|
||||
}
|
||||
renderItem={renderItem}
|
||||
extraData={pinned}
|
||||
ListHeaderComponent={
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
Pinned
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
Sources
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
ListEmptyComponent={
|
||||
loading && (
|
||||
<ActivityIndicator
|
||||
size="small"
|
||||
color={theme.colorAccent}
|
||||
style={{ marginTop: 16 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
colors={["white"]}
|
||||
progressBackgroundColor={theme.colorAccent}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Browse;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1 },
|
||||
});
|
||||
@@ -0,0 +1,155 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
Text,
|
||||
FlatList,
|
||||
ActivityIndicator,
|
||||
} from "react-native";
|
||||
import { useLibrary, useTheme } from "../../Hooks/reduxHooks";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import { Searchbar } from "../../Components/Searchbar";
|
||||
import NovelCover from "../../Components/NovelCover";
|
||||
import NovelList from "../../Components/NovelList";
|
||||
import EmptyView from "../../Components/EmptyView";
|
||||
import GlobalSearchNovelList from "./components/GlobalSearchNovelList";
|
||||
|
||||
const GlobalSearch = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [searchResults, setSearchResults] = useState("");
|
||||
const { sources, pinned } = useSelector((state) => state.sourceReducer);
|
||||
|
||||
const library = useLibrary();
|
||||
|
||||
const pinnedSources = sources.filter(
|
||||
(source) => pinned.indexOf(source.sourceId) !== -1
|
||||
);
|
||||
|
||||
const clearSearchbar = () => {
|
||||
setSearchText("");
|
||||
};
|
||||
|
||||
const onChangeText = (text) => setSearchText(text);
|
||||
|
||||
const onSubmitEditing = () => {
|
||||
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}`;
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
});
|
||||
}, 3000 * index)
|
||||
);
|
||||
};
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<>
|
||||
<View style={{ padding: 8, paddingVertical: 16 }}>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
{item.sourceName}
|
||||
</Text>
|
||||
<Text style={{ color: theme.textColorSecondary, fontSize: 12 }}>
|
||||
{item.sourceLanguage}
|
||||
</Text>
|
||||
</View>
|
||||
<GlobalSearchNovelList
|
||||
data={item.novels}
|
||||
theme={theme}
|
||||
library={library}
|
||||
navigation={navigation}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<Searchbar
|
||||
theme={theme}
|
||||
placeholder="Global Search"
|
||||
left="arrow-left"
|
||||
onPressLeft={() => navigation.goBack()}
|
||||
searchText={searchText}
|
||||
onChangeText={onChangeText}
|
||||
onSubmitEditing={onSubmitEditing}
|
||||
clearSearchbar={clearSearchbar}
|
||||
/>
|
||||
|
||||
<FlatList
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
padding: 4,
|
||||
}}
|
||||
data={searchResults}
|
||||
keyExtractor={(item) => item.sourceId.toString()}
|
||||
renderItem={renderItem}
|
||||
extraData={pinned}
|
||||
ListEmptyComponent={
|
||||
<>
|
||||
{!loading && (
|
||||
<EmptyView
|
||||
icon="__φ(..)"
|
||||
description={`Search a novel in your pinned sources ${
|
||||
pinned.length === 0
|
||||
? "(No sources pinned)"
|
||||
: ""
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
ListFooterComponent={
|
||||
loading && (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
padding: 16,
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator
|
||||
size="large"
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalSearch;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1 },
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View, Text, ImageBackground } from "react-native";
|
||||
import { TouchableRipple } from "react-native-paper";
|
||||
|
||||
import { useSettings, useTheme } from "../../../Hooks/reduxHooks";
|
||||
|
||||
const GlobalSearchNovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const comfortableTitle = () => (
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.title,
|
||||
{
|
||||
width: 115,
|
||||
color: theme.textColorPrimary,
|
||||
padding: 4,
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<TouchableRipple
|
||||
borderless
|
||||
centered
|
||||
rippleColor={theme.rippleColor}
|
||||
style={styles.opac}
|
||||
onPress={onPress}
|
||||
>
|
||||
<>
|
||||
<ImageBackground
|
||||
source={{ uri: item.novelCover }}
|
||||
style={{ height: 150, width: 115 }}
|
||||
imageStyle={[
|
||||
{ borderRadius: 4 },
|
||||
libraryStatus && { opacity: 0.5 },
|
||||
]}
|
||||
progressiveRenderingEnabled={true}
|
||||
/>
|
||||
{comfortableTitle()}
|
||||
</>
|
||||
</TouchableRipple>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalSearchNovelCover;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
titleContainer: {
|
||||
flex: 1,
|
||||
justifyContent: "flex-end",
|
||||
borderRadius: 4,
|
||||
},
|
||||
title: {
|
||||
fontFamily: "pt-sans-bold",
|
||||
fontSize: 14,
|
||||
padding: 8,
|
||||
},
|
||||
opac: {
|
||||
paddingHorizontal: 5,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 4,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, FlatList, Text } from "react-native";
|
||||
import GlobalSearchNovelCover from "./GlobalSearchNovelCover";
|
||||
|
||||
const GlobalSearchNovelList = ({ data, theme, library, navigation }) => {
|
||||
const checkIFInLibrary = (sourceId, novelUrl) => {
|
||||
return library.some(
|
||||
(obj) => obj.novelUrl === novelUrl && obj.sourceId === sourceId
|
||||
);
|
||||
};
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<GlobalSearchNovelCover
|
||||
item={item}
|
||||
onPress={() =>
|
||||
navigation.navigate("Novel", {
|
||||
...item,
|
||||
sourceId: item.extensionId,
|
||||
})
|
||||
}
|
||||
libraryStatus={checkIFInLibrary(item.extensionId, item.novelUrl)}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
contentContainerStyle={styles.flatListCont}
|
||||
horizontal={true}
|
||||
data={data}
|
||||
keyExtractor={(item) => item.novelUrl}
|
||||
renderItem={renderItem}
|
||||
ListEmptyComponent={
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
padding: 8,
|
||||
paddingVertical: 4,
|
||||
}}
|
||||
>
|
||||
No results found
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalSearchNovelList;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
flatListCont: {
|
||||
flexGrow: 1,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
});
|
||||
+22
-5
@@ -1,12 +1,15 @@
|
||||
import React from "react";
|
||||
import { View, Text, Image, StyleSheet } from "react-native";
|
||||
import { TouchableRipple, Button } from "react-native-paper";
|
||||
import { TouchableRipple, Button, IconButton } from "react-native-paper";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { pinSourceAction } from "../../../redux/source/source.actions";
|
||||
|
||||
const ExtensionCard = ({ item, theme }) => {
|
||||
const { sourceName, sourceCover, sourceLanguage, status } = item;
|
||||
const ExtensionCard = ({ item, theme, isPinned }) => {
|
||||
const { sourceId, sourceName, sourceCover, sourceLanguage, status } = item;
|
||||
|
||||
const navigation = useNavigation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const navigateToExtension = () =>
|
||||
navigation.navigate("Extension", {
|
||||
@@ -51,15 +54,29 @@ const ExtensionCard = ({ item, theme }) => {
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View>
|
||||
<View
|
||||
style={{ flexDirection: "row", alignItems: "center" }}
|
||||
>
|
||||
<Button
|
||||
labelStyle={{ letterSpacing: 0 }}
|
||||
uppercase={false}
|
||||
color={theme.colorAccentDark}
|
||||
color={theme.colorAccent}
|
||||
onPress={navigateToExtension}
|
||||
>
|
||||
Browse
|
||||
</Button>
|
||||
<IconButton
|
||||
icon={isPinned ? "pin" : "pin-outline"}
|
||||
animated
|
||||
size={21}
|
||||
onPress={() => dispatch(pinSourceAction(sourceId))}
|
||||
color={
|
||||
isPinned
|
||||
? theme.colorAccent
|
||||
: theme.textColorSecondary
|
||||
}
|
||||
style={{ margin: 2 }}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
@@ -21,7 +21,6 @@ const ChapterItem = ({
|
||||
navigation,
|
||||
theme,
|
||||
insertHistoryAction,
|
||||
markChapterReadAction,
|
||||
getChapterAction,
|
||||
chapter,
|
||||
loading,
|
||||
@@ -170,7 +169,7 @@ const ChapterItem = ({
|
||||
]}
|
||||
onScroll={({ nativeEvent }) => {
|
||||
if (isCloseToBottom(nativeEvent)) {
|
||||
markChapterReadAction(chapterId);
|
||||
dispatch(markChapterReadAction(chapterId, novelId));
|
||||
if (
|
||||
isTracked &&
|
||||
chapterNumber &&
|
||||
@@ -192,7 +191,7 @@ const ChapterItem = ({
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<ActivityIndicator
|
||||
size={50}
|
||||
color={theme.colorAccentDark}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
@@ -223,7 +222,7 @@ const ChapterItem = ({
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
theme: state.themeReducer.theme,
|
||||
theme: state.settingsReducer.theme,
|
||||
chapter: state.novelReducer.chapter,
|
||||
loading: state.novelReducer.chapterLoading,
|
||||
reader: state.settingsReducer.reader,
|
||||
@@ -231,7 +230,6 @@ const mapStateToProps = (state) => ({
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
insertHistoryAction,
|
||||
markChapterReadAction,
|
||||
getChapterAction,
|
||||
})(ChapterItem);
|
||||
|
||||
+5
-5
@@ -54,9 +54,9 @@ const ChapterBottomSheet = ({
|
||||
minimumValue={12}
|
||||
maximumValue={20}
|
||||
step={2}
|
||||
minimumTrackTintColor={theme.colorAccentDark}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor="#000000"
|
||||
thumbTintColor={theme.colorAccentDark}
|
||||
thumbTintColor={theme.colorAccent}
|
||||
onValueChange={(value) => updateReaderTextSize(value)}
|
||||
/>
|
||||
<View
|
||||
@@ -185,7 +185,7 @@ const ChapterBottomSheet = ({
|
||||
>
|
||||
<IconButton
|
||||
icon="minus"
|
||||
color={theme.colorAccentDark}
|
||||
color={theme.colorAccent}
|
||||
size={26}
|
||||
disabled={reader.padding <= 0 ? true : false}
|
||||
onPress={() =>
|
||||
@@ -202,7 +202,7 @@ const ChapterBottomSheet = ({
|
||||
</Text>
|
||||
<IconButton
|
||||
icon="plus"
|
||||
color={theme.colorAccentDark}
|
||||
color={theme.colorAccent}
|
||||
size={26}
|
||||
disabled={reader.padding >= 10 ? true : false}
|
||||
onPress={() =>
|
||||
@@ -253,7 +253,7 @@ const ChapterBottomSheet = ({
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
theme: state.themeReducer.theme,
|
||||
theme: state.settingsReducer.theme,
|
||||
reader: state.settingsReducer.reader,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { StyleSheet, View, ActivityIndicator } from "react-native";
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
|
||||
import NovelCover from "../../Components/NovelCover";
|
||||
import EmptyView from "../../Components/EmptyView";
|
||||
|
||||
import { Searchbar } from "../../Components/Searchbar";
|
||||
import { useTheme, useLibrary } from "../../Hooks/reduxHooks";
|
||||
import NovelList from "../../Components/NovelList";
|
||||
|
||||
const Extension = ({ navigation, route }) => {
|
||||
const { sourceId, sourceName, sourceUrl } = route.params;
|
||||
|
||||
const theme = useTheme();
|
||||
const library = useLibrary();
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [novels, setNovels] = useState();
|
||||
|
||||
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 = () => {
|
||||
fetch(getSourceUrl())
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
setNovels(json);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const clearSearchbar = () => {
|
||||
getNovels();
|
||||
setLoading(true);
|
||||
setSearchText("");
|
||||
};
|
||||
|
||||
const getSearchResults = () => {
|
||||
setLoading(true);
|
||||
fetch(getSearchUrl())
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
setNovels(json);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const checkIFInLibrary = (sourceId, novelUrl) => {
|
||||
return library.some(
|
||||
(obj) => obj.novelUrl === novelUrl && obj.sourceId === sourceId
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getNovels();
|
||||
}, []);
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<NovelCover
|
||||
item={item}
|
||||
onPress={() =>
|
||||
navigation.navigate("Novel", {
|
||||
novelName: item.novelName,
|
||||
novelCover: item.novelCover,
|
||||
novelUrl: item.novelUrl,
|
||||
sourceId: item.extensionId,
|
||||
})
|
||||
}
|
||||
libraryStatus={checkIFInLibrary(item.extensionId, item.novelUrl)}
|
||||
/>
|
||||
);
|
||||
|
||||
const listEmptyComponent = () =>
|
||||
novels.length === 0 && (
|
||||
<EmptyView icon="(; ̄Д ̄)" description="No results found" />
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<Searchbar
|
||||
theme={theme}
|
||||
placeholder={`Search ${sourceName}`}
|
||||
left="arrow-left"
|
||||
onPressLeft={() => navigation.goBack()}
|
||||
searchText={searchText}
|
||||
onChangeText={(text) => setSearchText(text)}
|
||||
onSubmitEditing={getSearchResults}
|
||||
clearSearchbar={clearSearchbar}
|
||||
right="earth"
|
||||
displayMenu={true}
|
||||
onPressRight={() => WebBrowser.openBrowserAsync(sourceUrl)}
|
||||
/>
|
||||
|
||||
{loading ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<ActivityIndicator size={60} color={theme.colorAccent} />
|
||||
</View>
|
||||
) : (
|
||||
<NovelList
|
||||
data={novels}
|
||||
renderItem={renderItem}
|
||||
ListEmptyComponent={listEmptyComponent()}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Extension;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
@@ -1,6 +1,4 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import { StyleSheet, View, FlatList, ActivityIndicator } from "react-native";
|
||||
|
||||
import { Appbar } from "../../Components/Appbar";
|
||||
@@ -22,15 +20,14 @@ const History = ({
|
||||
getHistoryAction,
|
||||
deleteHistoryAction,
|
||||
}) => {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
getHistoryAction();
|
||||
}, [getHistoryAction])
|
||||
);
|
||||
useEffect(() => {
|
||||
getHistoryAction();
|
||||
}, [getHistoryAction]);
|
||||
|
||||
const renderHistoryCard = ({ item }) => (
|
||||
<HistoryCard
|
||||
item={item}
|
||||
theme={theme}
|
||||
deleteHistoryAction={deleteHistoryAction}
|
||||
navigation={navigation}
|
||||
/>
|
||||
@@ -46,7 +43,7 @@ const History = ({
|
||||
]}
|
||||
>
|
||||
<FlatList
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
contentContainerStyle={{ flexGrow: 1, padding: 8 }}
|
||||
data={history}
|
||||
keyExtractor={(item) => item.novelUrl.toString()}
|
||||
renderItem={renderHistoryCard}
|
||||
@@ -54,7 +51,7 @@ const History = ({
|
||||
loading && (
|
||||
<ActivityIndicator
|
||||
size="small"
|
||||
color={theme.colorAccentDark}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -73,7 +70,7 @@ const History = ({
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
theme: state.themeReducer.theme,
|
||||
theme: state.settingsReducer.theme,
|
||||
history: state.historyReducer.history,
|
||||
loading: state.historyReducer.loading,
|
||||
});
|
||||
@@ -86,6 +83,5 @@ export default connect(mapStateToProps, {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 10,
|
||||
},
|
||||
});
|
||||
+8
-13
@@ -5,25 +5,20 @@ import { TouchableRipple, IconButton } from "react-native-paper";
|
||||
|
||||
import moment from "moment";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const HistoryCard = ({ item, deleteHistoryAction, navigation }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
import { useDispatch } from "react-redux";
|
||||
import { setNovel } from "../../../redux/novel/novel.actions";
|
||||
|
||||
const HistoryCard = ({ item, deleteHistoryAction, navigation, theme }) => {
|
||||
const dispatch = useDispatch();
|
||||
return (
|
||||
<TouchableRipple
|
||||
style={styles.historyCard}
|
||||
rippleColor={theme.rippleColor}
|
||||
borderless
|
||||
onPress={() =>
|
||||
navigation.navigate("Novel", {
|
||||
novelName: item.novelName,
|
||||
novelCover: item.novelCover,
|
||||
extensionId: item.extensionId,
|
||||
novelUrl: item.novelUrl,
|
||||
sourceId: item.sourceId,
|
||||
})
|
||||
}
|
||||
onPress={() => {
|
||||
navigation.navigate("Novel", item);
|
||||
dispatch(setNovel(item));
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
@@ -0,0 +1,126 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
RefreshControl,
|
||||
ActivityIndicator,
|
||||
} from "react-native";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
|
||||
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 { useTheme } from "../../Hooks/reduxHooks";
|
||||
import { setNovel } from "../../redux/novel/novel.actions";
|
||||
|
||||
const LibraryScreen = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { loading, novels } = useSelector((state) => state.libraryReducer);
|
||||
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getLibraryAction());
|
||||
}, [getLibraryAction]);
|
||||
|
||||
const onRefresh = () => {
|
||||
setRefreshing(true);
|
||||
dispatch(updateLibraryAction());
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
const clearSearchbar = () => {
|
||||
dispatch(getLibraryAction());
|
||||
setSearchText("");
|
||||
};
|
||||
|
||||
const onChangeText = (text) => {
|
||||
setSearchText(text);
|
||||
dispatch(searchLibraryAction(text));
|
||||
};
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<NovelCover
|
||||
item={item}
|
||||
onPress={() => {
|
||||
navigation.navigate("Novel", item);
|
||||
dispatch(setNovel(item));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const refreshControl = () => (
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
colors={["white"]}
|
||||
progressBackgroundColor={theme.colorAccent}
|
||||
/>
|
||||
);
|
||||
|
||||
const listEmptyComponent = () =>
|
||||
searchText !== "" ? (
|
||||
<Text style={[styles.emptySearch, { color: theme.colorAccent }]}>
|
||||
{`"${searchText}" not in library`}
|
||||
</Text>
|
||||
) : (
|
||||
<EmptyView
|
||||
icon="Σ(ಠ_ಠ)"
|
||||
description="Your library is empty. Add series to your library from Browse."
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<Searchbar
|
||||
placeholder="Search Library"
|
||||
searchText={searchText}
|
||||
clearSearchbar={clearSearchbar}
|
||||
onChangeText={onChangeText}
|
||||
left="magnify"
|
||||
theme={theme}
|
||||
/>
|
||||
{loading ? (
|
||||
<ActivityIndicator size="small" color={theme.colorAccent} />
|
||||
) : (
|
||||
<NovelList
|
||||
data={novels}
|
||||
renderItem={renderItem}
|
||||
refreshControl={refreshControl()}
|
||||
ListEmptyComponent={listEmptyComponent}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LibraryScreen;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
emptySearch: {
|
||||
marginTop: 8,
|
||||
textAlign: "center",
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
});
|
||||
@@ -8,7 +8,7 @@ import { useSelector } from "react-redux";
|
||||
import { Divider, ListItem, ListSection } from "../../Components/List";
|
||||
|
||||
const AboutScreen = ({ navigation }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
const theme = useSelector((state) => state.settingsReducer.theme);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -17,12 +17,12 @@ const AboutScreen = ({ navigation }) => {
|
||||
<ListSection>
|
||||
<ListItem
|
||||
title="Version"
|
||||
description="Stable 1.0.16"
|
||||
description="Stable 1.0.17"
|
||||
theme={theme}
|
||||
/>
|
||||
<ListItem
|
||||
title="Build Time"
|
||||
description="03-05-21 20:45 PM"
|
||||
description="06-05-21 21:00 PM"
|
||||
theme={theme}
|
||||
/>
|
||||
<ListItem
|
||||
@@ -6,7 +6,7 @@ import { Appbar } from "../../Components/Appbar";
|
||||
import { ListItem, ListSection } from "../../Components/List";
|
||||
|
||||
const MoreScreen = ({ navigation }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
const theme = useSelector((state) => state.settingsReducer.theme);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -1,14 +1,13 @@
|
||||
import React, { useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { connect } from "react-redux";
|
||||
import { connect, useDispatch } from "react-redux";
|
||||
|
||||
import { switchTheme } from "../../redux/theme/theme.actions";
|
||||
import { switchTheme } from "../../redux/settings/settings.actions";
|
||||
import {
|
||||
setDisplayMode,
|
||||
setItemsPerRow,
|
||||
} from "../../redux/settings/settings.actions";
|
||||
import { deleteAllHistory } from "../../database/queries/HistoryQueries";
|
||||
import { deleteNovelCache } from "../../database/queries/NovelQueries";
|
||||
import { deleteNovelCache } from "../../Database/queries/NovelQueries";
|
||||
|
||||
import { Appbar } from "../../Components/Appbar";
|
||||
import {
|
||||
@@ -22,7 +21,9 @@ import DisplayModeModal from "./components/DisplayModeModal";
|
||||
import GridSizeModal from "./components/GridSizeModal";
|
||||
import ThemeModal from "./components/ThemeModal";
|
||||
import { createBackup, restoreBackup } from "../../Services/backup";
|
||||
import { useTheme } from "../../Hooks/useTheme";
|
||||
import { useTheme } from "../../Hooks/reduxHooks";
|
||||
import { restoreLibraryAction } from "../../redux/library/library.actions";
|
||||
import { clearAllHistoryAction } from "../../redux/history/history.actions";
|
||||
|
||||
const SettingsScreen = ({
|
||||
navigation,
|
||||
@@ -44,6 +45,8 @@ const SettingsScreen = ({
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Display Mode Modal
|
||||
const [displayModalVisible, setDisplayModalVisible] = useState(false);
|
||||
const showDisplayModal = () => setDisplayModalVisible(true);
|
||||
@@ -80,7 +83,7 @@ const SettingsScreen = ({
|
||||
<ListItem
|
||||
title="Clear history"
|
||||
description="Delete reading history for all novels"
|
||||
onPress={() => deleteAllHistory()}
|
||||
onPress={() => dispatch(clearAllHistoryAction())}
|
||||
theme={theme}
|
||||
/>
|
||||
<Divider theme={theme} />
|
||||
@@ -116,14 +119,13 @@ const SettingsScreen = ({
|
||||
<ListItem
|
||||
title="Restore backup"
|
||||
description="Restore library from backup file"
|
||||
onPress={restoreBackup}
|
||||
onPress={() => dispatch(restoreLibraryAction())}
|
||||
theme={theme}
|
||||
/>
|
||||
<InfoItem
|
||||
title="Create backup may not work on devices with Android 9 or lower."
|
||||
icon="information-outline"
|
||||
description="Restore library from backup file"
|
||||
onPress={restoreBackup}
|
||||
theme={theme}
|
||||
/>
|
||||
</ListSection>
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
import { removeTracker, setTracker } from "../../redux/tracker/tracker.actions";
|
||||
|
||||
const TrackerScreen = ({ navigation }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
const theme = useSelector((state) => state.settingsReducer.theme);
|
||||
const tracker = useSelector((state) => state.trackerReducer.tracker);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -125,7 +125,7 @@ const TrackerScreen = ({ navigation }) => {
|
||||
<Button
|
||||
style={{ marginTop: 30 }}
|
||||
labelStyle={{
|
||||
color: theme.colorAccentDark,
|
||||
color: theme.colorAccent,
|
||||
letterSpacing: 0,
|
||||
textTransform: "none",
|
||||
}}
|
||||
@@ -136,7 +136,7 @@ const TrackerScreen = ({ navigation }) => {
|
||||
<Button
|
||||
style={{ marginTop: 30 }}
|
||||
labelStyle={{
|
||||
color: theme.colorAccentDark,
|
||||
color: theme.colorAccent,
|
||||
letterSpacing: 0,
|
||||
textTransform: "none",
|
||||
}}
|
||||
+1
-1
@@ -26,7 +26,7 @@ const DisplayModeModal = ({
|
||||
}
|
||||
mode="ios"
|
||||
uncheckedColor={theme.textColorSecondary}
|
||||
color={theme.colorAccentDark}
|
||||
color={theme.colorAccent}
|
||||
onPress={() => setDisplayMode(mode.displayMode)}
|
||||
/>
|
||||
));
|
||||
+2
-2
@@ -51,9 +51,9 @@ const GridSizeModal = ({
|
||||
minimumValue={1}
|
||||
maximumValue={5}
|
||||
step={1}
|
||||
minimumTrackTintColor={theme.colorAccentDark}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor="#000000"
|
||||
thumbTintColor={theme.colorAccentDark}
|
||||
thumbTintColor={theme.colorAccent}
|
||||
onValueChange={(value) => setItemsPerRow(value)}
|
||||
/>
|
||||
{/* <RadioButtonGroup
|
||||
+2
-1
@@ -15,6 +15,7 @@ const ThemeModal = ({
|
||||
{ id: 1, name: "Light", statusBar: "dark" },
|
||||
{ id: 2, name: "Dark", statusBar: "light" },
|
||||
{ id: 3, name: "Midnight Dusk", statusBar: "light" },
|
||||
{ id: 4, name: "Lime", statusBar: "light" },
|
||||
];
|
||||
|
||||
const renderThemeCheckboxes = () =>
|
||||
@@ -25,7 +26,7 @@ const ThemeModal = ({
|
||||
labelStyle={{ color: theme.textColorPrimary }}
|
||||
status={theme.id === item.id ? "checked" : "unchecked"}
|
||||
mode="ios"
|
||||
color={theme.colorAccentDark}
|
||||
color={theme.colorAccent}
|
||||
onPress={() => {
|
||||
switchTheme(item.id);
|
||||
setStatusBarStyle(item.statusBar);
|
||||
@@ -0,0 +1,146 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { StyleSheet, View, FlatList, RefreshControl } from "react-native";
|
||||
import { Provider, Portal } from "react-native-paper";
|
||||
import {
|
||||
useContinueReading,
|
||||
useNovel,
|
||||
usePreferences,
|
||||
useTheme,
|
||||
} from "../../Hooks/reduxHooks";
|
||||
|
||||
import ChapterItem from "./components/ChapterItem";
|
||||
import NovelInfoHeader from "./components/NovelHeader";
|
||||
import ChaptersSettingsSheet from "./components/ChaptersSettingsSheet";
|
||||
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
import {
|
||||
getNovelAction,
|
||||
sortAndFilterChapters,
|
||||
updateNovelAction,
|
||||
} from "../../redux/novel/novel.actions";
|
||||
import TrackSheet from "./components/Tracker/TrackSheet";
|
||||
import { showToast } from "../../Hooks/showToast";
|
||||
|
||||
const Novel = ({ route, navigation }) => {
|
||||
const item = route.params;
|
||||
const {
|
||||
sourceId,
|
||||
novelUrl,
|
||||
novelName,
|
||||
novelCover,
|
||||
followed,
|
||||
novelId,
|
||||
} = item;
|
||||
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const { novel, chapters, loading, fetching } = useNovel();
|
||||
|
||||
let chaptersSettingsSheetRef = useRef(null);
|
||||
let trackerSheetRef = useRef(null);
|
||||
|
||||
const { sort, filter } = usePreferences(
|
||||
novelId || (!loading && novel.novelId)
|
||||
);
|
||||
|
||||
let lastRead = useContinueReading(
|
||||
chapters,
|
||||
novelId || (!loading && novel.novelId)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(
|
||||
getNovelAction(followed, sourceId, novelUrl, novelId, sort, filter)
|
||||
);
|
||||
}, [getNovelAction]);
|
||||
|
||||
const onRefresh = () => {
|
||||
dispatch(updateNovelAction(sourceId, novelUrl, novelId));
|
||||
showToast(`Updated ${novelName}`);
|
||||
};
|
||||
|
||||
const refreshControl = () => (
|
||||
<RefreshControl
|
||||
onRefresh={onRefresh}
|
||||
refreshing={fetching}
|
||||
colors={[theme.textColorPrimary]}
|
||||
progressBackgroundColor={theme.colorPrimary}
|
||||
/>
|
||||
);
|
||||
|
||||
const ListHeaderComponent = () => (
|
||||
<NovelInfoHeader
|
||||
item={item}
|
||||
novel={novel}
|
||||
theme={theme}
|
||||
filter={filter}
|
||||
loading={loading}
|
||||
lastRead={lastRead}
|
||||
dispatch={dispatch}
|
||||
chapters={chapters}
|
||||
navigation={navigation}
|
||||
trackerSheetRef={trackerSheetRef}
|
||||
chaptersSettingsSheetRef={chaptersSettingsSheetRef}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<ChapterItem
|
||||
theme={theme}
|
||||
chapter={item}
|
||||
novelUrl={novelUrl}
|
||||
dispatch={dispatch}
|
||||
sourceId={sourceId}
|
||||
navigation={navigation}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<FlatList
|
||||
data={!loading && chapters}
|
||||
keyExtractor={(item) => item.chapterId.toString()}
|
||||
removeClippedSubviews={true}
|
||||
maxToRenderPerBatch={5}
|
||||
windowSize={15}
|
||||
initialNumToRender={7}
|
||||
renderItem={renderItem}
|
||||
ListHeaderComponent={ListHeaderComponent}
|
||||
refreshControl={refreshControl()}
|
||||
/>
|
||||
{!loading && (
|
||||
<Portal>
|
||||
<ChaptersSettingsSheet
|
||||
novelUrl={novelUrl}
|
||||
bottomSheetRef={chaptersSettingsSheetRef}
|
||||
dispatch={dispatch}
|
||||
sortAndFilterChapters={sortAndFilterChapters}
|
||||
novelId={novelId || novel.novelId}
|
||||
savedSort={sort}
|
||||
savedFilter={filter}
|
||||
/>
|
||||
<TrackSheet
|
||||
bottomSheetRef={trackerSheetRef}
|
||||
novelId={novelId || novel.novelId}
|
||||
novelName={novelName || novel.novelName}
|
||||
theme={theme}
|
||||
/>
|
||||
</Portal>
|
||||
)}
|
||||
</View>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Novel;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1 },
|
||||
});
|
||||
+26
-35
@@ -7,19 +7,15 @@ import {
|
||||
deleteChapterAction,
|
||||
downloadChapterAction,
|
||||
} from "../../../redux/novel/novel.actions";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
const ChapterCard = ({
|
||||
const ChapterItem = ({
|
||||
novelUrl,
|
||||
chapter,
|
||||
extensionId,
|
||||
sourceId,
|
||||
dispatch,
|
||||
theme,
|
||||
// downloading,
|
||||
downloadChapterAction,
|
||||
deleteChapterAction,
|
||||
navigation,
|
||||
}) => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const openMenu = () => setVisible(true);
|
||||
const closeMenu = () => setVisible(false);
|
||||
@@ -28,7 +24,7 @@ const ChapterCard = ({
|
||||
if (chapter.downloaded === 3) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
color={theme.textColorHintDark}
|
||||
color={theme.textColorHint}
|
||||
size={25}
|
||||
style={{ margin: 3.5, padding: 5 }}
|
||||
/>
|
||||
@@ -52,9 +48,11 @@ const ChapterCard = ({
|
||||
>
|
||||
<Menu.Item
|
||||
onPress={() =>
|
||||
deleteChapterAction(
|
||||
chapter.chapterId,
|
||||
chapter.chapterName
|
||||
dispatch(
|
||||
deleteChapterAction(
|
||||
chapter.chapterId,
|
||||
chapter.chapterName
|
||||
)
|
||||
)
|
||||
}
|
||||
title="Delete"
|
||||
@@ -67,18 +65,19 @@ const ChapterCard = ({
|
||||
<IconButton
|
||||
icon="arrow-down-circle-outline"
|
||||
animated
|
||||
color={theme.textColorHintDark}
|
||||
color={theme.textColorHint}
|
||||
size={25}
|
||||
onPress={() => {
|
||||
downloadChapterAction(
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapter.chapterUrl,
|
||||
chapter.chapterName,
|
||||
chapter.chapterId
|
||||
);
|
||||
// console.log(downloading);
|
||||
}}
|
||||
onPress={() =>
|
||||
dispatch(
|
||||
downloadChapterAction(
|
||||
sourceId,
|
||||
novelUrl,
|
||||
chapter.chapterUrl,
|
||||
chapter.chapterName,
|
||||
chapter.chapterId
|
||||
)
|
||||
)
|
||||
}
|
||||
style={{ margin: 2 }}
|
||||
/>
|
||||
);
|
||||
@@ -92,7 +91,7 @@ const ChapterCard = ({
|
||||
navigation.navigate("Chapter", {
|
||||
chapterId: chapter.chapterId,
|
||||
chapterUrl: chapter.chapterUrl,
|
||||
extensionId,
|
||||
extensionId: sourceId,
|
||||
novelUrl: novelUrl,
|
||||
chapterName: chapter.chapterName,
|
||||
novelId: chapter.novelId,
|
||||
@@ -108,7 +107,7 @@ const ChapterCard = ({
|
||||
color: theme.textColorPrimary,
|
||||
},
|
||||
chapter.read === 1 && {
|
||||
color: theme.textColorHintDark,
|
||||
color: theme.textColorHint,
|
||||
},
|
||||
]}
|
||||
numberOfLines={1}
|
||||
@@ -124,7 +123,7 @@ const ChapterCard = ({
|
||||
fontSize: 12,
|
||||
},
|
||||
chapter.read === 1 && {
|
||||
color: theme.textColorHintDark,
|
||||
color: theme.textColorHint,
|
||||
},
|
||||
]}
|
||||
numberOfLines={1}
|
||||
@@ -139,15 +138,7 @@ const ChapterCard = ({
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
theme: state.themeReducer.theme,
|
||||
// downloading: state.novelReducer.downloading,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
downloadChapterAction,
|
||||
deleteChapterAction,
|
||||
})(ChapterCard);
|
||||
export default ChapterItem;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
chapterCardContainer: {
|
||||
+38
-23
@@ -7,15 +7,28 @@ import { useSelector } from "react-redux";
|
||||
|
||||
const ChaptersSettingsSheet = ({
|
||||
bottomSheetRef,
|
||||
sort,
|
||||
filter,
|
||||
setSort,
|
||||
setFilter,
|
||||
novelUrl,
|
||||
novelId,
|
||||
sortAndFilterChapters,
|
||||
dispatch,
|
||||
savedSort,
|
||||
savedFilter,
|
||||
}) => {
|
||||
const [sort, setSort] = useState(savedSort || "ORDER BY chapterId ASC");
|
||||
const [filter, setFilter] = useState(savedFilter || "");
|
||||
|
||||
const sortChapters = (val) => {
|
||||
setSort(val);
|
||||
dispatch(sortAndFilterChapters(novelId, val, filter));
|
||||
};
|
||||
|
||||
const filterChapters = (val) => {
|
||||
setFilter(val);
|
||||
dispatch(sortAndFilterChapters(novelId, sort, val));
|
||||
};
|
||||
|
||||
const checkBoxStyle = { flexDirection: "row", alignItems: "center" };
|
||||
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
const theme = useSelector((state) => state.settingsReducer.theme);
|
||||
|
||||
const [animatedValue] = useState(new Animated.Value(0));
|
||||
|
||||
@@ -45,7 +58,9 @@ const ChaptersSettingsSheet = ({
|
||||
</List.Subheader>
|
||||
<View>
|
||||
<RadioButton.Group
|
||||
onValueChange={(newValue) => setSort(newValue)}
|
||||
onValueChange={(newValue) =>
|
||||
sortChapters(newValue, filter)
|
||||
}
|
||||
value={sort}
|
||||
>
|
||||
<View
|
||||
@@ -61,8 +76,8 @@ const ChaptersSettingsSheet = ({
|
||||
}}
|
||||
>
|
||||
<RadioButton.Item
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
uncheckedColor={theme.textColorHint}
|
||||
color={theme.colorAccent}
|
||||
value="ORDER BY chapterId DESC"
|
||||
/>
|
||||
<Text
|
||||
@@ -80,8 +95,8 @@ const ChaptersSettingsSheet = ({
|
||||
}}
|
||||
>
|
||||
<RadioButton.Item
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
uncheckedColor={theme.textColorHint}
|
||||
color={theme.colorAccent}
|
||||
value="ORDER BY chapterId ASC"
|
||||
/>
|
||||
<Text
|
||||
@@ -108,9 +123,9 @@ const ChaptersSettingsSheet = ({
|
||||
<View style={checkBoxStyle}>
|
||||
<Checkbox.Item
|
||||
status={filter === "" ? "checked" : "unchecked"}
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => setFilter("")}
|
||||
uncheckedColor={theme.textColorHint}
|
||||
color={theme.colorAccent}
|
||||
onPress={() => filterChapters("")}
|
||||
/>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Show all
|
||||
@@ -121,9 +136,9 @@ const ChaptersSettingsSheet = ({
|
||||
status={
|
||||
filter === "AND `read`=1" ? "checked" : "unchecked"
|
||||
}
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => setFilter("AND `read`=1")}
|
||||
uncheckedColor={theme.textColorHint}
|
||||
color={theme.colorAccent}
|
||||
onPress={() => filterChapters("AND `read`=1")}
|
||||
/>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Show read chapters
|
||||
@@ -134,9 +149,9 @@ const ChaptersSettingsSheet = ({
|
||||
status={
|
||||
filter === "AND `read`=0" ? "checked" : "unchecked"
|
||||
}
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => setFilter("AND `read`=0")}
|
||||
uncheckedColor={theme.textColorHint}
|
||||
color={theme.colorAccent}
|
||||
onPress={() => filterChapters("AND `read`=0")}
|
||||
/>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Show unread chapters
|
||||
@@ -149,9 +164,9 @@ const ChaptersSettingsSheet = ({
|
||||
? "checked"
|
||||
: "unchecked"
|
||||
}
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => setFilter("AND downloaded=1")}
|
||||
uncheckedColor={theme.textColorHint}
|
||||
color={theme.colorAccent}
|
||||
onPress={() => filterChapters("AND downloaded=1")}
|
||||
/>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Show downloaded chapters
|
||||
+1
-1
@@ -20,7 +20,7 @@ const FollowChip = ({ followed, novel, theme }) => {
|
||||
<MaterialCommunityIcons
|
||||
name={getIconName()}
|
||||
size={21}
|
||||
color={theme.colorAccentDark}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
)}
|
||||
onPress={() => dispatch(followNovelAction(novel))}
|
||||
+2
-2
@@ -6,8 +6,8 @@ export const GenreChip = ({ children, theme }) => (
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{
|
||||
color: theme.colorAccentDark,
|
||||
borderColor: theme.colorAccentDark,
|
||||
color: theme.colorAccent,
|
||||
borderColor: theme.colorAccent,
|
||||
backgroundColor: theme.colorPrimary,
|
||||
},
|
||||
]}
|
||||
+15
-3
@@ -1,13 +1,25 @@
|
||||
import React from "react";
|
||||
import { View, ImageBackground, StyleSheet } from "react-native";
|
||||
|
||||
import { ImageBackground, StyleSheet } from "react-native";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import easeGradient from "react-native-easing-gradient";
|
||||
|
||||
const NovelCoverImage = ({ children, source, theme }) => {
|
||||
const { colors, locations } = easeGradient({
|
||||
colorStops: {
|
||||
0: {
|
||||
color: "rgba(0,0,0,0.2)",
|
||||
},
|
||||
1: {
|
||||
color: theme.colorPrimaryDark,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<ImageBackground source={source} style={styles.background}>
|
||||
<LinearGradient
|
||||
colors={["rgba(0,0,0,0.2)", theme.colorPrimaryDark]}
|
||||
colors={colors}
|
||||
locations={locations}
|
||||
style={styles.linearGradient}
|
||||
>
|
||||
{children}
|
||||
+1
-1
@@ -29,7 +29,7 @@ const NovelSummary = ({ summary, followed, theme }) => {
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
color: theme.colorAccentDark,
|
||||
color: theme.colorAccent,
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
},
|
||||
styles.showMoreButton,
|
||||
+5
-6
@@ -1,21 +1,20 @@
|
||||
import React from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { Button } from "react-native-paper";
|
||||
import { useContinueReading } from "../../../../Hooks/reduxHooks";
|
||||
|
||||
const ReadButton = ({ navigation, novel, chapters, theme }) => {
|
||||
const lastRead = chapters.find((obj) => obj.read === 0);
|
||||
|
||||
const ReadButton = ({ navigation, novel, chapters, theme, lastRead }) => {
|
||||
return (
|
||||
chapters.length > 0 &&
|
||||
(lastRead ? (
|
||||
<Button
|
||||
color="white"
|
||||
style={[
|
||||
{ backgroundColor: theme.colorAccentDark },
|
||||
{ backgroundColor: theme.colorAccent },
|
||||
styles.startButton,
|
||||
]}
|
||||
uppercase={false}
|
||||
labelStyle={{ letterSpacing: 0 }}
|
||||
labelStyle={{ letterSpacing: 0, color: theme.colorButtonText }}
|
||||
onPress={() => {
|
||||
navigation.navigate("Chapter", {
|
||||
chapterId: lastRead.chapterId,
|
||||
@@ -32,7 +31,7 @@ const ReadButton = ({ navigation, novel, chapters, theme }) => {
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
color={theme.textColorHintDark}
|
||||
color={theme.textColorHint}
|
||||
style={[
|
||||
styles.startButton,
|
||||
{ backgroundColor: theme.colorDisabled },
|
||||
+1
-1
@@ -15,7 +15,7 @@ const TrackerChip = ({ isTracked, trackerSheetRef, theme }) => {
|
||||
<MaterialCommunityIcons
|
||||
name={getIconName(isTracked)}
|
||||
size={21}
|
||||
color={theme.colorAccentDark}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
)}
|
||||
onPress={() => trackerSheetRef.current.show()}
|
||||
+55
-49
@@ -17,18 +17,18 @@ import NovelSummary from "./Info/NovelSummary";
|
||||
import ReadButton from "./Info/ReadButton";
|
||||
|
||||
const NovelInfoHeader = ({
|
||||
novelName,
|
||||
novelCover,
|
||||
loading,
|
||||
item,
|
||||
novel,
|
||||
chapters,
|
||||
theme,
|
||||
chaptersSettingsSheetRef,
|
||||
filter,
|
||||
loading,
|
||||
dispatch,
|
||||
chapters,
|
||||
lastRead,
|
||||
navigation,
|
||||
trackerSheetRef,
|
||||
chaptersSettingsSheetRef,
|
||||
}) => {
|
||||
const navigation = useNavigation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [downloadMenu, showDownloadMenu] = useState(false);
|
||||
|
||||
const tracker = useSelector((state) => state.trackerReducer.tracker);
|
||||
@@ -46,11 +46,11 @@ const NovelInfoHeader = ({
|
||||
|
||||
return (
|
||||
<View style={{ flexGrow: 1 }}>
|
||||
<NovelCoverImage source={{ uri: novelCover }} theme={theme}>
|
||||
<NovelCoverImage source={{ uri: item.novelCover }} theme={theme}>
|
||||
<View style={styles.detailsContainer}>
|
||||
<View>
|
||||
<Image
|
||||
source={{ uri: novelCover }}
|
||||
source={{ uri: item.novelCover }}
|
||||
style={styles.logo}
|
||||
/>
|
||||
</View>
|
||||
@@ -62,43 +62,44 @@ const NovelInfoHeader = ({
|
||||
{ color: theme.textColorPrimary },
|
||||
]}
|
||||
>
|
||||
{novelName}
|
||||
{item.novelName}
|
||||
</Text>
|
||||
{!loading && (
|
||||
<>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
marginVertical: 3,
|
||||
fontSize: 14,
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{novel.author.replace(",", ", ")}
|
||||
</Text>
|
||||
<>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
marginVertical: 3,
|
||||
fontSize: 14,
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{(item.author &&
|
||||
item.author.replace(",", ", ")) ||
|
||||
(!loading &&
|
||||
novel.author.replace(",", ", "))}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
marginVertical: 3,
|
||||
fontSize: 14,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{novel.status}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{novel.source}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
marginVertical: 3,
|
||||
fontSize: 14,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.status || (!loading && novel.status)}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.source || (!loading && novel.source)}
|
||||
</Text>
|
||||
</>
|
||||
</View>
|
||||
</View>
|
||||
</NovelCoverImage>
|
||||
@@ -122,7 +123,7 @@ const NovelInfoHeader = ({
|
||||
WebBrowser.openBrowserAsync(novel.sourceUrl)
|
||||
}
|
||||
icon="earth"
|
||||
color={theme.colorAccentDark}
|
||||
color={theme.colorAccent}
|
||||
size={21}
|
||||
/>
|
||||
<IconButton
|
||||
@@ -130,7 +131,7 @@ const NovelInfoHeader = ({
|
||||
Share.share({ message: novel.sourceUrl })
|
||||
}
|
||||
icon="share-variant"
|
||||
color={theme.colorAccentDark}
|
||||
color={theme.colorAccent}
|
||||
size={21}
|
||||
/>
|
||||
<Menu
|
||||
@@ -139,7 +140,7 @@ const NovelInfoHeader = ({
|
||||
anchor={
|
||||
<IconButton
|
||||
icon="download"
|
||||
color={theme.colorAccentDark}
|
||||
color={theme.colorAccent}
|
||||
size={21}
|
||||
onPress={() => showDownloadMenu(true)}
|
||||
/>
|
||||
@@ -193,6 +194,7 @@ const NovelInfoHeader = ({
|
||||
chapters={chapters}
|
||||
navigation={navigation}
|
||||
theme={theme}
|
||||
lastRead={lastRead}
|
||||
/>
|
||||
<TouchableRipple
|
||||
style={styles.bottomsheet}
|
||||
@@ -214,7 +216,11 @@ const NovelInfoHeader = ({
|
||||
</Text>
|
||||
<IconButton
|
||||
icon="filter-variant"
|
||||
color={theme.textColorPrimary}
|
||||
color={
|
||||
filter
|
||||
? theme.filterColor
|
||||
: theme.textColorPrimary
|
||||
}
|
||||
size={24}
|
||||
/>
|
||||
</>
|
||||
+2
-2
@@ -19,13 +19,13 @@ export const AddMalTrackingCard = ({ theme, setTrackSearchDialog }) => (
|
||||
style={styles.rippleContainer}
|
||||
borderless
|
||||
onPress={() => setTrackSearchDialog(true)}
|
||||
rippleColor={theme.colorAccentDark}
|
||||
rippleColor={theme.colorAccent}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
flex: 1,
|
||||
textAlignVertical: "center",
|
||||
color: theme.colorAccentDark,
|
||||
color: theme.colorAccent,
|
||||
}}
|
||||
>
|
||||
Add Tracking
|
||||
+3
-3
@@ -39,13 +39,13 @@ const SetTrackChaptersDialog = ({
|
||||
keyboardType="numeric"
|
||||
theme={{
|
||||
colors: {
|
||||
primary: theme.colorAccentDark,
|
||||
placeholder: theme.textColorHintDark,
|
||||
primary: theme.colorAccent,
|
||||
placeholder: theme.textColorHint,
|
||||
text: theme.textColorPrimary,
|
||||
background: "transparent",
|
||||
},
|
||||
}}
|
||||
underlineColor={theme.textColorHintDark}
|
||||
underlineColor={theme.textColorHint}
|
||||
dense
|
||||
/>
|
||||
</Modal>
|
||||
+9
-9
@@ -85,12 +85,12 @@ const TrackSearchDialog = ({
|
||||
onSubmitEditing={getSearchresults}
|
||||
theme={{
|
||||
colors: {
|
||||
primary: theme.colorAccentDark,
|
||||
primary: theme.colorAccent,
|
||||
text: theme.textColorPrimary,
|
||||
background: "transparent",
|
||||
},
|
||||
}}
|
||||
underlineColor={theme.textColorHintDark}
|
||||
underlineColor={theme.textColorHint}
|
||||
right={
|
||||
<TextInput.Icon
|
||||
color={theme.textColorSecondary}
|
||||
@@ -104,7 +104,7 @@ const TrackSearchDialog = ({
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator
|
||||
color={theme.colorAccentDark}
|
||||
color={theme.colorAccent}
|
||||
size={45}
|
||||
style={{ margin: 16 }}
|
||||
/>
|
||||
@@ -125,11 +125,11 @@ const TrackSearchDialog = ({
|
||||
>
|
||||
<Button
|
||||
labelStyle={{
|
||||
color: theme.colorAccentDark,
|
||||
color: theme.colorAccent,
|
||||
letterSpacing: 0,
|
||||
textTransform: "none",
|
||||
}}
|
||||
theme={{ colors: { primary: theme.colorAccentDark } }}
|
||||
theme={{ colors: { primary: theme.colorAccent } }}
|
||||
onPress={() => setSelectedNovel(null)}
|
||||
>
|
||||
Remove
|
||||
@@ -137,22 +137,22 @@ const TrackSearchDialog = ({
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
<Button
|
||||
labelStyle={{
|
||||
color: theme.colorAccentDark,
|
||||
color: theme.colorAccent,
|
||||
letterSpacing: 0,
|
||||
textTransform: "none",
|
||||
}}
|
||||
theme={{ colors: { primary: theme.colorAccentDark } }}
|
||||
theme={{ colors: { primary: theme.colorAccent } }}
|
||||
onPress={() => setTrackSearchDialog(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
labelStyle={{
|
||||
color: theme.colorAccentDark,
|
||||
color: theme.colorAccent,
|
||||
letterSpacing: 0,
|
||||
textTransform: "none",
|
||||
}}
|
||||
theme={{ colors: { primary: theme.colorAccentDark } }}
|
||||
theme={{ colors: { primary: theme.colorAccent } }}
|
||||
onPress={() => {
|
||||
if (selectedNovel) {
|
||||
dispatch(
|
||||
@@ -0,0 +1,95 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
FlatList,
|
||||
ActivityIndicator,
|
||||
RefreshControl,
|
||||
} from "react-native";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import EmptyView from "../../Components/EmptyView";
|
||||
import {
|
||||
getUpdatesAction,
|
||||
updateLibraryAction,
|
||||
} from "../../redux/updates/updates.actions";
|
||||
import { Appbar } from "./components/Appbar";
|
||||
import UpdatesItem from "./components/UpdatesItem";
|
||||
import { useTheme } from "../../Hooks/reduxHooks";
|
||||
|
||||
const Updates = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const { updates, loading } = useSelector((state) => state.updatesReducer);
|
||||
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getUpdatesAction());
|
||||
}, [getUpdatesAction, updates]);
|
||||
|
||||
const onRefresh = async () => {
|
||||
setRefreshing(true);
|
||||
dispatch(updateLibraryAction());
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
const onPress = (item) =>
|
||||
navigation.navigate("Chapter", {
|
||||
chapterId: item.chapterId,
|
||||
chapterUrl: item.chapterUrl,
|
||||
extensionId: item.sourceId,
|
||||
novelUrl: item.novelUrl,
|
||||
chapterName: item.chapterName,
|
||||
novelId: item.novelId,
|
||||
});
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<UpdatesItem item={item} theme={theme} onPress={() => onPress(item)} />
|
||||
);
|
||||
|
||||
const ListFooterComponent = () =>
|
||||
loading && <ActivityIndicator size="small" color={theme.colorAccent} />;
|
||||
|
||||
const ListEmptyComponent = () =>
|
||||
!loading && (
|
||||
<EmptyView icon="(˘・_・˘)" description="No recent updates" />
|
||||
);
|
||||
|
||||
const refreshControl = () => (
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
colors={["white"]}
|
||||
progressBackgroundColor={theme.colorAccent}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar theme={theme} dispatch={dispatch} />
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<FlatList
|
||||
contentContainerStyle={styles.flatList}
|
||||
data={updates}
|
||||
keyExtractor={(item) => item.updateId.toString()}
|
||||
renderItem={renderItem}
|
||||
ListFooterComponent={ListFooterComponent()}
|
||||
ListEmptyComponent={ListEmptyComponent()}
|
||||
refreshControl={refreshControl()}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Updates;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1 },
|
||||
flatList: { flexGrow: 1, paddingVertical: 8 },
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
import { Appbar as MaterialAppbar } from "react-native-paper";
|
||||
|
||||
import { updateLibraryAction } from "../../../redux/updates/updates.actions";
|
||||
|
||||
export const Appbar = ({ theme, dispatch }) => (
|
||||
<MaterialAppbar.Header style={{ backgroundColor: theme.colorPrimary }}>
|
||||
<MaterialAppbar.Content
|
||||
title="Updates"
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
/>
|
||||
<MaterialAppbar.Action
|
||||
icon="reload"
|
||||
onPress={() => dispatch(updateLibraryAction())}
|
||||
/>
|
||||
</MaterialAppbar.Header>
|
||||
);
|
||||
@@ -0,0 +1,65 @@
|
||||
import React from "react";
|
||||
import { View, Text, Image, StyleSheet } from "react-native";
|
||||
import { TouchableRipple } from "react-native-paper";
|
||||
import moment from "moment";
|
||||
|
||||
const UpdateCard = ({ item, theme, onPress }) => {
|
||||
return (
|
||||
<TouchableRipple
|
||||
style={styles.updateCard}
|
||||
onPress={onPress}
|
||||
rippleColor={theme.rippleColor}
|
||||
borderless
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
source={{ uri: item.novelCover }}
|
||||
style={styles.updateIcon}
|
||||
/>
|
||||
<View style={styles.chapterDetails}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{`${item.chapterName} · ${moment(
|
||||
item.updateTime
|
||||
).calendar()}`}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateCard;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
updateCard: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
updateIcon: {
|
||||
width: 42,
|
||||
height: 42,
|
||||
borderRadius: 4,
|
||||
},
|
||||
chapterDetails: {
|
||||
flex: 1,
|
||||
marginLeft: 16,
|
||||
},
|
||||
});
|
||||
@@ -3,13 +3,13 @@ export const fetchSources = async () => {
|
||||
|
||||
let res = await fetch(url);
|
||||
let sources = await res.json();
|
||||
// console.log(sources);
|
||||
|
||||
return sources;
|
||||
};
|
||||
|
||||
export const fetchNovel = async (sourceId, novelUrl) => {
|
||||
const url = `https://lnreader-extensions.vercel.app/api/${sourceId}/novel/${novelUrl}`;
|
||||
// console.log(url);
|
||||
|
||||
let data = await fetch(url);
|
||||
let res = await data.json();
|
||||
|
||||
@@ -35,8 +35,6 @@ export const fetchNovel = async (sourceId, novelUrl) => {
|
||||
export const fetchChapter = async (sourceId, novelUrl, chapterUrl) => {
|
||||
const url = `https://lnreader-extensions.vercel.app/api/${sourceId}/novel/${novelUrl}${chapterUrl}`;
|
||||
|
||||
// console.log(url);
|
||||
|
||||
let res = await fetch(url);
|
||||
let chapter = await res.json();
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import * as DocumentPicker from "expo-document-picker";
|
||||
import { Platform } from "react-native";
|
||||
import * as FileSystem from "expo-file-system";
|
||||
import { StorageAccessFramework } from "expo-file-system";
|
||||
|
||||
import { getLibrary } from "../database/queries/LibraryQueries";
|
||||
import { restoreLibrary } from "../database/queries/NovelQueries";
|
||||
import { getLibrary } from "../Database/queries/LibraryQueries";
|
||||
import { restoreLibrary } from "../Database/queries/NovelQueries";
|
||||
import { showToast } from "../Hooks/showToast";
|
||||
|
||||
export const createBackup = async () => {
|
||||
@@ -1,9 +1,19 @@
|
||||
import * as SQLite from "expo-sqlite";
|
||||
const db = SQLite.openDatabase("lnreader.db");
|
||||
|
||||
import { getLibrary } from "../database/queries/LibraryQueries";
|
||||
import { getLibrary } from "../Database/queries/LibraryQueries";
|
||||
import { fetchChapters, fetchNovel } from "../Services/Source/source";
|
||||
import { ToastAndroid } from "react-native";
|
||||
import * as Notifications from "expo-notifications";
|
||||
|
||||
Notifications.setNotificationHandler({
|
||||
handleNotification: async () => {
|
||||
return {
|
||||
shouldShowAlert: true,
|
||||
shouldPlaySound: true,
|
||||
shouldSetBadge: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const updateNovelDetails = async (novel, novelId) => {
|
||||
db.transaction((tx) => {
|
||||
@@ -27,7 +37,7 @@ const updateNovelDetails = async (novel, novelId) => {
|
||||
|
||||
export const updateNovel = async (extensionId, novelUrl, novelId) => {
|
||||
let novel = await fetchNovel(extensionId, novelUrl);
|
||||
// console.log(novel);
|
||||
|
||||
await updateNovelDetails(novel, novelId);
|
||||
|
||||
db.transaction((tx) => {
|
||||
@@ -89,14 +99,7 @@ export const updateNovelChapters = async (extensionId, novelUrl, novelId) => {
|
||||
tx.executeSql(
|
||||
"INSERT OR IGNORE INTO updates (chapterId, novelId, updateTime) values (?, ?, (datetime('now','localtime')))",
|
||||
[insertId, novelId],
|
||||
(txObj, res) => {
|
||||
// console.log(
|
||||
// "Inserted Chapter Id -> " +
|
||||
// insertId +
|
||||
// " Novel Id " +
|
||||
// novelId
|
||||
// );
|
||||
},
|
||||
(txObj, res) => {},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
}
|
||||
@@ -114,6 +117,12 @@ export const updateAllNovels = async () => {
|
||||
setTimeout(async () => {
|
||||
updateNovelChapters(novel.sourceId, novel.novelUrl, novel.novelId);
|
||||
console.log(novel.novelName + " Updated");
|
||||
if (index + 1 === libraryNovels.length) {
|
||||
Notifications.scheduleNotificationAsync({
|
||||
content: { title: "Library Updated" },
|
||||
trigger: null,
|
||||
});
|
||||
}
|
||||
}, 3000 * index)
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Reader fonts
|
||||
*/
|
||||
|
||||
export const fonts = {
|
||||
"pt-sans-bold": require("../../assets/fonts/PTSansNarrow-Bold.ttf"),
|
||||
"arbutus-slab": require("../../assets/fonts/ArbutusSlab-Regular.ttf"),
|
||||
domine: require("../../assets/fonts/Domine-VariableFont_wght.ttf"),
|
||||
lato: require("../../assets/fonts/Lato-Regular.ttf"),
|
||||
"noto-sans": require("../../assets/fonts/NotoSans-Regular.ttf"),
|
||||
"open-sans": require("../../assets/fonts/OpenSans-Regular.ttf"),
|
||||
"pt-serif": require("../../assets/fonts/PTSerif-Regular.ttf"),
|
||||
};
|
||||
@@ -4,10 +4,10 @@
|
||||
export const theme = {
|
||||
colorPrimary: "#000000",
|
||||
colorPrimaryDark: "#000000",
|
||||
colorAccentDark: "#3399FF",
|
||||
colorAccent: "#3399FF",
|
||||
textColorPrimary: "#FFFFFF",
|
||||
textColorSecondary: "rgba(255,255,255,0.7)",
|
||||
textColorHintDark: "rgba(255,255,255,0.5)",
|
||||
textColorHint: "rgba(255,255,255,0.5)",
|
||||
rippleColor: "rgba(255,255,255,0.2)",
|
||||
};
|
||||
|
||||
@@ -19,15 +19,17 @@ export const lightTheme = {
|
||||
name: "Light",
|
||||
colorPrimary: "#FFFFFF",
|
||||
colorPrimaryDark: "#FAFAFA",
|
||||
colorAccentDark: "#2979FF",
|
||||
colorAccent: "#2979FF",
|
||||
textColorPrimary: "#000000",
|
||||
textColorSecondary: "rgba(0,0,0,0.54)",
|
||||
textColorHintDark: "rgba(0,0,0,0.38)",
|
||||
textColorHint: "rgba(0,0,0,0.38)",
|
||||
rippleColor: "rgba(0,0,0,0.12)",
|
||||
searchBarColor: "#FFFFFF",
|
||||
menuColor: "#FFFFFF",
|
||||
dividerColor: "rgba(0,0,0,0.1)",
|
||||
filterColor: "#FFC107",
|
||||
colorDisabled: "rgba(220,220,220,255)",
|
||||
colorButtonText: "#000000",
|
||||
statusBar: "dark",
|
||||
};
|
||||
|
||||
@@ -39,15 +41,17 @@ export const darkTheme = {
|
||||
name: "Dark",
|
||||
colorPrimary: "#242529",
|
||||
colorPrimaryDark: "#202125",
|
||||
colorAccentDark: "#3399FF",
|
||||
colorAccent: "#3399FF",
|
||||
textColorPrimary: "#FFFFFF",
|
||||
textColorSecondary: "rgba(255,255,255,0.7)",
|
||||
textColorHintDark: "rgba(255,255,255,0.5)",
|
||||
textColorHint: "rgba(255,255,255,0.5)",
|
||||
rippleColor: "rgba(255,255,255,0.2)",
|
||||
searchBarColor: "#303135",
|
||||
menuColor: "#242529",
|
||||
filterColor: "#FFEB3B",
|
||||
dividerColor: "rgba(255,255,255,0.1)",
|
||||
colorDisabled: "rgba(57,57,57,255)",
|
||||
colorButtonText: "#FFFFFF",
|
||||
statusBar: "light",
|
||||
};
|
||||
|
||||
@@ -59,15 +63,17 @@ export const amoledDarkTheme = {
|
||||
name: "AMOLED Dark",
|
||||
colorPrimary: "#000000",
|
||||
colorPrimaryDark: "#000000",
|
||||
colorAccentDark: "#3399FF",
|
||||
colorAccent: "#3399FF",
|
||||
textColorPrimary: "#FFFFFF",
|
||||
textColorSecondary: "rgba(255,255,255,0.7)",
|
||||
textColorHintDark: "rgba(255,255,255,0.5)",
|
||||
textColorHint: "rgba(255,255,255,0.5)",
|
||||
rippleColor: "rgba(255,255,255,0.2)",
|
||||
searchBarColor: "#1F1F1F",
|
||||
menuColor: "#242529",
|
||||
filterColor: "#FFEB3B",
|
||||
dividerColor: "rgba(255,255,255,0.15)",
|
||||
colorDisabled: "rgba(33,33,33,255)",
|
||||
colorButtonText: "#FFFFFF",
|
||||
statusBar: "light",
|
||||
};
|
||||
|
||||
@@ -79,14 +85,35 @@ export const midnightDuskTheme = {
|
||||
name: "Midnight Dusk",
|
||||
colorPrimary: "#201F27",
|
||||
colorPrimaryDark: "#16151D",
|
||||
colorAccentDark: "#F02475",
|
||||
colorAccent: "#F02475",
|
||||
textColorPrimary: "#FFFFFF",
|
||||
textColorSecondary: "rgba(255,255,255,0.7)",
|
||||
textColorHintDark: "rgba(255,255,255,0.5)",
|
||||
textColorHint: "rgba(255,255,255,0.5)",
|
||||
rippleColor: "rgba(255,255,255,0.2)",
|
||||
searchBarColor: "#201F27",
|
||||
menuColor: "#201F27",
|
||||
filterColor: "#FFEB3B",
|
||||
dividerColor: "rgba(255,255,255,0.1)",
|
||||
colorDisabled: "rgba(57,57,57,255)",
|
||||
colorButtonText: "#FFFFFF",
|
||||
statusBar: "light",
|
||||
};
|
||||
|
||||
export const limeTheme = {
|
||||
id: 4,
|
||||
name: "Lime",
|
||||
colorPrimary: "#201F27",
|
||||
colorPrimaryDark: "#16151D",
|
||||
colorAccent: "#4AF88A",
|
||||
textColorPrimary: "#FFFFFF",
|
||||
textColorSecondary: "rgba(255,255,255,0.7)",
|
||||
textColorHint: "rgba(255,255,255,0.5)",
|
||||
rippleColor: "rgba(255,255,255,0.2)",
|
||||
searchBarColor: "#201F27",
|
||||
menuColor: "#201F27",
|
||||
filterColor: "#FFEB3B",
|
||||
dividerColor: "rgba(255,255,255,0.1)",
|
||||
colorDisabled: "rgba(57,57,57,255)",
|
||||
colorButtonText: "#16151D",
|
||||
statusBar: "light",
|
||||
};
|
||||
@@ -1,274 +0,0 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View, Text, ImageBackground, Image } from "react-native";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import { TouchableRipple } from "react-native-paper";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const NovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
const displayMode = useSelector(
|
||||
(state) => state.settingsReducer.displayMode
|
||||
);
|
||||
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
const itemsPerRow = useSelector(
|
||||
(state) => state.settingsReducer.itemsPerRow
|
||||
);
|
||||
|
||||
const height = {
|
||||
1: 550,
|
||||
2: 280,
|
||||
3: 180,
|
||||
4: 140,
|
||||
5: 100,
|
||||
6: 80,
|
||||
7: 70,
|
||||
8: 58,
|
||||
9: 55,
|
||||
10: 50,
|
||||
};
|
||||
|
||||
return displayMode !== 2 ? (
|
||||
<View style={{ flex: 1 / itemsPerRow }}>
|
||||
<TouchableRipple
|
||||
borderless
|
||||
centered
|
||||
rippleColor={theme.rippleColor}
|
||||
style={styles.opac}
|
||||
onPress={onPress}
|
||||
>
|
||||
<>
|
||||
{item.novelCover && (
|
||||
<ImageBackground
|
||||
source={{ uri: item.novelCover }}
|
||||
style={{ height: height[itemsPerRow] }}
|
||||
imageStyle={[
|
||||
{ borderRadius: 4 },
|
||||
libraryStatus && { opacity: 0.5 },
|
||||
]}
|
||||
progressiveRenderingEnabled={true}
|
||||
>
|
||||
<View style={{ flexDirection: "row", margin: 4 }}>
|
||||
{item.chaptersDownloaded && (
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
flex: 0,
|
||||
backgroundColor: "#47a84a",
|
||||
color: "#FFFFFF",
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
paddingTop: 2,
|
||||
paddingHorizontal: 4,
|
||||
alignSelf: "flex-start",
|
||||
fontSize: 12,
|
||||
},
|
||||
!item.chaptersUnread && {
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.chaptersDownloaded}
|
||||
</Text>
|
||||
)}
|
||||
{item.chaptersUnread && (
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
flex: 0,
|
||||
backgroundColor: "#2979FF",
|
||||
color: "#FFFFFF",
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
paddingTop: 2,
|
||||
paddingHorizontal: 4,
|
||||
alignSelf: "flex-start",
|
||||
fontSize: 12,
|
||||
},
|
||||
!item.chaptersDownloaded && {
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.chaptersUnread}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
{displayMode === 0 && (
|
||||
<View style={styles.titleContainer}>
|
||||
<LinearGradient
|
||||
colors={[
|
||||
"transparent",
|
||||
"rgba(0,0,0,0.6)",
|
||||
]}
|
||||
style={styles.linearGradient}
|
||||
>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.title,
|
||||
{
|
||||
color:
|
||||
"rgba(255,255,255,1)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
</View>
|
||||
)}
|
||||
</ImageBackground>
|
||||
)}
|
||||
{displayMode === 1 && (
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.title,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
padding: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
</TouchableRipple>
|
||||
</View>
|
||||
) : (
|
||||
<TouchableRipple
|
||||
borderless
|
||||
centered
|
||||
rippleColor={theme.rippleColor}
|
||||
style={styles.listView}
|
||||
onPress={onPress}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: item.novelCover }}
|
||||
style={styles.extensionIcon}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
marginLeft: 16,
|
||||
fontSize: 15,
|
||||
paddingRight: 8,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{item.chaptersDownloaded && (
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
backgroundColor: "#47a84a",
|
||||
color: "#FFFFFF",
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
paddingTop: 2,
|
||||
paddingHorizontal: 4,
|
||||
fontSize: 12,
|
||||
},
|
||||
!item.chaptersUnread && {
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.chaptersDownloaded}
|
||||
</Text>
|
||||
)}
|
||||
{item.chaptersUnread && (
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
backgroundColor: "#2979FF",
|
||||
color: "#FFFFFF",
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
paddingTop: 2,
|
||||
paddingHorizontal: 4,
|
||||
fontSize: 12,
|
||||
},
|
||||
!item.chaptersDownloaded && {
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.chaptersUnread}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
);
|
||||
};
|
||||
|
||||
export default NovelCover;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// logo: {
|
||||
// height: 180,
|
||||
// },
|
||||
titleContainer: {
|
||||
flex: 1,
|
||||
justifyContent: "flex-end",
|
||||
borderRadius: 4,
|
||||
},
|
||||
title: {
|
||||
fontFamily: "pt-sans-bold",
|
||||
fontSize: 14,
|
||||
padding: 8,
|
||||
},
|
||||
|
||||
linearGradient: {
|
||||
bottom: -1,
|
||||
borderRadius: 4,
|
||||
},
|
||||
opac: {
|
||||
paddingHorizontal: 4.5,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 4,
|
||||
},
|
||||
extensionIcon: {
|
||||
width: 42,
|
||||
height: 42,
|
||||
borderRadius: 4,
|
||||
},
|
||||
listView: {
|
||||
flex: 1,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
borderRadius: 4,
|
||||
},
|
||||
});
|
||||
@@ -17,7 +17,8 @@ import {
|
||||
getChapter,
|
||||
downloadChapter,
|
||||
deleteChapter,
|
||||
} from "../../database/queries/ChapterQueries";
|
||||
} from "../../Database/queries/ChapterQueries";
|
||||
import { SET_LAST_READ } from "../preferences/preference.types";
|
||||
|
||||
export const getChapterAction = (
|
||||
extensionId,
|
||||
@@ -36,10 +37,24 @@ export const getChapterAction = (
|
||||
dispatch({ type: GET_CHAPTER, payload: chapter });
|
||||
};
|
||||
|
||||
export const markChapterReadAction = (chapterId) => async (dispatch) => {
|
||||
await markChapterRead(chapterId);
|
||||
dispatch({ type: CHAPTER_READ, payload: { chapterId } });
|
||||
};
|
||||
// export const markChapterReadAction = (chapterId, novelId) => async (
|
||||
// dispatch
|
||||
// ) => {
|
||||
// await markChapterRead(chapterId);
|
||||
|
||||
// console.log("DIspatchedbsjs");
|
||||
// dispatch({
|
||||
// type: CHAPTER_READ,
|
||||
// payload: { chapterId },
|
||||
// });
|
||||
|
||||
// dispatch({
|
||||
// type: SET_LAST_READ,
|
||||
// payload: { novelId, chapterId: chapterId + 1 },
|
||||
// });
|
||||
|
||||
// console.log("Set Last Read Diaptched", chapterId);
|
||||
// };
|
||||
|
||||
export const downloadChapterAction = (
|
||||
extensionId,
|
||||
|
||||
@@ -3,12 +3,15 @@ import {
|
||||
CLEAR_NOVEL_HISTORY,
|
||||
GET_HISTORY,
|
||||
LOAD_HISTORY,
|
||||
CLEAR_HISTORY,
|
||||
} from "./history.types";
|
||||
import {
|
||||
getHistory,
|
||||
insertHistory,
|
||||
deleteHistory,
|
||||
} from "../../database/queries/HistoryQueries";
|
||||
deleteAllHistory,
|
||||
} from "../../Database/queries/HistoryQueries";
|
||||
import { SET_LAST_READ } from "../preferences/preference.types";
|
||||
|
||||
export const getHistoryAction = () => async (dispatch) => {
|
||||
dispatch({ type: LOAD_HISTORY });
|
||||
@@ -24,6 +27,15 @@ export const insertHistoryAction = (novelId, chapterId) => async (dispatch) => {
|
||||
dispatch({
|
||||
type: UPDATE_NOVEL_HISTORY,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: SET_LAST_READ,
|
||||
payload: { novelId, chapterId },
|
||||
});
|
||||
|
||||
const history = await getHistory();
|
||||
|
||||
dispatch({ type: GET_HISTORY, payload: history });
|
||||
};
|
||||
|
||||
export const deleteHistoryAction = (novelId) => async (dispatch) => {
|
||||
@@ -34,3 +46,9 @@ export const deleteHistoryAction = (novelId) => async (dispatch) => {
|
||||
payload: { novelId },
|
||||
});
|
||||
};
|
||||
|
||||
export const clearAllHistoryAction = () => async (dispatch) => {
|
||||
deleteAllHistory();
|
||||
|
||||
dispatch({ type: CLEAR_HISTORY });
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
CLEAR_HISTORY,
|
||||
CLEAR_NOVEL_HISTORY,
|
||||
GET_HISTORY,
|
||||
LOAD_HISTORY,
|
||||
@@ -27,12 +28,11 @@ const historyReducer = (state = initialState, action) => {
|
||||
(item) => item.novelId !== payload.novelId
|
||||
),
|
||||
};
|
||||
// return {
|
||||
// ...state,
|
||||
// history: state.history.map((novel) =>
|
||||
// novel.novelUrl === payload.novelUrl ? payload : novel
|
||||
// ),
|
||||
// };
|
||||
case CLEAR_HISTORY:
|
||||
return {
|
||||
...state,
|
||||
history: [],
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -2,3 +2,4 @@ export const LOAD_HISTORY = "LOAD_HISTORY";
|
||||
export const GET_HISTORY = "GET_HISTORY";
|
||||
export const UPDATE_NOVEL_HISTORY = "UPDATE_NOVEL_HISTORY";
|
||||
export const CLEAR_NOVEL_HISTORY = "CLEAR_NOVEL_HISTORY";
|
||||
export const CLEAR_HISTORY = "CLEAR_HISTORY";
|
||||
|
||||
@@ -5,7 +5,8 @@ import {
|
||||
import {
|
||||
getLibrary,
|
||||
searchLibrary,
|
||||
} from "../../database/queries/LibraryQueries";
|
||||
} from "../../Database/queries/LibraryQueries";
|
||||
import { restoreBackup } from "../../Services/backup";
|
||||
|
||||
export const getLibraryAction = () => async (dispatch) => {
|
||||
const res = await getLibrary();
|
||||
@@ -24,3 +25,14 @@ export const searchLibraryAction = (searchText) => async (dispatch) => {
|
||||
payload: res,
|
||||
});
|
||||
};
|
||||
|
||||
export const restoreLibraryAction = () => async (dispatch) => {
|
||||
await restoreBackup();
|
||||
|
||||
const res = await getLibrary();
|
||||
|
||||
dispatch({
|
||||
type: GET_LIBRARY_NOVELS,
|
||||
payload: res,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -22,18 +22,6 @@ const libraryReducer = (state = initialState, action) => {
|
||||
return { ...state, novels: payload, loading: false };
|
||||
case GET_LIBRARY_SEARCH_RESULTS:
|
||||
return { ...state, novels: payload, loading: false };
|
||||
// case FOLLOW_NOVEL:
|
||||
// return {
|
||||
// ...state,
|
||||
// novels: [...state.novels, { ...payload, followed: 1 }],
|
||||
// };
|
||||
// case UNFOLLOW_NOVEL:
|
||||
// return {
|
||||
// ...state,
|
||||
// novels: state.novels.filter(
|
||||
// (novel) => novel.novelId !== payload
|
||||
// ),
|
||||
// };
|
||||
case UPDATE_IN_LIBRARY:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
CHAPTER_DOWNLOADED,
|
||||
CHAPTER_DELETED,
|
||||
UPDATE_NOVEL,
|
||||
SET_NOVEL_SETTINGS,
|
||||
} from "./novel.types";
|
||||
|
||||
import { updateNovel } from "../../Services/updates";
|
||||
@@ -22,7 +23,7 @@ import {
|
||||
followNovel,
|
||||
insertNovel,
|
||||
getNovel,
|
||||
} from "../../database/queries/NovelQueries";
|
||||
} from "../../Database/queries/NovelQueries";
|
||||
import {
|
||||
getChapters,
|
||||
insertChapters,
|
||||
@@ -31,8 +32,14 @@ import {
|
||||
getChapter,
|
||||
downloadChapter,
|
||||
deleteChapter,
|
||||
} from "../../database/queries/ChapterQueries";
|
||||
import { deleteNovelUpdates } from "../../database/queries/UpdateQueries";
|
||||
} from "../../Database/queries/ChapterQueries";
|
||||
import { deleteNovelUpdates } from "../../Database/queries/UpdateQueries";
|
||||
import {
|
||||
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";
|
||||
|
||||
export const setNovel = (novel) => async (dispatch) => {
|
||||
dispatch({ type: SET_NOVEL, payload: novel });
|
||||
@@ -58,7 +65,7 @@ export const getNovelAction = (
|
||||
} else {
|
||||
dispatch({ type: FETCHING_NOVEL });
|
||||
|
||||
const novel = await getNovel(novelUrl);
|
||||
const novel = await getNovel(sourceId, novelUrl);
|
||||
|
||||
if (novel) {
|
||||
novel.chapters = await getChapters(novel.novelId, sort, filter);
|
||||
@@ -67,21 +74,12 @@ export const getNovelAction = (
|
||||
payload: novel,
|
||||
});
|
||||
} else {
|
||||
/**
|
||||
* Fetch novel from source
|
||||
*/
|
||||
const fetchedNovel = await fetchNovel(sourceId, novelUrl);
|
||||
|
||||
/**
|
||||
* Insert novel in db
|
||||
*/
|
||||
const fetchedNovelId = await insertNovel(fetchedNovel);
|
||||
await insertChapters(fetchedNovelId, fetchedNovel.chapters);
|
||||
|
||||
/**
|
||||
* Get Novel from db
|
||||
*/
|
||||
const novel = await getNovel(novelUrl);
|
||||
const novel = await getNovel(sourceId, novelUrl);
|
||||
novel.chapters = await getChapters(novel.novelId);
|
||||
|
||||
dispatch({
|
||||
@@ -92,14 +90,20 @@ export const getNovelAction = (
|
||||
}
|
||||
};
|
||||
|
||||
export const sortAndFilterChapters = (novelUrl, filter, sort) => async (
|
||||
export const sortAndFilterChapters = (novelId, sort, filter) => async (
|
||||
dispatch
|
||||
) => {
|
||||
// const chapters = await getChaptersFromDb(novelUrl, filter, sort);
|
||||
// dispatch({
|
||||
// type: GET_CHAPTERS,
|
||||
// payload: chapters,
|
||||
// });
|
||||
const chapters = await getChapters(novelId, sort, filter);
|
||||
|
||||
dispatch({
|
||||
type: GET_CHAPTERS,
|
||||
payload: chapters,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: SET_CHAPTER_LIST_PREF,
|
||||
payload: { novelId, sort, filter },
|
||||
});
|
||||
};
|
||||
|
||||
export const followNovelAction = (novel) => async (dispatch) => {
|
||||
@@ -114,6 +118,13 @@ export const followNovelAction = (novel) => async (dispatch) => {
|
||||
payload: { novelUrl: novel.novelUrl, followed: !novel.followed },
|
||||
});
|
||||
|
||||
const res = await getLibrary();
|
||||
|
||||
dispatch({
|
||||
type: GET_LIBRARY_NOVELS,
|
||||
payload: res,
|
||||
});
|
||||
|
||||
ToastAndroid.show(
|
||||
!novel.followed ? "Added to library" : "Removed from library",
|
||||
ToastAndroid.SHORT
|
||||
@@ -139,9 +150,15 @@ export const getChapterAction = (
|
||||
dispatch({ type: GET_CHAPTER, payload: chapter });
|
||||
};
|
||||
|
||||
export const markChapterReadAction = (chapterId) => async (dispatch) => {
|
||||
export const markChapterReadAction = (chapterId, novelId) => async (
|
||||
dispatch
|
||||
) => {
|
||||
await markChapterRead(chapterId);
|
||||
dispatch({ type: CHAPTER_READ, payload: { chapterId } });
|
||||
dispatch({
|
||||
type: SET_LAST_READ,
|
||||
payload: { novelId, chapterId: chapterId + 1 },
|
||||
});
|
||||
};
|
||||
|
||||
export const downloadChapterAction = (
|
||||
@@ -223,14 +240,14 @@ export const deleteAllChaptersAction = (chapters) => async (dispatch) => {
|
||||
ToastAndroid.show(`Deleted all chapters`, ToastAndroid.SHORT);
|
||||
};
|
||||
|
||||
export const updateNovelAction = (extensionId, novelUrl, novelId) => async (
|
||||
export const updateNovelAction = (sourceId, novelUrl, novelId) => async (
|
||||
dispatch
|
||||
) => {
|
||||
dispatch({ type: FETCHING_NOVEL });
|
||||
|
||||
await updateNovel(extensionId, novelUrl, novelId);
|
||||
await updateNovel(sourceId, novelUrl, novelId);
|
||||
|
||||
let novel = await getNovel(novelUrl);
|
||||
let novel = await getNovel(sourceId, novelUrl);
|
||||
let chapters = await getChapters(novel.novelId);
|
||||
|
||||
dispatch({
|
||||
|
||||
@@ -91,7 +91,6 @@ const novelReducer = (state = initialState, action) => {
|
||||
case CHAPTER_DOWNLOADED:
|
||||
return {
|
||||
...state,
|
||||
// downloading: state.downloading.filter((id) => id === payload),
|
||||
chapters: state.chapters.map((chapter) =>
|
||||
chapter.chapterId === payload
|
||||
? { ...chapter, downloaded: 1 }
|
||||
@@ -107,7 +106,6 @@ const novelReducer = (state = initialState, action) => {
|
||||
: chapter
|
||||
),
|
||||
};
|
||||
|
||||
case UPDATE_LAST_READ:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -17,3 +17,5 @@ export const GET_CHAPTER = "GET_CHAPTER";
|
||||
|
||||
export const FOLLOW_NOVEL = "FOLLOW_NOVEL";
|
||||
export const UNFOLLOW_NOVEL = "UNFOLLOW_NOVEL";
|
||||
|
||||
export const SET_NOVEL_SETTINGS = "SET_NOVEL_SETTINGS";
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { SET_CHAPTER_LIST_PREF, SET_LAST_READ } from "./preference.types";
|
||||
|
||||
const initialState = {
|
||||
novelSettings: [],
|
||||
};
|
||||
|
||||
const novelReducer = (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
switch (type) {
|
||||
case SET_LAST_READ:
|
||||
return {
|
||||
...state,
|
||||
novelSettings: !state.novelSettings.some(
|
||||
(novel) => novel.novelId === payload.novelId
|
||||
)
|
||||
? [
|
||||
{
|
||||
novelId: payload.novelId,
|
||||
lastRead: payload.chapterId,
|
||||
},
|
||||
]
|
||||
: state.novelSettings.map((novel) =>
|
||||
novel.novelId === payload.novelId
|
||||
? { ...novel, lastRead: payload.chapterId }
|
||||
: novel
|
||||
),
|
||||
};
|
||||
case SET_CHAPTER_LIST_PREF:
|
||||
return {
|
||||
...state,
|
||||
novelSettings: state.novelSettings.some(
|
||||
(novel) => novel.novelId === payload.novelId
|
||||
)
|
||||
? state.novelSettings.map((novel) =>
|
||||
novel.novelId === payload.novelId
|
||||
? {
|
||||
...novel,
|
||||
sort: payload.sort,
|
||||
filter: payload.filter,
|
||||
}
|
||||
: novel
|
||||
)
|
||||
: [
|
||||
...state.novelSettings,
|
||||
{
|
||||
novelId: payload.novelId,
|
||||
sort: payload.sort,
|
||||
filter: payload.filter,
|
||||
},
|
||||
],
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default novelReducer;
|
||||
@@ -0,0 +1,2 @@
|
||||
export const SET_CHAPTER_LIST_PREF = "SET_CHAPTER_LIST_PREF";
|
||||
export const SET_LAST_READ = "SET_LAST_READ";
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
SWITCH_THEME,
|
||||
SET_DISPLAY_MODE,
|
||||
SET_ITEMS_PER_ROW,
|
||||
UPDATE_READER_TEXT_SIZE,
|
||||
@@ -8,6 +9,13 @@ import {
|
||||
SET_READER_FONT,
|
||||
} from "./settings.types";
|
||||
|
||||
export const switchTheme = (val) => (dispatch) => {
|
||||
dispatch({
|
||||
type: SWITCH_THEME,
|
||||
payload: val,
|
||||
});
|
||||
};
|
||||
|
||||
export const setDisplayMode = (val) => (dispatch) => {
|
||||
dispatch({
|
||||
type: SET_DISPLAY_MODE,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { SET_NOVEL_SETTINGS } from "../novel/novel.types";
|
||||
import {
|
||||
SWITCH_THEME,
|
||||
SET_DISPLAY_MODE,
|
||||
SET_ITEMS_PER_ROW,
|
||||
SET_READER_FONT,
|
||||
@@ -7,6 +9,21 @@ import {
|
||||
UPDATE_READER_TEXT_SIZE,
|
||||
UPDATE_READER_THEME,
|
||||
} from "./settings.types";
|
||||
import {
|
||||
amoledDarkTheme,
|
||||
lightTheme,
|
||||
darkTheme,
|
||||
midnightDuskTheme,
|
||||
limeTheme,
|
||||
} from "../../Theme/theme";
|
||||
|
||||
const themes = {
|
||||
0: amoledDarkTheme,
|
||||
1: lightTheme,
|
||||
2: darkTheme,
|
||||
3: midnightDuskTheme,
|
||||
4: limeTheme,
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
displayMode: 0,
|
||||
@@ -18,6 +35,7 @@ const initialState = {
|
||||
padding: 5,
|
||||
fontFamily: null,
|
||||
},
|
||||
theme: themes[2],
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -39,11 +57,12 @@ const settingsReducer = (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
switch (type) {
|
||||
case SWITCH_THEME:
|
||||
return { ...state, theme: themes[payload] };
|
||||
case SET_DISPLAY_MODE:
|
||||
return {
|
||||
...state,
|
||||
displayMode: payload,
|
||||
itemsPerRow: payload === 2 ? 1 : state.itemsPerRow,
|
||||
};
|
||||
case SET_ITEMS_PER_ROW:
|
||||
return {
|
||||
@@ -69,6 +88,8 @@ const settingsReducer = (state = initialState, action) => {
|
||||
...state,
|
||||
reader: { ...state.reader, fontFamily: payload },
|
||||
};
|
||||
case SET_NOVEL_SETTINGS:
|
||||
console.log(state.novelSettings);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export const SWITCH_THEME = "SWITCH_THEME";
|
||||
export const SET_DISPLAY_MODE = "SET_DISPLAY_MODE";
|
||||
export const SET_ITEMS_PER_ROW = "SET_ITEMS_PER_ROW";
|
||||
export const UPDATE_READER_THEME = "UPDATE_READER_THEME";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GET_SOURCES } from "./source.types";
|
||||
import { GET_SOURCES, PIN_SOURCES, SEARCH_SOURCES } from "./source.types";
|
||||
import { fetchSources } from "../../Services/Source/source";
|
||||
|
||||
export const getSourcesAction = () => async (dispatch) => {
|
||||
@@ -9,3 +9,17 @@ export const getSourcesAction = () => async (dispatch) => {
|
||||
payload: res,
|
||||
});
|
||||
};
|
||||
|
||||
export const searchSourcesAction = (searchText) => async (dispatch) => {
|
||||
dispatch({
|
||||
type: SEARCH_SOURCES,
|
||||
payload: searchText,
|
||||
});
|
||||
};
|
||||
|
||||
export const pinSourceAction = (sourceId) => async (dispatch) => {
|
||||
dispatch({
|
||||
type: PIN_SOURCES,
|
||||
payload: sourceId,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,19 +1,38 @@
|
||||
import { GET_SOURCES } from "./source.types";
|
||||
import { GET_SOURCES, SEARCH_SOURCES, PIN_SOURCES } from "./source.types";
|
||||
|
||||
const initialState = {
|
||||
extensions: [],
|
||||
sources: [],
|
||||
pinnedSources: [],
|
||||
search: [],
|
||||
pinned: [],
|
||||
loading: true,
|
||||
};
|
||||
|
||||
const libraryReducer = (state = initialState, action) => {
|
||||
const sourceReducer = (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
switch (type) {
|
||||
case GET_SOURCES:
|
||||
return { extensions: payload, loading: false };
|
||||
return { ...state, sources: payload, loading: false };
|
||||
case PIN_SOURCES:
|
||||
return {
|
||||
...state,
|
||||
pinned:
|
||||
state.pinned.indexOf(payload) === -1
|
||||
? [...state.pinned, payload]
|
||||
: state.pinned.filter((source) => source !== payload),
|
||||
loading: false,
|
||||
};
|
||||
case SEARCH_SOURCES:
|
||||
return {
|
||||
...state,
|
||||
search: state.sources.filter((source) =>
|
||||
source.sourceName.includes(payload)
|
||||
),
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default libraryReducer;
|
||||
export default sourceReducer;
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export const GET_SOURCES = "GET_SOURCES";
|
||||
export const SEARCH_SOURCES = "SEARCH_SOURCES";
|
||||
export const PIN_SOURCES = "PIN_SOURCES";
|
||||
|
||||
+4
-4
@@ -3,14 +3,14 @@ import { persistStore, persistReducer } from "redux-persist";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import thunk from "redux-thunk";
|
||||
|
||||
import themeReducer from "./theme/theme.reducer";
|
||||
import settingsReducer from "./settings/settings.reducer";
|
||||
import libraryReducer from "./library/library.reducer";
|
||||
import extensionReducer from "./source/source.reducers";
|
||||
import sourceReducer from "./source/source.reducers";
|
||||
import novelReducer from "./novel/novel.reducer";
|
||||
import historyReducer from "./history/history.reducer";
|
||||
import updatesReducer from "./updates/updates.reducer";
|
||||
import trackerReducer from "./tracker/tracker.reducer";
|
||||
import preferenceReducer from "./preferences/preference.reducer";
|
||||
|
||||
const persistConfig = {
|
||||
key: "root",
|
||||
@@ -21,14 +21,14 @@ const persistConfig = {
|
||||
const persistedReducer = persistReducer(
|
||||
persistConfig,
|
||||
combineReducers({
|
||||
themeReducer,
|
||||
settingsReducer,
|
||||
libraryReducer,
|
||||
extensionReducer,
|
||||
sourceReducer,
|
||||
novelReducer,
|
||||
historyReducer,
|
||||
updatesReducer,
|
||||
trackerReducer,
|
||||
preferenceReducer,
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { SWITCH_THEME } from "./theme.types";
|
||||
|
||||
export const switchTheme = (val) => (dispatch) => {
|
||||
dispatch({
|
||||
type: SWITCH_THEME,
|
||||
payload: val,
|
||||
});
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user