Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d32cb41475 | |||
| 5bb3be576e | |||
| 2f09b7e29e | |||
| b672aba6ff | |||
| 80363d6016 | |||
| 195fcaf9b2 | |||
| 5ec08b3add | |||
| 625cdff212 | |||
| 83afe9c254 | |||
| 404f59323a | |||
| 20ad556039 | |||
| 14c994486c | |||
| c6884458a1 | |||
| 9dab2eccaa | |||
| 2e21a9fd33 | |||
| 781e8df740 | |||
| fa4d810ccb | |||
| 37f531dd93 | |||
| 1ea98ec031 | |||
| 65e5377345 | |||
| 2b78350ad9 | |||
| 69217b415d | |||
| 8c04ee3d33 | |||
| 1f78188fd1 | |||
| 5fb9d70036 | |||
| b3ec043e32 |
@@ -9,7 +9,7 @@ import Router from "./src/navigation/Router";
|
||||
import { createTables, createIndexes } from "./src/services/db";
|
||||
import { setStatusBar } from "./src/services/asyncStorage";
|
||||
|
||||
// Redyx
|
||||
// Redux
|
||||
import { Provider } from "react-redux";
|
||||
import { persistor, store } from "./src/redux/store/configureStore";
|
||||
import { PersistGate } from "redux-persist/lib/integration/react";
|
||||
|
||||
Generated
+5
@@ -7270,6 +7270,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"redux-devtools-extension": {
|
||||
"version": "2.13.9",
|
||||
"resolved": "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz",
|
||||
"integrity": "sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A=="
|
||||
},
|
||||
"redux-persist": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"react-native-webview": "11.0.0",
|
||||
"react-redux": "^7.2.2",
|
||||
"redux": "^4.0.5",
|
||||
"redux-devtools-extension": "^2.13.9",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"rn-sliding-up-panel": "^2.4.4"
|
||||
|
||||
@@ -1,19 +1,136 @@
|
||||
import React from "react";
|
||||
|
||||
import { Appbar } from "react-native-paper";
|
||||
import React, { useRef } from "react";
|
||||
import { View, TextInput, StyleSheet } from "react-native";
|
||||
import {
|
||||
TouchableRipple,
|
||||
IconButton,
|
||||
Appbar as MaterialAppbar,
|
||||
} from "react-native-paper";
|
||||
import Constants from "expo-constants";
|
||||
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
export const CustomAppbar = ({ title, onBackAction }) => {
|
||||
export const Appbar = ({ title, onBackAction }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
return (
|
||||
<Appbar.Header style={{ backgroundColor: theme.colorDarkPrimary }}>
|
||||
{onBackAction && <Appbar.BackAction onPress={onBackAction} />}
|
||||
<Appbar.Content
|
||||
<MaterialAppbar.Header style={{ backgroundColor: theme.colorPrimary }}>
|
||||
{onBackAction && (
|
||||
<MaterialAppbar.BackAction onPress={onBackAction} />
|
||||
)}
|
||||
<MaterialAppbar.Content
|
||||
title={title}
|
||||
titleStyle={{ color: theme.textColorPrimaryDark }}
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
/>
|
||||
</Appbar.Header>
|
||||
</MaterialAppbar.Header>
|
||||
);
|
||||
};
|
||||
|
||||
export const SearchAppbar = ({
|
||||
placeholder,
|
||||
getSearchResults,
|
||||
setSearchText,
|
||||
searchText,
|
||||
getNovels,
|
||||
setLoading,
|
||||
screen,
|
||||
onFilter,
|
||||
}) => {
|
||||
const searchRef = useRef(null);
|
||||
|
||||
const navigation = useNavigation();
|
||||
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
borderless
|
||||
onPress={() => searchRef.current.focus()}
|
||||
style={[
|
||||
styles.searchAppbarContainer,
|
||||
{ backgroundColor: theme.searchBarColor },
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<View style={{ flex: 1, flexDirection: "row" }}>
|
||||
<IconButton
|
||||
icon={screen === "Library" ? "magnify" : "arrow-left"}
|
||||
color={theme.textColorSecondary}
|
||||
style={{ marginLeft: 0 }}
|
||||
size={23}
|
||||
onPress={() => {
|
||||
if (screen === "Extension") {
|
||||
navigation.goBack();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
ref={searchRef}
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: theme.textColorSecondary,
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
placeholderTextColor={theme.textColorSecondary}
|
||||
onChangeText={(text) => {
|
||||
setSearchText(text);
|
||||
if (screen === "Library") {
|
||||
getSearchResults(text);
|
||||
}
|
||||
}}
|
||||
onSubmitEditing={() => {
|
||||
if (screen === "Extension") {
|
||||
getSearchResults(searchText);
|
||||
}
|
||||
}}
|
||||
defaultValue={searchText}
|
||||
/>
|
||||
</View>
|
||||
{searchText !== "" && (
|
||||
<IconButton
|
||||
icon="close"
|
||||
color={theme.textColorSecondary}
|
||||
style={{ marginRight: 0 }}
|
||||
size={23}
|
||||
onPress={() => {
|
||||
setLoading?.(true);
|
||||
getNovels();
|
||||
setSearchText("");
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<IconButton
|
||||
icon="filter-variant"
|
||||
color={theme.textColorSecondary}
|
||||
style={{ marginRight: 0 }}
|
||||
size={23}
|
||||
disabled={typeof onFilter === "function" ? false : true}
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
onPress={() => onFilter?.()}
|
||||
/>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
searchAppbarContainer: {
|
||||
marginTop: Constants.statusBarHeight + 8,
|
||||
height: 48,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 16,
|
||||
marginBottom: 12,
|
||||
borderRadius: 8,
|
||||
marginHorizontal: 12,
|
||||
elevation: 2,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const BottomSheetHandle = () => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: theme.textColorHintDark,
|
||||
height: 5,
|
||||
width: 30,
|
||||
borderRadius: 50,
|
||||
top: 10,
|
||||
alignSelf: "center",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BottomSheetHandle;
|
||||
@@ -0,0 +1,52 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const EmptyView = ({ icon, description }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
return (
|
||||
<View style={styles.emptyViewContainer}>
|
||||
<Text
|
||||
style={[
|
||||
styles.emptyViewIcon,
|
||||
{
|
||||
color: theme.textColorSecondary,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{icon}
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.emptyViewText,
|
||||
{
|
||||
color: theme.textColorSecondary,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{description}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmptyView;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
emptyViewContainer: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
emptyViewIcon: {
|
||||
fontSize: 45,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
emptyViewText: {
|
||||
fontWeight: "bold",
|
||||
marginTop: 10,
|
||||
textAlign: "center",
|
||||
paddingHorizontal: 30,
|
||||
},
|
||||
});
|
||||
@@ -1,38 +1,42 @@
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import { StyleSheet, View, Text, ImageBackground } from "react-native";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import { TouchableRipple } from "react-native-paper";
|
||||
import { theme } from "../../theme/theme";
|
||||
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const NovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
const [mode, setMode] = useState();
|
||||
AsyncStorage.getItem("@display_mode").then((value) => setMode(value));
|
||||
const displayMode = useSelector(
|
||||
(state) => state.settingsReducer.displayMode
|
||||
);
|
||||
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
// const itemsPerRow = useSelector(
|
||||
// (state) => state.settingsReducer.itemsPerRow
|
||||
// );
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 / 3 }}>
|
||||
<TouchableRipple
|
||||
borderless
|
||||
centered
|
||||
rippleColor={theme.rippleColorDark}
|
||||
rippleColor={theme.rippleColor}
|
||||
style={styles.opac}
|
||||
onPress={onPress}
|
||||
>
|
||||
<>
|
||||
{item.novelCover ? (
|
||||
{item.novelCover && (
|
||||
<ImageBackground
|
||||
source={{
|
||||
uri: item.novelCover,
|
||||
}}
|
||||
source={{ uri: item.novelCover }}
|
||||
style={styles.logo}
|
||||
imageStyle={[
|
||||
{ borderRadius: 4 },
|
||||
libraryStatus && { opacity: 0.4 },
|
||||
libraryStatus && { opacity: 0.5 },
|
||||
]}
|
||||
progressiveRenderingEnabled={true}
|
||||
>
|
||||
{(!mode || mode === "compact") && (
|
||||
{displayMode === 0 && (
|
||||
<View style={styles.titleContainer}>
|
||||
<LinearGradient
|
||||
colors={[
|
||||
@@ -43,7 +47,13 @@ const NovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={styles.title}
|
||||
style={[
|
||||
styles.title,
|
||||
{
|
||||
color:
|
||||
"rgba(255,255,255,1)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
@@ -51,13 +61,18 @@ const NovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
</View>
|
||||
)}
|
||||
</ImageBackground>
|
||||
) : (
|
||||
<View>
|
||||
<Text style={{ color: "white" }}>HAHAHAHAH</Text>
|
||||
</View>
|
||||
)}
|
||||
{mode === "comfortable" && (
|
||||
<Text numberOfLines={2} style={styles.title}>
|
||||
{displayMode === 1 && (
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.title,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
padding: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
)}
|
||||
@@ -71,8 +86,7 @@ export default NovelCover;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
logo: {
|
||||
height: 173,
|
||||
borderRadius: 4,
|
||||
height: 180,
|
||||
},
|
||||
titleContainer: {
|
||||
flex: 1,
|
||||
@@ -82,18 +96,16 @@ const styles = StyleSheet.create({
|
||||
title: {
|
||||
fontFamily: "pt-sans-bold",
|
||||
fontSize: 14,
|
||||
color: "white",
|
||||
padding: 8,
|
||||
textShadowColor: "rgba(0, 0, 0, 0.75)",
|
||||
textShadowOffset: { width: -1, height: 1 },
|
||||
textShadowRadius: 10,
|
||||
},
|
||||
|
||||
linearGradient: {
|
||||
bottom: -1,
|
||||
borderRadius: 4,
|
||||
},
|
||||
opac: {
|
||||
paddingHorizontal: 3.5,
|
||||
paddingVertical: 3.5,
|
||||
paddingHorizontal: 4.5,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 4,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from "react";
|
||||
import { TextInput, StyleSheet } from "react-native";
|
||||
import { theme } from "../../theme/theme";
|
||||
|
||||
const SearchBar = ({ searchText, onChangeText, onSubmitEditing }) => (
|
||||
<TextInput
|
||||
autoFocus
|
||||
placeholder="Search..."
|
||||
defaultValue={searchText}
|
||||
style={styles.searchBar}
|
||||
placeholderTextColor={theme.textColorHintDark}
|
||||
blurOnSubmit={true}
|
||||
onChangeText={onChangeText}
|
||||
onSubmitEditing={onSubmitEditing}
|
||||
/>
|
||||
);
|
||||
|
||||
export default SearchBar;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
searchBar: {
|
||||
flex: 1,
|
||||
fontSize: 18,
|
||||
color: "white",
|
||||
marginLeft: 15,
|
||||
},
|
||||
});
|
||||
@@ -1,17 +1,22 @@
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import { Checkbox } from "react-native-paper";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
export const DisplayCheckbox = ({ displayMode, onPress, value }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
const label = {
|
||||
0: "Compact",
|
||||
1: "Comfortable",
|
||||
};
|
||||
|
||||
return (
|
||||
<Checkbox.Item
|
||||
label={value.charAt(0).toUpperCase() + value.slice(1)}
|
||||
labelStyle={{ color: theme.textColorPrimaryDark }}
|
||||
label={label[value]}
|
||||
labelStyle={{ color: theme.textColorPrimary }}
|
||||
status={displayMode === value ? "checked" : "unchecked"}
|
||||
mode="ios"
|
||||
uncheckedColor={theme.textColorSecondaryDark}
|
||||
uncheckedColor={theme.textColorSecondary}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={onPress}
|
||||
/>
|
||||
@@ -24,10 +29,10 @@ export const ThemeCheckbox = ({ onPress, label, checked }) => {
|
||||
return (
|
||||
<Checkbox.Item
|
||||
label={label}
|
||||
labelStyle={{ color: theme.textColorPrimaryDark }}
|
||||
labelStyle={{ color: theme.textColorPrimary }}
|
||||
status={checked ? "checked" : "unchecked"}
|
||||
mode="ios"
|
||||
uncheckedColor={theme.textColorSecondaryDark}
|
||||
uncheckedColor={theme.textColorSecondary}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={onPress}
|
||||
/>
|
||||
|
||||
+37
-34
@@ -4,23 +4,24 @@ import {
|
||||
TransitionPresets,
|
||||
} from "@react-navigation/stack";
|
||||
import { createMaterialBottomTabNavigator } from "@react-navigation/material-bottom-tabs";
|
||||
import { Provider } from "react-native-paper";
|
||||
|
||||
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
|
||||
import Library from "../screens/Library";
|
||||
import History from "../screens/history/History";
|
||||
import Browse from "../screens/Browse";
|
||||
import Library from "../screens/Library/Library";
|
||||
import History from "../screens/History/History";
|
||||
import Browse from "../screens/Browse/Browse";
|
||||
|
||||
import More from "../screens/more/More";
|
||||
import About from "../screens/more/About";
|
||||
import Settings from "../screens/more/Settings";
|
||||
import More from "../screens/More/More";
|
||||
import About from "../screens/More/About";
|
||||
import Settings from "../screens/More/Settings";
|
||||
|
||||
import NovelItem from "../screens/NovelItem";
|
||||
import ChapterItem from "../screens/ChapterItem";
|
||||
import NovelItem from "../screens/Novel/Novel";
|
||||
import ChapterItem from "../screens/Chapter/Chapter";
|
||||
|
||||
// Extensions
|
||||
import BoxNovel from "../screens/extensions/boxnovel/BoxNovel";
|
||||
import ReadLightNovel from "../screens/extensions/readlightnovel/ReadLightNovel";
|
||||
import BoxNovel from "../screens/Extensions/boxnovel/BoxNovel";
|
||||
import ReadLightNovel from "../screens/Extensions/readlightnovel/ReadLightNovel";
|
||||
|
||||
const Stack = createStackNavigator();
|
||||
|
||||
@@ -64,7 +65,7 @@ const BottomNavigator = () => {
|
||||
return (
|
||||
<Tab.Navigator
|
||||
shifting={false}
|
||||
barStyle={{ backgroundColor: theme.colorDarkPrimary }}
|
||||
barStyle={{ backgroundColor: theme.colorPrimary }}
|
||||
activeColor={theme.colorAccentDark}
|
||||
>
|
||||
<Tab.Screen
|
||||
@@ -139,29 +140,31 @@ const BottomNavigator = () => {
|
||||
|
||||
const Router = () => {
|
||||
return (
|
||||
<Stack.Navigator screenOptions={stackNavigatorConfig}>
|
||||
<Stack.Screen name="Router" component={BottomNavigator} />
|
||||
<Stack.Screen
|
||||
name="ChapterItem"
|
||||
component={ChapterItem}
|
||||
options={{ ...TransitionPresets.SlideFromRightIOS }}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="NovelItem"
|
||||
component={NovelItem}
|
||||
options={{
|
||||
headerTitle: "",
|
||||
headerShown: true,
|
||||
headerTransparent: true,
|
||||
headerTintColor: "white",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen name="BoxNovelStack" component={BoxNovelStack} />
|
||||
<Stack.Screen
|
||||
name="ReadLightNovelStack"
|
||||
component={ReadLightNovelStack}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
<Provider>
|
||||
<Stack.Navigator screenOptions={stackNavigatorConfig}>
|
||||
<Stack.Screen name="Router" component={BottomNavigator} />
|
||||
<Stack.Screen
|
||||
name="ChapterItem"
|
||||
component={ChapterItem}
|
||||
options={{ ...TransitionPresets.SlideFromRightIOS }}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="NovelItem"
|
||||
component={NovelItem}
|
||||
options={{
|
||||
headerTitle: "",
|
||||
headerShown: true,
|
||||
headerTransparent: true,
|
||||
headerTintColor: "white",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen name="BoxNovelStack" component={BoxNovelStack} />
|
||||
<Stack.Screen
|
||||
name="ReadLightNovelStack"
|
||||
component={ReadLightNovelStack}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { GET_EXTENSIONS } from "./types";
|
||||
import { fetchExtensionList } from "../../services/api";
|
||||
|
||||
export const getExtensions = () => async (dispatch) => {
|
||||
const res = await fetchExtensionList();
|
||||
|
||||
dispatch({
|
||||
type: GET_EXTENSIONS,
|
||||
payload: res,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
UPDATE_NOVEL_HISTORY,
|
||||
CLEAR_NOVEL_HISTORY,
|
||||
GET_HISTORY,
|
||||
LOAD_HISTORY,
|
||||
} from "./types";
|
||||
import {
|
||||
getHistoryFromDb,
|
||||
insertIntoHistory,
|
||||
deleteChapterHistory,
|
||||
} from "../../services/db";
|
||||
|
||||
export const getHistory = () => async (dispatch) => {
|
||||
dispatch({ type: LOAD_HISTORY });
|
||||
|
||||
const history = await getHistoryFromDb();
|
||||
|
||||
dispatch({ type: GET_HISTORY, payload: history });
|
||||
};
|
||||
|
||||
export const updateNovelHistory = (chapterUrl, chapterName, novelUrl) => async (
|
||||
dispatch
|
||||
) => {
|
||||
await insertIntoHistory(chapterUrl, chapterName, novelUrl);
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_NOVEL_HISTORY,
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteChapterFromHistory = (novelUrl) => async (dispatch) => {
|
||||
await deleteChapterHistory(novelUrl);
|
||||
|
||||
dispatch({
|
||||
type: CLEAR_NOVEL_HISTORY,
|
||||
payload: { novelUrl },
|
||||
});
|
||||
};
|
||||
@@ -1,11 +1,20 @@
|
||||
import { GET_LIBRARY_NOVELS } from "./types";
|
||||
import { getLibraryFromDb } from "../../services/db";
|
||||
import { getLibraryFromDb, searchInLibrary } from "../../services/db";
|
||||
|
||||
export const getLibraryNovels = () => async (dispatch) => {
|
||||
getLibraryFromDb().then((res) => console.log(res));
|
||||
const res = await getLibraryFromDb();
|
||||
|
||||
dispatch({
|
||||
type: GET_LIBRARY_NOVELS,
|
||||
payload: res.data,
|
||||
payload: res,
|
||||
});
|
||||
};
|
||||
|
||||
export const searchLibraryNovels = (searchText) => async (dispatch) => {
|
||||
const res = await searchInLibrary(searchText);
|
||||
|
||||
dispatch({
|
||||
type: GET_LIBRARY_NOVELS,
|
||||
payload: res,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import { ToastAndroid } from "react-native";
|
||||
|
||||
import {
|
||||
LOADING_NOVEL,
|
||||
GET_NOVEL,
|
||||
GET_CHAPTERS,
|
||||
FETCHING_NOVEL,
|
||||
SET_NOVEL,
|
||||
UPDATE_IN_LIBRARY,
|
||||
CHAPTER_READ,
|
||||
GET_CHAPTER,
|
||||
CHAPTER_LOADING,
|
||||
CHAPTER_DOWNLOADING,
|
||||
CHAPTER_DOWNLOADED,
|
||||
CHAPTER_DELETED,
|
||||
} from "./types";
|
||||
import {
|
||||
checkNovelInDb,
|
||||
getChaptersFromDb,
|
||||
getNovelInfoFromDb,
|
||||
insertChaptersInDb,
|
||||
insertNovelInfoInDb,
|
||||
toggleFavourite,
|
||||
downloadChapterFromSource,
|
||||
deleteChapterFromDb,
|
||||
chapterRead,
|
||||
isChapterDownloaded,
|
||||
getChapterFromDb,
|
||||
} from "../../services/db";
|
||||
import {
|
||||
fetchChapterFromSource,
|
||||
fetchChaptersFromSource,
|
||||
fetchNovelFromSource,
|
||||
} from "../../services/api";
|
||||
|
||||
export const setNovel = (novel) => async (dispatch) => {
|
||||
dispatch({ type: SET_NOVEL, payload: novel });
|
||||
};
|
||||
|
||||
export const getNovel = (inLibrary, extensionId, novelUrl) => async (
|
||||
dispatch
|
||||
) => {
|
||||
dispatch({ type: LOADING_NOVEL });
|
||||
|
||||
if (inLibrary === 1) {
|
||||
const chapters = await getChaptersFromDb(novelUrl, "", "");
|
||||
dispatch({
|
||||
type: GET_CHAPTERS,
|
||||
payload: chapters,
|
||||
});
|
||||
} else {
|
||||
dispatch({ type: FETCHING_NOVEL });
|
||||
|
||||
const inCache = await checkNovelInDb(novelUrl);
|
||||
|
||||
if (inCache) {
|
||||
const novel = await getNovelInfoFromDb(novelUrl);
|
||||
const chapters = await getChaptersFromDb(novelUrl, "", "");
|
||||
|
||||
dispatch({
|
||||
type: GET_NOVEL,
|
||||
payload: { novel, chapters },
|
||||
});
|
||||
} else {
|
||||
const novel = await fetchNovelFromSource(extensionId, novelUrl);
|
||||
const chapters = await fetchChaptersFromSource(
|
||||
extensionId,
|
||||
novelUrl
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: GET_NOVEL,
|
||||
payload: { novel, chapters },
|
||||
});
|
||||
|
||||
insertNovelInfoInDb(novel);
|
||||
insertChaptersInDb(novelUrl, chapters);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const sortAndFilterChapters = (novelUrl, filter, sort) => async (
|
||||
dispatch
|
||||
) => {
|
||||
const chapters = await getChaptersFromDb(novelUrl, filter, sort);
|
||||
dispatch({
|
||||
type: GET_CHAPTERS,
|
||||
payload: chapters,
|
||||
});
|
||||
};
|
||||
|
||||
export const insertNovelInLibrary = (inLibrary, novelUrl) => async (
|
||||
dispatch
|
||||
) => {
|
||||
toggleFavourite(inLibrary, novelUrl);
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_IN_LIBRARY,
|
||||
payload: !inLibrary,
|
||||
});
|
||||
|
||||
ToastAndroid.show(
|
||||
!inLibrary ? "Added to library" : "Removed from library",
|
||||
ToastAndroid.SHORT
|
||||
);
|
||||
};
|
||||
|
||||
export const getChapter = (extensionId, chapterUrl, novelUrl) => async (
|
||||
dispatch
|
||||
) => {
|
||||
dispatch({ type: CHAPTER_LOADING });
|
||||
const isDownloaded = await isChapterDownloaded(chapterUrl, novelUrl);
|
||||
let chapter;
|
||||
|
||||
if (isDownloaded) {
|
||||
chapter = await getChapterFromDb(chapterUrl, novelUrl);
|
||||
} else {
|
||||
chapter = await fetchChapterFromSource(
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterUrl
|
||||
);
|
||||
}
|
||||
dispatch({ type: GET_CHAPTER, payload: chapter });
|
||||
};
|
||||
|
||||
export const updateChapterRead = (chapterUrl, novelUrl) => async (dispatch) => {
|
||||
await chapterRead(chapterUrl, novelUrl);
|
||||
|
||||
dispatch({ type: CHAPTER_READ, payload: { chapterUrl, novelUrl } });
|
||||
};
|
||||
|
||||
// export const downloadChapter = (
|
||||
// extensionId,
|
||||
// novelUrl,
|
||||
// chapterUrl,
|
||||
// chapterName
|
||||
// ) => async (dispatch) => {
|
||||
// dispatch({
|
||||
// type: CHAPTER_DOWNLOADING,
|
||||
// payload: {
|
||||
// extensionId,
|
||||
// novelUrl,
|
||||
// chapterUrl,
|
||||
// },
|
||||
// });
|
||||
|
||||
// await downloadChapterFromSource(extensionId, novelUrl, chapterUrl);
|
||||
|
||||
// dispatch({
|
||||
// type: CHAPTER_DOWNLOADED,
|
||||
// payload: { extensionId, novelUrl, chapterUrl },
|
||||
// });
|
||||
|
||||
// ToastAndroid.show(`Downloaded ${chapterName}`, ToastAndroid.SHORT);
|
||||
// };
|
||||
|
||||
// export const deleteChapter = (
|
||||
// extensionId,
|
||||
// novelUrl,
|
||||
// chapterUrl,
|
||||
// chapterName
|
||||
// ) => (dispatch) => {
|
||||
// deleteChapterFromDb(extensionId, novelUrl, chapterUrl);
|
||||
|
||||
// dispatch({
|
||||
// type: CHAPTER_DELETED,
|
||||
// payload: { extensionId, novelUrl, chapterUrl },
|
||||
// });
|
||||
|
||||
// ToastAndroid.show(`Deleted ${chapterName}`, ToastAndroid.SHORT);
|
||||
// };
|
||||
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
SET_DISPLAY_MODE,
|
||||
SET_ITEMS_PER_ROW,
|
||||
UPDATE_READER_TEXT_SIZE,
|
||||
UPDATE_READER_THEME,
|
||||
} from "./types";
|
||||
|
||||
export const setDisplayMode = (val) => (dispatch) => {
|
||||
dispatch({
|
||||
type: SET_DISPLAY_MODE,
|
||||
payload: val,
|
||||
});
|
||||
};
|
||||
|
||||
export const setItemsPerRow = (val) => (dispatch) => {
|
||||
dispatch({
|
||||
type: SET_ITEMS_PER_ROW,
|
||||
payload: val,
|
||||
});
|
||||
};
|
||||
|
||||
export const updateReaderTheme = (val) => (dispatch) => {
|
||||
dispatch({
|
||||
type: UPDATE_READER_THEME,
|
||||
payload: val,
|
||||
});
|
||||
};
|
||||
|
||||
export const updateReaderTextSize = (val) => (dispatch) => {
|
||||
dispatch({
|
||||
type: UPDATE_READER_TEXT_SIZE,
|
||||
payload: val,
|
||||
});
|
||||
};
|
||||
@@ -1,8 +1,15 @@
|
||||
import { SWITCH_THEME } from "./types";
|
||||
|
||||
export const switchTheme = (theme) => (dispatch) => {
|
||||
/**
|
||||
* 0 -> Amoled Dark Theme
|
||||
* 1 -> Light Theme
|
||||
* 2 -> Dark Theme
|
||||
* 3 -> Midnight Dusk Theme
|
||||
*/
|
||||
|
||||
export const switchTheme = (val) => (dispatch) => {
|
||||
dispatch({
|
||||
type: SWITCH_THEME,
|
||||
payload: theme,
|
||||
payload: val,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,3 +1,47 @@
|
||||
export const SWITCH_THEME = "SWITCH_THEME";
|
||||
|
||||
export const GET_LIBRARY_NOVELS = "GET_LIBRARY_NOVELS";
|
||||
|
||||
export const SET_DISPLAY_MODE = "SET_DISPLAY_MODE";
|
||||
|
||||
export const SET_ITEMS_PER_ROW = "SET_ITEMS_PER_ROW";
|
||||
|
||||
export const GET_EXTENSIONS = "GET_EXTENSIONS";
|
||||
|
||||
export const LOADING_NOVEL = "LOADING_NOVEL";
|
||||
|
||||
export const FETCHING_NOVEL = "FETCHING_NOVEL";
|
||||
|
||||
export const LOADING_CHAPTERS = "LOADING_CHAPTERS";
|
||||
|
||||
export const SET_NOVEL = "SET_NOVEL";
|
||||
|
||||
export const GET_NOVEL = "GET_NOVEL";
|
||||
|
||||
export const UPDATE_IN_LIBRARY = "UPDATE_IN_LIBRARY";
|
||||
|
||||
export const GET_CHAPTERS = "GET_CHAPTERS";
|
||||
|
||||
export const CHAPTER_DOWNLOADED = "CHAPTER_DOWNLOADED";
|
||||
|
||||
export const CHAPTER_DELETED = "CHAPTER_DELETED";
|
||||
|
||||
export const CHAPTER_DOWNLOADING = "CHAPTER_DOWNLOADING";
|
||||
|
||||
export const LOAD_HISTORY = "LOAD_HISTORY";
|
||||
|
||||
export const UPDATE_NOVEL_HISTORY = "UPDATE_NOVEL_HISTORY";
|
||||
|
||||
export const CLEAR_NOVEL_HISTORY = "CLEAR_NOVEL_HISTORY";
|
||||
|
||||
export const GET_HISTORY = "GET_HISTORY";
|
||||
|
||||
export const CHAPTER_READ = "CHAPTER_READ";
|
||||
|
||||
export const GET_CHAPTER = "GET_CHAPTER";
|
||||
|
||||
export const CHAPTER_LOADING = "CHAPTER_LOADING";
|
||||
|
||||
export const UPDATE_READER_THEME = "UPDATE_READER_THEME";
|
||||
|
||||
export const UPDATE_READER_TEXT_SIZE = "UPDATE_READER_TEXT_SIZE";
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { GET_EXTENSIONS } from "../actions/types";
|
||||
|
||||
const initialState = {
|
||||
extensions: [],
|
||||
loading: true,
|
||||
};
|
||||
|
||||
const libraryReducer = (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
switch (type) {
|
||||
case GET_EXTENSIONS:
|
||||
return { extensions: payload, loading: false };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default libraryReducer;
|
||||
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
CLEAR_NOVEL_HISTORY,
|
||||
GET_HISTORY,
|
||||
LOAD_HISTORY,
|
||||
UPDATE_NOVEL_HISTORY,
|
||||
} from "../actions/types";
|
||||
|
||||
const initialState = {
|
||||
history: [],
|
||||
loading: true,
|
||||
};
|
||||
|
||||
const historyReducer = (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
switch (type) {
|
||||
case LOAD_HISTORY:
|
||||
return { ...state, loading: true };
|
||||
case GET_HISTORY:
|
||||
return { ...state, history: payload, loading: false };
|
||||
case UPDATE_NOVEL_HISTORY:
|
||||
return state;
|
||||
case CLEAR_NOVEL_HISTORY:
|
||||
return {
|
||||
...state,
|
||||
history: state.history.filter(
|
||||
(item) => item.novelUrl !== payload.novelUrl
|
||||
),
|
||||
};
|
||||
// return {
|
||||
// ...state,
|
||||
// history: state.history.map((novel) =>
|
||||
// novel.novelUrl === payload.novelUrl ? payload : novel
|
||||
// ),
|
||||
// };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default historyReducer;
|
||||
@@ -0,0 +1,108 @@
|
||||
import {
|
||||
CHAPTER_DOWNLOADING,
|
||||
CHAPTER_DOWNLOADED,
|
||||
CHAPTER_DELETED,
|
||||
GET_CHAPTERS,
|
||||
LOADING_NOVEL,
|
||||
GET_NOVEL,
|
||||
GET_CHAPTER,
|
||||
FETCHING_NOVEL,
|
||||
SET_NOVEL,
|
||||
UPDATE_IN_LIBRARY,
|
||||
CHAPTER_READ,
|
||||
CHAPTER_LOADING,
|
||||
} from "../actions/types";
|
||||
|
||||
const initialState = {
|
||||
novel: null,
|
||||
chapters: [],
|
||||
chapter: null,
|
||||
chapterLoading: true,
|
||||
loading: true,
|
||||
fetching: false,
|
||||
};
|
||||
|
||||
const novelReducer = (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
switch (type) {
|
||||
case LOADING_NOVEL:
|
||||
return { ...state, loading: true };
|
||||
case FETCHING_NOVEL:
|
||||
return { ...state, loading: true, fetching: true };
|
||||
case SET_NOVEL:
|
||||
return { ...state, novel: payload };
|
||||
case GET_NOVEL:
|
||||
return {
|
||||
...state,
|
||||
novel: payload.novel,
|
||||
chapters: payload.chapters,
|
||||
loading: false,
|
||||
fetching: false,
|
||||
};
|
||||
case GET_CHAPTERS:
|
||||
return {
|
||||
...state,
|
||||
chapters: payload,
|
||||
loading: false,
|
||||
fetching: false,
|
||||
};
|
||||
case UPDATE_IN_LIBRARY:
|
||||
return {
|
||||
...state,
|
||||
novel: { ...state.novel, libraryStatus: payload },
|
||||
};
|
||||
|
||||
case CHAPTER_READ:
|
||||
return {
|
||||
...state,
|
||||
chapters: state.chapters.map((chapter) =>
|
||||
chapter.chapterUrl === payload.chapterUrl &&
|
||||
chapter.novelUrl === payload.novelUrl
|
||||
? { ...chapter, read: 1 }
|
||||
: chapter
|
||||
),
|
||||
};
|
||||
case CHAPTER_LOADING:
|
||||
return {
|
||||
...state,
|
||||
chapterLoading: true,
|
||||
};
|
||||
case GET_CHAPTER:
|
||||
return { ...state, chapter: payload, chapterLoading: false };
|
||||
// case CHAPTER_DOWNLOADING:
|
||||
// return {
|
||||
// ...state,
|
||||
// chapters: [
|
||||
// state.chapters.map((chap) =>
|
||||
// chap.novelUrl === payload.novelUrl &&
|
||||
// chap.chapterUrl === payload.novelUrl
|
||||
// ? { ...chap, downloading: true, downloaded: false }
|
||||
// : chap
|
||||
// ),
|
||||
// ],
|
||||
// };
|
||||
// case CHAPTER_DOWNLOADED:
|
||||
// return {
|
||||
// ...state,
|
||||
// downloads: state.downloads.map((chap) =>
|
||||
// chap.chapterUrl === payload.chapterUrl &&
|
||||
// chap.novelUrl === payload.novelUrl
|
||||
// ? { ...chap, downloading: false, downloaded: true }
|
||||
// : chap
|
||||
// ),
|
||||
// };
|
||||
// case CHAPTER_DELETED:
|
||||
// return {
|
||||
// downloads: state.downloads.filter(
|
||||
// (chapter) =>
|
||||
// chapter.chapterUrl === payload.chapterUrl &&
|
||||
// chapter.novelUrl === payload.novelUrl
|
||||
// ),
|
||||
// };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default novelReducer;
|
||||
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
SET_DISPLAY_MODE,
|
||||
SET_ITEMS_PER_ROW,
|
||||
UPDATE_READER_TEXT_SIZE,
|
||||
UPDATE_READER_THEME,
|
||||
} from "../actions/types";
|
||||
|
||||
const initialState = {
|
||||
displayMode: 0,
|
||||
itemsPerRow: 3,
|
||||
reader: {
|
||||
theme: 1,
|
||||
textSize: 16,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Display Mode
|
||||
* 0 -> Compact
|
||||
* 1 -> Comfortable
|
||||
* 2 -> List
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reader Settings
|
||||
* theme
|
||||
* 1 -> Dark
|
||||
* 2 -> White
|
||||
* 3 -> Sepia
|
||||
*/
|
||||
|
||||
const settingsReducer = (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
switch (type) {
|
||||
case SET_DISPLAY_MODE:
|
||||
return { ...state, displayMode: payload };
|
||||
case SET_ITEMS_PER_ROW:
|
||||
return { ...state, itemsPerRow: payload };
|
||||
case UPDATE_READER_THEME:
|
||||
return { ...state, reader: { ...state.reader, theme: payload } };
|
||||
case UPDATE_READER_TEXT_SIZE:
|
||||
return { ...state, reader: { ...state.reader, textSize: payload } };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default settingsReducer;
|
||||
@@ -1,7 +1,20 @@
|
||||
import { amoledDarkTheme } from "../../theme/theme";
|
||||
import {
|
||||
amoledDarkTheme,
|
||||
lightTheme,
|
||||
darkTheme,
|
||||
midnightDuskTheme,
|
||||
} from "../../theme/theme";
|
||||
|
||||
const themes = {
|
||||
0: amoledDarkTheme,
|
||||
1: lightTheme,
|
||||
2: darkTheme,
|
||||
3: midnightDuskTheme,
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
theme: amoledDarkTheme,
|
||||
themeCode: 0,
|
||||
theme: themes[0],
|
||||
};
|
||||
|
||||
const themeReducer = (state = initialState, action) => {
|
||||
@@ -9,7 +22,7 @@ const themeReducer = (state = initialState, action) => {
|
||||
|
||||
switch (type) {
|
||||
case "SWITCH_THEME":
|
||||
return { theme: payload };
|
||||
return { ...state, themeCode: payload, theme: themes[payload] };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -4,16 +4,28 @@ import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import thunk from "redux-thunk";
|
||||
|
||||
import themeReducer from "../reducers/theme";
|
||||
import settingsReducer from "../reducers/settings";
|
||||
import libraryReducer from "../reducers/library";
|
||||
import extensionReducer from "../reducers/extension";
|
||||
import novelReducer from "../reducers/novel";
|
||||
import historyReducer from "../reducers/history";
|
||||
|
||||
const persistConfig = {
|
||||
key: "root",
|
||||
storage: AsyncStorage,
|
||||
blacklist: ["novelReducer"],
|
||||
};
|
||||
|
||||
const persistedReducer = persistReducer(
|
||||
persistConfig,
|
||||
combineReducers({ themeReducer, libraryReducer })
|
||||
combineReducers({
|
||||
themeReducer,
|
||||
settingsReducer,
|
||||
libraryReducer,
|
||||
extensionReducer,
|
||||
novelReducer,
|
||||
historyReducer,
|
||||
})
|
||||
);
|
||||
|
||||
export const store = createStore(persistedReducer, applyMiddleware(thunk));
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
Text,
|
||||
FlatList,
|
||||
Image,
|
||||
ActivityIndicator,
|
||||
} from "react-native";
|
||||
import { TouchableRipple, Button } from "react-native-paper";
|
||||
import { CustomAppbar } from "../components/common/Appbar";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const Browse = ({ navigation }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [extensions, setExtensions] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`https://lnreader-extensions.herokuapp.com/api/`)
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
setExtensions(json);
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomAppbar title="Browse" />
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorDarkPrimaryDark },
|
||||
]}
|
||||
>
|
||||
{!loading ? (
|
||||
<FlatList
|
||||
data={extensions}
|
||||
keyExtractor={(item) => item.sourceId.toString()}
|
||||
renderItem={({ item }) => (
|
||||
<TouchableRipple
|
||||
style={styles.sourceCard}
|
||||
onPress={() =>
|
||||
navigation.navigate(
|
||||
item.sourceName + "Stack"
|
||||
)
|
||||
}
|
||||
rippleColor={theme.rippleColorDark}
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
source={{
|
||||
uri: item.sourceCover,
|
||||
}}
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 6,
|
||||
}}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
marginLeft: 15,
|
||||
flex: 1,
|
||||
justifyContent: "space-between",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<View>
|
||||
<Text
|
||||
style={{
|
||||
color:
|
||||
theme.textColorPrimaryDark,
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
{item.sourceName}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color:
|
||||
theme.textColorSecondaryDark,
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{item.sourceLanguage}
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Button
|
||||
labelStyle={{
|
||||
letterSpacing: 0,
|
||||
}}
|
||||
uppercase={false}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() =>
|
||||
navigation.navigate(
|
||||
item.sourceName +
|
||||
"Stack"
|
||||
)
|
||||
}
|
||||
>
|
||||
Browse
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<View style={{ padding: 20 }}>
|
||||
<ActivityIndicator
|
||||
size="small"
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Browse;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
sourceCard: {
|
||||
paddingVertical: 10,
|
||||
marginVertical: 5,
|
||||
paddingHorizontal: 20,
|
||||
borderRadius: 6,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { StyleSheet, View, FlatList, ActivityIndicator } from "react-native";
|
||||
import { Appbar } from "../../components/common/Appbar";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { getExtensions } from "../../redux/actions/extension";
|
||||
|
||||
import ExtensionCard from "./components/ExtensionCard";
|
||||
|
||||
const Browse = ({ theme, extensions, loading, getExtensions }) => {
|
||||
useEffect(() => {
|
||||
getExtensions();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar title="Browse" />
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<FlatList
|
||||
data={extensions}
|
||||
keyExtractor={(item) => item.sourceId.toString()}
|
||||
renderItem={({ item }) => <ExtensionCard item={item} />}
|
||||
ListEmptyComponent={
|
||||
loading && (
|
||||
<ActivityIndicator
|
||||
size="small"
|
||||
color={theme.colorAccentDark}
|
||||
style={{ marginTop: 16 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
theme: state.themeReducer.theme,
|
||||
extensions: state.extensionReducer.extensions,
|
||||
loading: state.extensionReducer.loading,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { getExtensions })(Browse);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
import React from "react";
|
||||
import { View, Text, Image, StyleSheet } from "react-native";
|
||||
import { TouchableRipple, Button } from "react-native-paper";
|
||||
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const ExtensionCard = ({ item }) => {
|
||||
const { sourceName, sourceCover, sourceLanguage } = item;
|
||||
|
||||
const navigation = useNavigation();
|
||||
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
style={styles.extensionCard}
|
||||
onPress={() => navigation.navigate(sourceName + "Stack")}
|
||||
rippleColor={theme.rippleColor}
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
source={{
|
||||
uri: sourceCover,
|
||||
}}
|
||||
style={styles.extensionIcon}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
<View style={styles.extensionDetails}>
|
||||
<View>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
{sourceName}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{sourceLanguage}
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Button
|
||||
labelStyle={{
|
||||
letterSpacing: 0,
|
||||
}}
|
||||
uppercase={false}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() =>
|
||||
navigation.navigate(sourceName + "Stack")
|
||||
}
|
||||
>
|
||||
Browse
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtensionCard;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
extensionCard: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginVertical: 4,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 20,
|
||||
borderRadius: 6,
|
||||
},
|
||||
extensionIcon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 8,
|
||||
},
|
||||
extensionDetails: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginLeft: 16,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,193 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { StyleSheet, View, Text, ActivityIndicator } from "react-native";
|
||||
|
||||
import { Appbar, Provider, Portal } from "react-native-paper";
|
||||
import { CollapsibleHeaderScrollView } from "react-native-collapsible-header-views";
|
||||
|
||||
import BottomSheet from "./components/BottomSheet";
|
||||
|
||||
import { updateNovelHistory } from "../../redux/actions/history";
|
||||
import { getChapter, updateChapterRead } from "../../redux/actions/novel";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
|
||||
const ChapterItem = ({
|
||||
route,
|
||||
navigation,
|
||||
theme,
|
||||
updateNovelHistory,
|
||||
updateChapterRead,
|
||||
getChapter,
|
||||
chapter,
|
||||
loading,
|
||||
reader,
|
||||
}) => {
|
||||
const { extensionId, chapterUrl, novelUrl, chapterName } = route.params;
|
||||
|
||||
let _panel = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
getChapter(extensionId, chapterUrl, novelUrl);
|
||||
updateNovelHistory(chapterUrl, chapterName, novelUrl);
|
||||
}, []);
|
||||
|
||||
const readerBackground = (val) => {
|
||||
const backgroundColor = {
|
||||
1: "#000000",
|
||||
2: "#FFFFFF",
|
||||
3: "#F4ECD8",
|
||||
};
|
||||
|
||||
return backgroundColor[val];
|
||||
};
|
||||
|
||||
const readerTextColor = (val) => {
|
||||
const textColor = val === 1 ? theme.textColorSecondary : "#000000";
|
||||
|
||||
return textColor;
|
||||
};
|
||||
|
||||
const readerLineHeight = (val) => {
|
||||
const lineHeight = {
|
||||
12: 20,
|
||||
16: 25,
|
||||
20: 28,
|
||||
};
|
||||
return lineHeight[val];
|
||||
};
|
||||
|
||||
const isCloseToBottom = ({
|
||||
layoutMeasurement,
|
||||
contentOffset,
|
||||
contentSize,
|
||||
}) => {
|
||||
const paddingToBottom = 40;
|
||||
return (
|
||||
layoutMeasurement.height + contentOffset.y >=
|
||||
contentSize.height - paddingToBottom
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
<CollapsibleHeaderScrollView
|
||||
headerContainerBackgroundColor={"rgba(0,0,0,0.4)"}
|
||||
CollapsibleHeaderComponent={
|
||||
<Appbar.Header
|
||||
style={{
|
||||
backgroundColor: "transparent",
|
||||
elevation: 0,
|
||||
}}
|
||||
>
|
||||
<Appbar.BackAction
|
||||
onPress={() => navigation.goBack()}
|
||||
color="#FFFFFF"
|
||||
size={26}
|
||||
style={{ marginRight: 0 }}
|
||||
/>
|
||||
<Appbar.Content
|
||||
title={loading ? "Chapter" : chapter.chapterName}
|
||||
titleStyle={{ color: "#FFFFFF" }}
|
||||
/>
|
||||
{!loading && (
|
||||
<>
|
||||
<Appbar.Action
|
||||
icon="chevron-left"
|
||||
size={26}
|
||||
disabled={!chapter.prevChapter}
|
||||
onPress={() => {
|
||||
navigation.push("ChapterItem", {
|
||||
chapterUrl: chapter.prevChapter,
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterName: chapter.chapterName,
|
||||
});
|
||||
}}
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon="chevron-right"
|
||||
size={26}
|
||||
disabled={!chapter.nextChapter}
|
||||
onPress={() => {
|
||||
navigation.push("ChapterItem", {
|
||||
chapterUrl: chapter.nextChapter,
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterName: chapter.chapterName,
|
||||
});
|
||||
}}
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon="dots-vertical"
|
||||
size={26}
|
||||
onPress={() =>
|
||||
_panel.current.show({ velocity: -1.5 })
|
||||
}
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Appbar.Header>
|
||||
}
|
||||
headerHeight={100}
|
||||
contentContainerStyle={[
|
||||
styles.container,
|
||||
{ backgroundColor: readerBackground(reader.theme) },
|
||||
]}
|
||||
onScroll={({ nativeEvent }) => {
|
||||
if (isCloseToBottom(nativeEvent)) {
|
||||
updateChapterRead(chapterUrl, novelUrl);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<ActivityIndicator
|
||||
size={50}
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
paddingVertical: 15,
|
||||
fontSize: reader.textSize,
|
||||
color: readerTextColor(reader.theme),
|
||||
lineHeight: readerLineHeight(reader.textSize),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{chapter.chapterText.trim()}
|
||||
</Text>
|
||||
)}
|
||||
<Portal>
|
||||
<BottomSheet bottomSheetRef={_panel} />
|
||||
</Portal>
|
||||
</CollapsibleHeaderScrollView>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
theme: state.themeReducer.theme,
|
||||
chapter: state.novelReducer.chapter,
|
||||
loading: state.novelReducer.chapterLoading,
|
||||
reader: state.settingsReducer.reader,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
updateNovelHistory,
|
||||
updateChapterRead,
|
||||
getChapter,
|
||||
})(ChapterItem);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexGrow: 1,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
});
|
||||
+34
-40
@@ -5,19 +5,21 @@ import { ToggleButton } from "react-native-paper";
|
||||
import Slider from "@react-native-community/slider";
|
||||
import Bottomsheet from "rn-sliding-up-panel";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { saveReaderTheme } from "../../services/asyncStorage";
|
||||
import {
|
||||
updateReaderTextSize,
|
||||
updateReaderTheme,
|
||||
} from "../../../redux/actions/settings";
|
||||
import BottomSheetHandle from "../../../components/common/BottomSheetHandle";
|
||||
|
||||
const ChapterBottomSheet = ({
|
||||
bottomSheetRef,
|
||||
setSize,
|
||||
size,
|
||||
readerTheme,
|
||||
setReaderTheme,
|
||||
theme,
|
||||
reader,
|
||||
updateReaderTextSize,
|
||||
updateReaderTheme,
|
||||
}) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
return (
|
||||
<Bottomsheet
|
||||
animatedValue={new Animated.Value(0)}
|
||||
@@ -29,26 +31,14 @@ const ChapterBottomSheet = ({
|
||||
<View
|
||||
style={[
|
||||
styles.contentContainer,
|
||||
{
|
||||
backgroundColor: "rgba(0,0,0,0.4)",
|
||||
},
|
||||
{ backgroundColor: "rgba(0,0,0,0.4)" },
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: theme.textColorHintDark,
|
||||
height: 5,
|
||||
width: 30,
|
||||
borderRadius: 50,
|
||||
top: 10,
|
||||
alignSelf: "center",
|
||||
}}
|
||||
/>
|
||||
|
||||
<View style={{ flex: 1, alignItems: "center", paddingTop: 30 }}>
|
||||
<BottomSheetHandle />
|
||||
<View style={styles.readerSettingsContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimaryDark,
|
||||
color: theme.textColorPrimary,
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
@@ -59,52 +49,49 @@ const ChapterBottomSheet = ({
|
||||
width: Dimensions.get("window").width,
|
||||
height: 40,
|
||||
}}
|
||||
value={size}
|
||||
value={reader.textSize}
|
||||
minimumValue={12}
|
||||
maximumValue={20}
|
||||
step={4}
|
||||
minimumTrackTintColor={theme.colorAccentDark}
|
||||
maximumTrackTintColor="#000000"
|
||||
thumbTintColor={theme.colorAccentDark}
|
||||
onValueChange={(value) => setSize(value)}
|
||||
onValueChange={(value) => updateReaderTextSize(value)}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimaryDark,
|
||||
color: theme.textColorPrimary,
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Reader Theme
|
||||
</Text>
|
||||
<ToggleButton.Row
|
||||
onValueChange={(value) => {
|
||||
setReaderTheme(value);
|
||||
saveReaderTheme(value);
|
||||
}}
|
||||
value={readerTheme}
|
||||
onValueChange={(value) => updateReaderTheme(value)}
|
||||
value={reader.theme}
|
||||
style={{ marginTop: 10 }}
|
||||
>
|
||||
<ToggleButton
|
||||
icon="format-text"
|
||||
color="white"
|
||||
color="#FFFFFF"
|
||||
value={1}
|
||||
style={{
|
||||
backgroundColor: "black",
|
||||
backgroundColor: "#000000",
|
||||
marginHorizontal: 10,
|
||||
}}
|
||||
/>
|
||||
<ToggleButton
|
||||
icon="format-text"
|
||||
color="black"
|
||||
color="#000000"
|
||||
value={2}
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
backgroundColor: "#FFFFFF",
|
||||
marginHorizontal: 10,
|
||||
}}
|
||||
/>
|
||||
<ToggleButton
|
||||
icon="format-text"
|
||||
color="black"
|
||||
color="#000000"
|
||||
value={3}
|
||||
style={{
|
||||
backgroundColor: "#F4ECD8",
|
||||
@@ -118,12 +105,19 @@ const ChapterBottomSheet = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default ChapterBottomSheet;
|
||||
const mapStateToProps = (state) => ({
|
||||
theme: state.themeReducer.theme,
|
||||
reader: state.settingsReducer.reader,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
updateReaderTextSize,
|
||||
updateReaderTheme,
|
||||
})(ChapterBottomSheet);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
// paddingTop: ,
|
||||
// alignItems: "center",
|
||||
},
|
||||
readerSettingsContainer: { flex: 1, alignItems: "center", paddingTop: 30 },
|
||||
});
|
||||
@@ -1,315 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { StyleSheet, View, Text, ActivityIndicator } from "react-native";
|
||||
|
||||
import { Appbar, Provider, Portal } from "react-native-paper";
|
||||
import { theme } from "../theme/theme";
|
||||
import { CollapsibleHeaderScrollView } from "react-native-collapsible-header-views";
|
||||
|
||||
import BottomSheet from "../components/chapter/BottomSheet";
|
||||
|
||||
import * as SQLite from "expo-sqlite";
|
||||
const db = SQLite.openDatabase("lnreader.db");
|
||||
|
||||
import { fetchChapterFromSource } from "../services/api";
|
||||
|
||||
import { getReaderTheme } from "../services/asyncStorage";
|
||||
|
||||
const ChapterItem = ({ route, navigation }) => {
|
||||
const { extensionId, chapterUrl, novelUrl, chapterName } = route.params;
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [chapter, setChapter] = useState();
|
||||
|
||||
const [size, setSize] = useState(16);
|
||||
|
||||
const [readerTheme, setReaderTheme] = useState(1);
|
||||
|
||||
getReaderTheme().then((value) => setReaderTheme(value));
|
||||
|
||||
const setHistory = () => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"INSERT INTO HistoryTable (chapterUrl, novelUrl, chapterName, lastRead) VALUES ( ?, ?, ?, (datetime('now','localtime')))",
|
||||
[chapterUrl, novelUrl, chapterName],
|
||||
(tx, res) =>
|
||||
console.log(
|
||||
"Inserted into history table: " + novelUrl + chapterUrl
|
||||
),
|
||||
(tx, error) => console.log(error)
|
||||
);
|
||||
tx.executeSql(
|
||||
"UPDATE LibraryTable SET lastRead = ?, lastReadName = ?, unread = 0 WHERE novelUrl = ?",
|
||||
[chapterUrl, chapterName, novelUrl],
|
||||
(tx, res) =>
|
||||
console.log("Set Last Read " + novelUrl + chapterUrl),
|
||||
(tx, error) => console.log(error)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const updateHistory = () => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"UPDATE HistoryTable SET lastRead = (datetime('now','localtime')), chapterName=?, chapterUrl=? WHERE novelUrl = ?",
|
||||
[chapterName, chapterUrl, novelUrl],
|
||||
(tx, res) =>
|
||||
console.log("Updated into history table: " + novelUrl),
|
||||
(tx, error) => console.log(error)
|
||||
);
|
||||
tx.executeSql(
|
||||
"UPDATE LibraryTable SET lastRead = ?, lastReadName = ?, unread = 0 WHERE novelUrl = ?",
|
||||
[chapterUrl, chapterName, novelUrl],
|
||||
(tx, res) =>
|
||||
console.log("Set Last read" + novelUrl + chapterUrl),
|
||||
(tx, error) => console.log(error)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const setRead = () => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"UPDATE ChapterTable SET `read` = 1 WHERE chapterUrl = ?",
|
||||
[chapterUrl],
|
||||
(tx, res) => console.log("Updated readStatus: " + novelUrl),
|
||||
(tx, error) => console.log(error)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const checkIfInHistory = () => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"SELECT * FROM HistoryTable WHERE novelUrl=?",
|
||||
[novelUrl],
|
||||
(txObj, res) => {
|
||||
if (res.rows.length === 0) {
|
||||
console.log("Not In History");
|
||||
setHistory();
|
||||
} else {
|
||||
console.log("In History");
|
||||
updateHistory();
|
||||
}
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// const updateLastRead = () => {
|
||||
// db.transaction((tx) => {
|
||||
|
||||
// tx.executeSql(
|
||||
// "UPDATE LibraryTable SET lastRead = ?, lastReadName = ?, unread = 0 WHERE novelUrl = ?",
|
||||
// [chapter.nextChapter, chapterName, novelUrl],
|
||||
// (tx, res) =>
|
||||
// console.log("Set Last Read " + novelUrl + chapterUrl),
|
||||
// (tx, error) => console.log(error)
|
||||
// );
|
||||
// });
|
||||
// };
|
||||
|
||||
const checkIfDownloaded = () => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"SELECT * FROM DownloadsTable WHERE chapterUrl=? AND novelUrl=?",
|
||||
[chapterUrl, novelUrl],
|
||||
(txObj, res) => {
|
||||
if (res.rows.length === 0) {
|
||||
// console.log("Not Downloaded");
|
||||
fetchChapterFromSource(
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterUrl
|
||||
).then((res) => {
|
||||
setChapter(res);
|
||||
setLoading(false);
|
||||
});
|
||||
} else {
|
||||
// console.log("Already Downloaded");
|
||||
getChapterFromDB();
|
||||
}
|
||||
checkIfInHistory();
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const getChapterFromDB = () => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
`SELECT * FROM DownloadsTable WHERE chapterUrl = ?`,
|
||||
[chapterUrl],
|
||||
(tx, results) => {
|
||||
setChapter(results.rows.item(0));
|
||||
setLoading(false);
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
checkIfDownloaded();
|
||||
}, []);
|
||||
|
||||
const isCloseToBottom = ({
|
||||
layoutMeasurement,
|
||||
contentOffset,
|
||||
contentSize,
|
||||
}) => {
|
||||
const paddingToBottom = 0;
|
||||
return (
|
||||
layoutMeasurement.height + contentOffset.y >=
|
||||
contentSize.height - paddingToBottom
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
<CollapsibleHeaderScrollView
|
||||
headerContainerBackgroundColor={"rgba(0,0,0,0.4)"}
|
||||
CollapsibleHeaderComponent={
|
||||
<Appbar.Header
|
||||
style={{
|
||||
backgroundColor: "transparent",
|
||||
elevation: 0,
|
||||
}}
|
||||
>
|
||||
<Appbar.BackAction
|
||||
onPress={() => {
|
||||
// navigation.navigate("NovelItem", {
|
||||
// novelUrl,
|
||||
// extensionId,
|
||||
// navigatingFrom: 0,
|
||||
// });
|
||||
navigation.goBack();
|
||||
}}
|
||||
color={"white"}
|
||||
size={26}
|
||||
style={{ marginRight: 0 }}
|
||||
/>
|
||||
<Appbar.Content
|
||||
title={loading ? "Chapter" : chapter.chapterName}
|
||||
titleStyle={{ color: theme.textColorPrimaryDark }}
|
||||
/>
|
||||
{!loading && (
|
||||
<>
|
||||
<Appbar.Action
|
||||
icon="chevron-left"
|
||||
size={26}
|
||||
disabled={!chapter.prevChapter}
|
||||
onPress={() => {
|
||||
navigation.push("ChapterItem", {
|
||||
chapterUrl: chapter.prevChapter,
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterName: chapter.chapterName,
|
||||
});
|
||||
}}
|
||||
color={"white"}
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon="chevron-right"
|
||||
size={26}
|
||||
disabled={!chapter.nextChapter}
|
||||
onPress={() => {
|
||||
navigation.push("ChapterItem", {
|
||||
chapterUrl: chapter.nextChapter,
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterName: chapter.chapterName,
|
||||
});
|
||||
}}
|
||||
color={"white"}
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon="dots-vertical"
|
||||
size={26}
|
||||
onPress={() =>
|
||||
_panel.show({ velocity: -1.5 })
|
||||
}
|
||||
color={"white"}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Appbar.Header>
|
||||
}
|
||||
headerHeight={100}
|
||||
contentContainerStyle={[
|
||||
styles.container,
|
||||
readerTheme === 1 && {
|
||||
backgroundColor: theme.colorDarkPrimary,
|
||||
},
|
||||
readerTheme === 2 && {
|
||||
backgroundColor: "white",
|
||||
},
|
||||
readerTheme === 3 && {
|
||||
backgroundColor: "#F4ECD8",
|
||||
},
|
||||
]}
|
||||
onScroll={({ nativeEvent }) => {
|
||||
// _panel.hide();
|
||||
if (isCloseToBottom(nativeEvent)) {
|
||||
// console.log("Scroll End Reached");
|
||||
setRead();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* <Button mode="contained" onPress={() => setHistory()}>
|
||||
Set History
|
||||
</Button> */}
|
||||
{loading ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<ActivityIndicator
|
||||
size={50}
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
paddingVertical: 15,
|
||||
fontSize: size,
|
||||
},
|
||||
readerTheme === 1
|
||||
? { color: theme.textColorSecondaryDark }
|
||||
: { color: "black" },
|
||||
size === 16
|
||||
? { lineHeight: 25 }
|
||||
: size === 20
|
||||
? { lineHeight: 28 }
|
||||
: size === 12 && { lineHeight: 20 },
|
||||
]}
|
||||
>
|
||||
{chapter.chapterText.trim()}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
<Portal>
|
||||
<BottomSheet
|
||||
bottomSheetRef={(c) => (_panel = c)}
|
||||
setSize={setSize}
|
||||
size={size}
|
||||
setReaderTheme={setReaderTheme}
|
||||
readerTheme={readerTheme}
|
||||
/>
|
||||
</Portal>
|
||||
</CollapsibleHeaderScrollView>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChapterItem;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexGrow: 1,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
});
|
||||
@@ -1,63 +1,55 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
|
||||
import { StyleSheet, View, FlatList, ActivityIndicator } from "react-native";
|
||||
|
||||
import { Appbar } from "../../components/common/Appbar";
|
||||
import HistoryCard from "./components/HistoryCard";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import EmptyView from "../../components/common/EmptyView";
|
||||
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
Text,
|
||||
FlatList,
|
||||
ActivityIndicator,
|
||||
} from "react-native";
|
||||
|
||||
import { CustomAppbar } from "../../components/common/Appbar";
|
||||
import HistoryCard from "../../components/history/HistoryCard";
|
||||
|
||||
import { getHistoryFromDb, deleteChapterHistory } from "../../services/db";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const History = ({ navigation }) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [history, setHistory] = useState();
|
||||
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
const getHistory = async () => {
|
||||
await getHistoryFromDb().then((res) => setHistory(res));
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const deleteHistory = (novelId) => {
|
||||
deleteChapterHistory(novelId);
|
||||
getHistory();
|
||||
};
|
||||
getHistory,
|
||||
deleteChapterFromHistory,
|
||||
} from "../../redux/actions/history";
|
||||
|
||||
const History = ({
|
||||
navigation,
|
||||
theme,
|
||||
history,
|
||||
loading,
|
||||
getHistory,
|
||||
deleteChapterFromHistory,
|
||||
}) => {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
getHistory();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const renderHistoryCard = ({ item }) => (
|
||||
<HistoryCard
|
||||
item={item}
|
||||
deleteHistory={deleteChapterFromHistory}
|
||||
navigation={navigation}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomAppbar title="History" />
|
||||
<Appbar title="History" />
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorDarkPrimaryDark },
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<FlatList
|
||||
contentContainerStyle={{ flex: 1 }}
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
data={history}
|
||||
keyExtractor={(item) => item.historyId.toString()}
|
||||
renderItem={({ item }) => (
|
||||
<HistoryCard
|
||||
item={item}
|
||||
deleteHistory={deleteHistory}
|
||||
navigation={navigation}
|
||||
/>
|
||||
)}
|
||||
keyExtractor={(item) => item.novelUrl.toString()}
|
||||
renderItem={renderHistoryCard}
|
||||
ListFooterComponent={
|
||||
loading && (
|
||||
<ActivityIndicator
|
||||
@@ -68,34 +60,10 @@ const History = ({ navigation }) => {
|
||||
}
|
||||
ListEmptyComponent={
|
||||
!loading && (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondaryDark,
|
||||
fontSize: 45,
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
(˘・_・˘)
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondaryDark,
|
||||
fontWeight: "bold",
|
||||
marginTop: 10,
|
||||
textAlign: "center",
|
||||
paddingHorizontal: 30,
|
||||
}}
|
||||
>
|
||||
Nothing read recently.
|
||||
</Text>
|
||||
</View>
|
||||
<EmptyView
|
||||
icon="(˘・_・˘)"
|
||||
description="Nothing read recently"
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
@@ -104,17 +72,20 @@ const History = ({ navigation }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default History;
|
||||
const mapStateToProps = (state) => ({
|
||||
theme: state.themeReducer.theme,
|
||||
history: state.historyReducer.history,
|
||||
loading: state.historyReducer.loading,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
getHistory,
|
||||
deleteChapterFromHistory,
|
||||
})(History);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 10,
|
||||
},
|
||||
historyCard: {
|
||||
marginTop: 10,
|
||||
borderRadius: 4,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
},
|
||||
});
|
||||
|
||||
+20
-27
@@ -12,11 +12,8 @@ const HistoryCard = ({ item, deleteHistory, navigation }) => {
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
style={[
|
||||
styles.historyCard,
|
||||
// { backgroundColor: theme.colorDarkPrimary },
|
||||
]}
|
||||
rippleColor={theme.rippleColorDark}
|
||||
style={styles.historyCard}
|
||||
rippleColor={theme.rippleColor}
|
||||
borderless
|
||||
onPress={() =>
|
||||
navigation.navigate("NovelItem", {
|
||||
@@ -24,31 +21,20 @@ const HistoryCard = ({ item, deleteHistory, navigation }) => {
|
||||
novelCover: item.novelCover,
|
||||
extensionId: item.extensionId,
|
||||
novelUrl: item.novelUrl,
|
||||
navigatingFrom: 0,
|
||||
libraryStatus: item.libraryStatus,
|
||||
})
|
||||
}
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
source={{ uri: item.novelCover }}
|
||||
style={{
|
||||
height: 80,
|
||||
width: 57,
|
||||
borderRadius: 4,
|
||||
}}
|
||||
style={styles.historyCover}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
marginLeft: 15,
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<View style={styles.historyText}>
|
||||
<View style={{ flex: 0.9 }}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimaryDark,
|
||||
color: theme.textColorPrimary,
|
||||
fontWeight: "bold",
|
||||
fontSize: 16,
|
||||
}}
|
||||
@@ -58,7 +44,7 @@ const HistoryCard = ({ item, deleteHistory, navigation }) => {
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondaryDark,
|
||||
color: theme.textColorSecondary,
|
||||
marginTop: 2,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
@@ -77,7 +63,7 @@ const HistoryCard = ({ item, deleteHistory, navigation }) => {
|
||||
<IconButton
|
||||
icon="delete-outline"
|
||||
size={22}
|
||||
color={theme.textColorPrimaryDark}
|
||||
color={theme.textColorPrimary}
|
||||
style={{
|
||||
marginRight: 0,
|
||||
}}
|
||||
@@ -86,7 +72,7 @@ const HistoryCard = ({ item, deleteHistory, navigation }) => {
|
||||
<IconButton
|
||||
icon="play"
|
||||
size={24}
|
||||
color={theme.textColorPrimaryDark}
|
||||
color={theme.textColorPrimary}
|
||||
style={{ marginRight: 10 }}
|
||||
onPress={() =>
|
||||
navigation.navigate("ChapterItem", {
|
||||
@@ -110,13 +96,20 @@ export default HistoryCard;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
historyCard: {
|
||||
// backgroundColor: theme.colorDarkPrimary,
|
||||
// paddingVertical: 10,
|
||||
// marginVertical: 5,
|
||||
// paddingHorizontal: 20,
|
||||
marginBottom: 15,
|
||||
borderRadius: 4,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
},
|
||||
historyCover: {
|
||||
height: 80,
|
||||
width: 57,
|
||||
borderRadius: 4,
|
||||
},
|
||||
historyText: {
|
||||
marginLeft: 15,
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
});
|
||||
@@ -1,220 +0,0 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
TextInput,
|
||||
RefreshControl,
|
||||
ActivityIndicator,
|
||||
ToastAndroid,
|
||||
} from "react-native";
|
||||
import { Appbar } from "react-native-paper";
|
||||
import { FlatList } from "react-native-gesture-handler";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
|
||||
import NovelCover from "../components/common/NovelCover";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { getLibraryFromDb, searchInLibrary } from "../services/db";
|
||||
|
||||
import { getLibraryNovels } from "../redux/actions/library";
|
||||
|
||||
const LibraryScreen = ({ navigation }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [novels, setNovels] = useState();
|
||||
|
||||
const [searchBar, setSearchBar] = useState(false);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const getLibrary = async () => {
|
||||
await getLibraryFromDb().then((res) => {
|
||||
setNovels(res);
|
||||
});
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
const searchNovel = async (searchText) => {
|
||||
await searchInLibrary(searchText).then((res) => setNovels(res));
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
setSearchBar(false);
|
||||
getLibrary();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const onRefresh = () => {
|
||||
ToastAndroid.show("Updating library", ToastAndroid.SHORT);
|
||||
setRefreshing(true);
|
||||
getLibrary();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar.Header style={{ backgroundColor: theme.colorDarkPrimary }}>
|
||||
{!searchBar ? (
|
||||
<>
|
||||
<Appbar.Content
|
||||
title="Library"
|
||||
titleStyle={{ color: theme.textColorPrimaryDark }}
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon="magnify"
|
||||
onPress={() => setSearchBar(true)}
|
||||
color={theme.textColorPrimaryDark}
|
||||
/>
|
||||
<Appbar.Action
|
||||
color={theme.textColorPrimaryDark}
|
||||
icon="filter-variant"
|
||||
disabled
|
||||
// onPress={() => _panel.show({ velocity: -1.5 })}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Appbar.BackAction
|
||||
onPress={() => {
|
||||
setSearchBar(false);
|
||||
setSearchText("");
|
||||
getLibrary();
|
||||
}}
|
||||
color={theme.textColorPrimaryDark}
|
||||
size={24}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="Search..."
|
||||
defaultValue={searchText}
|
||||
style={{
|
||||
fontSize: 18,
|
||||
flex: 1,
|
||||
color: theme.textColorPrimaryDark,
|
||||
marginLeft: 15,
|
||||
}}
|
||||
autoFocus
|
||||
placeholderTextColor={theme.textColorHintDark}
|
||||
blurOnSubmit={true}
|
||||
onChangeText={(text) => {
|
||||
setSearchText(text);
|
||||
searchNovel(text);
|
||||
}}
|
||||
/>
|
||||
{searchText !== "" && (
|
||||
<Appbar.Action
|
||||
icon="close"
|
||||
onPress={() => {
|
||||
setSearchText("");
|
||||
// getLibrary();
|
||||
}}
|
||||
color={theme.textColorPrimaryDark}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Appbar.Header>
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorDarkPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<FlatList
|
||||
contentContainerStyle={{ flex: 1 }}
|
||||
numColumns={3}
|
||||
data={novels}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyExtractor={(item) => item.novelUrl}
|
||||
renderItem={({ item }) => (
|
||||
<NovelCover
|
||||
item={item}
|
||||
onPress={() =>
|
||||
navigation.navigate("NovelItem", {
|
||||
...item,
|
||||
navigatingFrom: 1,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
colors={["white"]}
|
||||
progressBackgroundColor={theme.colorAccentDark}
|
||||
/>
|
||||
}
|
||||
ListEmptyComponent={
|
||||
!loading ? (
|
||||
!searchBar ? (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondaryDark,
|
||||
fontSize: 45,
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Σ(ಠ_ಠ)
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondaryDark,
|
||||
fontWeight: "bold",
|
||||
marginTop: 10,
|
||||
textAlign: "center",
|
||||
paddingHorizontal: 30,
|
||||
}}
|
||||
>
|
||||
Your library is empty. Add series to
|
||||
your library from Browse.
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
searchBar &&
|
||||
searchText !== "" && (
|
||||
<Text
|
||||
style={{
|
||||
color: theme.colorAccentDark,
|
||||
fontWeight: "bold",
|
||||
marginTop: 10,
|
||||
textAlign: "center",
|
||||
paddingHorizontal: 30,
|
||||
}}
|
||||
>
|
||||
{`"${searchText}" not in library`}
|
||||
</Text>
|
||||
)
|
||||
)
|
||||
) : (
|
||||
<ActivityIndicator
|
||||
size="small"
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LibraryScreen;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 4,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,149 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
RefreshControl,
|
||||
ActivityIndicator,
|
||||
ToastAndroid,
|
||||
} from "react-native";
|
||||
import { FlatList } from "react-native-gesture-handler";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import NovelCover from "../../components/common/NovelCover";
|
||||
import EmptyView from "../../components/common/EmptyView";
|
||||
|
||||
import {
|
||||
getLibraryNovels,
|
||||
searchLibraryNovels,
|
||||
} from "../../redux/actions/library";
|
||||
import { SearchAppbar } from "../../components/common/Appbar";
|
||||
import { setNovel } from "../../redux/actions/novel";
|
||||
|
||||
const LibraryScreen = ({
|
||||
navigation,
|
||||
theme,
|
||||
novels,
|
||||
loading,
|
||||
getLibraryNovels,
|
||||
searchLibraryNovels,
|
||||
setNovel,
|
||||
}) => {
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
setSearchText("");
|
||||
getLibraryNovels();
|
||||
}, [])
|
||||
);
|
||||
|
||||
/**
|
||||
* TODO: fix refreshing
|
||||
*/
|
||||
const onRefresh = () => {
|
||||
ToastAndroid.show("Updating library", ToastAndroid.SHORT);
|
||||
setRefreshing(true);
|
||||
getLibraryNovels();
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
const redirectToNovel = (item) => {
|
||||
navigation.navigate("NovelItem", item);
|
||||
setNovel(item);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<SearchAppbar
|
||||
screen="Library"
|
||||
placeholder="Search Library"
|
||||
getNovels={getLibraryNovels}
|
||||
getSearchResults={searchLibraryNovels}
|
||||
searchText={searchText}
|
||||
setSearchText={setSearchText}
|
||||
/>
|
||||
{loading ? (
|
||||
<ActivityIndicator
|
||||
size="small"
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
) : (
|
||||
<FlatList
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
paddingBottom: 8,
|
||||
}}
|
||||
numColumns={3}
|
||||
data={novels}
|
||||
keyExtractor={(item) => item.novelUrl}
|
||||
renderItem={({ item }) => (
|
||||
<NovelCover
|
||||
item={item}
|
||||
onPress={() => redirectToNovel(item)}
|
||||
/>
|
||||
)}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
colors={["white"]}
|
||||
progressBackgroundColor={theme.colorAccentDark}
|
||||
/>
|
||||
}
|
||||
ListEmptyComponent={
|
||||
searchText !== "" ? (
|
||||
<Text
|
||||
style={[
|
||||
styles.emptySearch,
|
||||
{ color: theme.colorAccentDark },
|
||||
]}
|
||||
>
|
||||
{`"${searchText}" not in library`}
|
||||
</Text>
|
||||
) : (
|
||||
<EmptyView
|
||||
icon="Σ(ಠ_ಠ)"
|
||||
description="Your library is empty. Add series to your library from Browse."
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
theme: state.themeReducer.theme,
|
||||
novels: state.libraryReducer.novels,
|
||||
loading: state.libraryReducer.loading,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
getLibraryNovels,
|
||||
searchLibraryNovels,
|
||||
setNovel,
|
||||
})(LibraryScreen);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 4,
|
||||
},
|
||||
emptySearch: {
|
||||
marginTop: 8,
|
||||
textAlign: "center",
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, TextInput } from "react-native";
|
||||
import { Appbar } from "react-native-paper";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const LibraryAppbar = ({
|
||||
search,
|
||||
setSearch,
|
||||
getLibraryNovels,
|
||||
searchLibraryNovels,
|
||||
}) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
const { searching, searchText } = search;
|
||||
|
||||
return (
|
||||
<Appbar.Header style={{ backgroundColor: theme.colorPrimary }}>
|
||||
{!searching ? (
|
||||
<>
|
||||
<Appbar.Content
|
||||
title="Library"
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon="magnify"
|
||||
onPress={() =>
|
||||
setSearch({ ...search, searching: true })
|
||||
}
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
<Appbar.Action
|
||||
color={theme.textColorPrimary}
|
||||
icon="filter-variant"
|
||||
disabled
|
||||
// onPress={() => _panel.show({ velocity: -1.5 })}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Appbar.BackAction
|
||||
onPress={() => {
|
||||
setSearch({ searching: false, searchText: "" });
|
||||
getLibraryNovels();
|
||||
}}
|
||||
color={theme.textColorPrimary}
|
||||
size={24}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="Search..."
|
||||
defaultValue={searchText}
|
||||
style={[
|
||||
styles.searchBar,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
},
|
||||
]}
|
||||
autoFocus
|
||||
placeholderTextColor={theme.textColorHintDark}
|
||||
blurOnSubmit={true}
|
||||
onChangeText={(text) => {
|
||||
setSearch({ ...search, searchText: text });
|
||||
searchLibraryNovels(text);
|
||||
}}
|
||||
/>
|
||||
{searchText !== "" && (
|
||||
<Appbar.Action
|
||||
icon="close"
|
||||
onPress={() => {
|
||||
setSearch({ ...search, searchText: "" });
|
||||
getLibraryNovels();
|
||||
}}
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Appbar.Header>
|
||||
);
|
||||
};
|
||||
|
||||
export default LibraryAppbar;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
searchBar: { fontSize: 18, flex: 1, marginLeft: 15 },
|
||||
});
|
||||
+18
-21
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import * as Linking from "expo-linking";
|
||||
import { List, Divider } from "react-native-paper";
|
||||
|
||||
import { CustomAppbar } from "../../components/common/Appbar";
|
||||
import { Appbar } from "../../components/common/Appbar";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
@@ -11,46 +11,43 @@ const AboutScreen = ({ navigation }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomAppbar
|
||||
title="About"
|
||||
onBackAction={() => navigation.goBack()}
|
||||
/>
|
||||
<Appbar title="About" onBackAction={() => navigation.goBack()} />
|
||||
<List.Section
|
||||
style={{
|
||||
flex: 1,
|
||||
marginTop: 0,
|
||||
backgroundColor: theme.colorDarkPrimaryDark,
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
marginBottom: 0,
|
||||
}}
|
||||
>
|
||||
<List.Item
|
||||
titleStyle={{ color: theme.textColorPrimaryDark }}
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
title="Version"
|
||||
descriptionStyle={{ color: theme.textColorSecondaryDark }}
|
||||
description="Stable 1.0.17"
|
||||
descriptionStyle={{ color: theme.textColorSecondary }}
|
||||
description="Stable 1.0.6"
|
||||
/>
|
||||
<List.Item
|
||||
titleStyle={{ color: theme.textColorPrimaryDark }}
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
title="Build Time"
|
||||
descriptionStyle={{ color: theme.textColorSecondaryDark }}
|
||||
description="09-03-21 09:46 AM"
|
||||
descriptionStyle={{ color: theme.textColorSecondary }}
|
||||
description="21-03-21 11:10 AM"
|
||||
/>
|
||||
|
||||
<List.Item
|
||||
titleStyle={{ color: theme.textColorPrimaryDark }}
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
title="What's new"
|
||||
onPress={() =>
|
||||
Linking.openURL(
|
||||
"https://github.com/rajarsheechatterjee/lnreader/commits/master"
|
||||
"https://github.com/rajarsheechatterjee/lnreader/commits/main"
|
||||
)
|
||||
}
|
||||
rippleColor={theme.rippleColorDark}
|
||||
rippleColor={theme.rippleColor}
|
||||
/>
|
||||
<Divider />
|
||||
|
||||
<List.Item
|
||||
titleStyle={{ color: theme.textColorPrimaryDark }}
|
||||
descriptionStyle={{ color: theme.textColorSecondaryDark }}
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
descriptionStyle={{ color: theme.textColorSecondary }}
|
||||
title="Github"
|
||||
description="https://github.com/rajarsheechatterjee/lnreader"
|
||||
onPress={() =>
|
||||
@@ -58,11 +55,11 @@ const AboutScreen = ({ navigation }) => {
|
||||
"https://github.com/rajarsheechatterjee/lnreader"
|
||||
)
|
||||
}
|
||||
rippleColor={theme.rippleColorDark}
|
||||
rippleColor={theme.rippleColor}
|
||||
/>
|
||||
<List.Item
|
||||
titleStyle={{ color: theme.textColorPrimaryDark }}
|
||||
descriptionStyle={{ color: theme.textColorSecondaryDark }}
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
descriptionStyle={{ color: theme.textColorSecondary }}
|
||||
title="Extensions"
|
||||
description="https://github.com/rajarsheechatterjee/lnreader-extensions"
|
||||
descriptionNumberOfLines={1}
|
||||
@@ -71,7 +68,7 @@ const AboutScreen = ({ navigation }) => {
|
||||
"https://github.com/rajarsheechatterjee/lnreader-extensions"
|
||||
)
|
||||
}
|
||||
rippleColor={theme.rippleColorDark}
|
||||
rippleColor={theme.rippleColor}
|
||||
/>
|
||||
</List.Section>
|
||||
</>
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { List } from "react-native-paper";
|
||||
|
||||
import { CustomAppbar } from "../../components/common/Appbar";
|
||||
import { Appbar } from "../../components/common/Appbar";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
@@ -12,17 +12,17 @@ const MoreScreen = ({ navigation }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomAppbar title="More" />
|
||||
<Appbar title="More" />
|
||||
<List.Section
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: theme.colorDarkPrimaryDark,
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
}}
|
||||
>
|
||||
<List.Item
|
||||
titleStyle={{ color: theme.textColorPrimaryDark }}
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
title="Settings"
|
||||
left={() => (
|
||||
<List.Icon
|
||||
@@ -33,7 +33,7 @@ const MoreScreen = ({ navigation }) => {
|
||||
onPress={() => navigation.navigate("Settings")}
|
||||
/>
|
||||
<List.Item
|
||||
titleStyle={{ color: theme.textColorPrimaryDark }}
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
title="About"
|
||||
left={() => (
|
||||
<List.Icon
|
||||
|
||||
+116
-114
@@ -1,63 +1,72 @@
|
||||
import React, { useState } from "react";
|
||||
import { View, ToastAndroid } from "react-native";
|
||||
import { View } from "react-native";
|
||||
import { setStatusBarStyle } from "expo-status-bar";
|
||||
import { List } from "react-native-paper";
|
||||
import { List, Modal, Portal } from "react-native-paper";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { switchTheme } from "../../redux/actions/theme";
|
||||
import {
|
||||
amoledDarkTheme,
|
||||
darkTheme,
|
||||
midnightDuskTheme,
|
||||
lightTheme,
|
||||
} from "../../theme/theme";
|
||||
import { setDisplayMode, setItemsPerRow } from "../../redux/actions/settings";
|
||||
|
||||
import { CustomAppbar } from "../../components/common/Appbar";
|
||||
import { Appbar } from "../../components/common/Appbar";
|
||||
import {
|
||||
DisplayCheckbox,
|
||||
ThemeCheckbox,
|
||||
} from "../../components/settings/Checkbox";
|
||||
|
||||
import {
|
||||
setAppTheme,
|
||||
getAppTheme,
|
||||
getDisplayMode,
|
||||
setDisplayMode,
|
||||
} from "../../services/asyncStorage";
|
||||
|
||||
import {
|
||||
deleteHistory,
|
||||
deleteNovelsNotInLibrary,
|
||||
deleteDatabase,
|
||||
} from "../../services/db";
|
||||
|
||||
const SettingsScreen = ({ navigation, theme, switchTheme }) => {
|
||||
const desciptionStyles = {
|
||||
color: theme.textColorSecondaryDark,
|
||||
const SettingsScreen = ({
|
||||
navigation,
|
||||
theme,
|
||||
themeCode,
|
||||
displayMode,
|
||||
switchTheme,
|
||||
setDisplayMode,
|
||||
}) => {
|
||||
const display = {
|
||||
0: "Compact",
|
||||
1: "Comfortable",
|
||||
};
|
||||
|
||||
const titleStyles = { color: theme.textColorPrimaryDark };
|
||||
const themes = [
|
||||
{ themeCode: 0, label: "AMOLED Dark Theme", statusBar: "light" },
|
||||
{ themeCode: 1, label: "Light Theme", statusBar: "dark" },
|
||||
{ themeCode: 2, label: "Dark Theme", statusBar: "light" },
|
||||
{ themeCode: 3, label: "Midnight Dusk Theme", statusBar: "light" },
|
||||
];
|
||||
|
||||
const [displayMode, setDisplay] = useState("compact");
|
||||
const [applicationTheme, setApplicationTheme] = useState("amoledDarkTheme");
|
||||
const desciptionStyles = {
|
||||
color: theme.textColorSecondary,
|
||||
};
|
||||
|
||||
getAppTheme().then((res) => setApplicationTheme(res));
|
||||
const titleStyles = { color: theme.textColorPrimary };
|
||||
|
||||
getDisplayMode().then((res) => setDisplay(res));
|
||||
/**
|
||||
* Display Mode Modal
|
||||
*/
|
||||
const [displayModalVisible, setDisplayModalVisible] = useState(false);
|
||||
const showDisplayModal = () => setDisplayModalVisible(true);
|
||||
const hideDisplayModal = () => setDisplayModalVisible(false);
|
||||
|
||||
/**
|
||||
* Change Theme Modal
|
||||
*/
|
||||
const [themeModalVisible, setthemeModalVisible] = useState(false);
|
||||
const showthemeModal = () => setthemeModalVisible(true);
|
||||
const hidethemeModal = () => setthemeModalVisible(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomAppbar
|
||||
title="Settings"
|
||||
onBackAction={() => navigation.goBack()}
|
||||
/>
|
||||
<View
|
||||
style={{ flex: 1, backgroundColor: theme.colorDarkPrimaryDark }}
|
||||
>
|
||||
<Appbar title="Settings" onBackAction={() => navigation.goBack()} />
|
||||
<View style={{ flex: 1, backgroundColor: theme.colorPrimaryDark }}>
|
||||
<List.Section
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: theme.colorDarkPrimaryDark,
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
}}
|
||||
>
|
||||
<List.Subheader
|
||||
@@ -74,7 +83,7 @@ const SettingsScreen = ({ navigation, theme, switchTheme }) => {
|
||||
descriptionStyle={desciptionStyles}
|
||||
description="Delete history for novels not in your library"
|
||||
onPress={() => deleteNovelsNotInLibrary()}
|
||||
rippleColor={theme.rippleColorDark}
|
||||
rippleColor={theme.rippleColor}
|
||||
/>
|
||||
<List.Item
|
||||
titleStyle={titleStyles}
|
||||
@@ -82,7 +91,7 @@ const SettingsScreen = ({ navigation, theme, switchTheme }) => {
|
||||
descriptionStyle={desciptionStyles}
|
||||
description="Delete reading history for all novels"
|
||||
onPress={() => deleteHistory()}
|
||||
rippleColor={theme.rippleColorDark}
|
||||
rippleColor={theme.rippleColor}
|
||||
/>
|
||||
<List.Subheader
|
||||
style={{
|
||||
@@ -92,26 +101,37 @@ const SettingsScreen = ({ navigation, theme, switchTheme }) => {
|
||||
>
|
||||
Display
|
||||
</List.Subheader>
|
||||
<View>
|
||||
<DisplayCheckbox
|
||||
value="comfortable"
|
||||
displayMode={displayMode}
|
||||
onPress={() => {
|
||||
setDisplay("comfortable");
|
||||
setDisplayMode("comfortable");
|
||||
<List.Item
|
||||
titleStyle={titleStyles}
|
||||
title="Display Mode"
|
||||
descriptionStyle={desciptionStyles}
|
||||
description={display[displayMode]}
|
||||
onPress={showDisplayModal}
|
||||
rippleColor={theme.rippleColor}
|
||||
/>
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={displayModalVisible}
|
||||
onDismiss={hideDisplayModal}
|
||||
contentContainerStyle={{
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
padding: 20,
|
||||
margin: 20,
|
||||
borderRadius: 6,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<DisplayCheckbox
|
||||
value="compact"
|
||||
displayMode={displayMode}
|
||||
onPress={() => {
|
||||
setDisplay("compact");
|
||||
setDisplayMode("compact");
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
>
|
||||
<DisplayCheckbox
|
||||
value={1}
|
||||
displayMode={displayMode}
|
||||
onPress={() => setDisplayMode(1)}
|
||||
/>
|
||||
<DisplayCheckbox
|
||||
value={0}
|
||||
displayMode={displayMode}
|
||||
onPress={() => setDisplayMode(0)}
|
||||
/>
|
||||
</Modal>
|
||||
</Portal>
|
||||
{/* <List.Subheader
|
||||
style={{
|
||||
color: theme.colorAccentDark,
|
||||
@@ -125,7 +145,7 @@ const SettingsScreen = ({ navigation, theme, switchTheme }) => {
|
||||
title="Delete database"
|
||||
descriptionStyle={desciptionStyles}
|
||||
description="Delete entire database"
|
||||
rippleColor={theme.rippleColorDark}
|
||||
rippleColor={theme.rippleColor}
|
||||
onPress={() => deleteDatabase()}
|
||||
/> */}
|
||||
|
||||
@@ -137,66 +157,42 @@ const SettingsScreen = ({ navigation, theme, switchTheme }) => {
|
||||
>
|
||||
Theme
|
||||
</List.Subheader>
|
||||
<View>
|
||||
<ThemeCheckbox
|
||||
label="Light Theme"
|
||||
checked={
|
||||
applicationTheme === "lightTheme" ? true : false
|
||||
}
|
||||
onPress={() => {
|
||||
switchTheme(lightTheme);
|
||||
setApplicationTheme(`lightTheme`);
|
||||
setAppTheme("lightTheme");
|
||||
setStatusBarStyle("dark");
|
||||
<List.Item
|
||||
titleStyle={titleStyles}
|
||||
title="Theme"
|
||||
descriptionStyle={desciptionStyles}
|
||||
description={themes[themeCode].label}
|
||||
onPress={showthemeModal}
|
||||
rippleColor={theme.rippleColor}
|
||||
/>
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={themeModalVisible}
|
||||
onDismiss={hidethemeModal}
|
||||
contentContainerStyle={{
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
padding: 20,
|
||||
margin: 20,
|
||||
borderRadius: 6,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<ThemeCheckbox
|
||||
label="Dark Theme"
|
||||
checked={
|
||||
applicationTheme === "darkTheme" ? true : false
|
||||
}
|
||||
onPress={() => {
|
||||
switchTheme(darkTheme);
|
||||
setApplicationTheme("darkTheme");
|
||||
setAppTheme("darkTheme");
|
||||
setStatusBarStyle("light");
|
||||
}}
|
||||
/>
|
||||
<View>
|
||||
<ThemeCheckbox
|
||||
label="Amoled Dark Theme"
|
||||
checked={
|
||||
applicationTheme === "amoledDarkTheme"
|
||||
? true
|
||||
: false
|
||||
}
|
||||
onPress={() => {
|
||||
switchTheme(amoledDarkTheme);
|
||||
setApplicationTheme(`amoledDarkTheme`);
|
||||
setAppTheme("amoledDarkTheme");
|
||||
setStatusBarStyle("light");
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<ThemeCheckbox
|
||||
label="Midnight Dusk Theme"
|
||||
checked={
|
||||
applicationTheme === "midnightDuskTheme"
|
||||
? true
|
||||
: false
|
||||
}
|
||||
onPress={() => {
|
||||
switchTheme(midnightDuskTheme);
|
||||
setApplicationTheme("midnightDuskTheme");
|
||||
setAppTheme("midnightDuskTheme");
|
||||
setStatusBarStyle("light");
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
>
|
||||
{themes.map((item, index) => (
|
||||
<ThemeCheckbox
|
||||
key={index}
|
||||
label={themes[item.themeCode].label}
|
||||
checked={
|
||||
themeCode === item.themeCode
|
||||
? true
|
||||
: false
|
||||
}
|
||||
onPress={() => {
|
||||
switchTheme(item.themeCode);
|
||||
setStatusBarStyle(item.statusBar);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Modal>
|
||||
</Portal>
|
||||
</List.Section>
|
||||
</View>
|
||||
</>
|
||||
@@ -204,7 +200,13 @@ const SettingsScreen = ({ navigation, theme, switchTheme }) => {
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
themeCode: state.themeReducer.themeCode,
|
||||
theme: state.themeReducer.theme,
|
||||
displayMode: state.settingsReducer.displayMode,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { switchTheme })(SettingsScreen);
|
||||
export default connect(mapStateToProps, {
|
||||
switchTheme,
|
||||
setDisplayMode,
|
||||
setItemsPerRow,
|
||||
})(SettingsScreen);
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
FlatList,
|
||||
RefreshControl,
|
||||
ToastAndroid,
|
||||
} from "react-native";
|
||||
import { Provider, Portal } from "react-native-paper";
|
||||
|
||||
import ChapterCard from "./components/ChapterCard";
|
||||
import NovelInfoHeader from "./components/NovelHeader";
|
||||
import { BottomSheet } from "./components/BottomSheet";
|
||||
|
||||
import { downloadOrDeleteChapter } from "../../services/db";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import {
|
||||
getNovel,
|
||||
insertNovelInLibrary,
|
||||
sortAndFilterChapters,
|
||||
} from "../../redux/actions/novel";
|
||||
|
||||
const Novel = ({
|
||||
route,
|
||||
theme,
|
||||
novel,
|
||||
chapters,
|
||||
loading,
|
||||
getNovel,
|
||||
fetching,
|
||||
insertNovelInLibrary,
|
||||
sortAndFilterChapters,
|
||||
}) => {
|
||||
const {
|
||||
extensionId,
|
||||
novelUrl,
|
||||
novelName,
|
||||
novelCover,
|
||||
libraryStatus,
|
||||
} = route.params;
|
||||
|
||||
const [downloading, setDownloading] = useState({
|
||||
downloading: false,
|
||||
chapterUrl: "",
|
||||
});
|
||||
|
||||
let _panel = useRef(null); // Bottomsheet ref
|
||||
|
||||
/**
|
||||
* Download chapter from source
|
||||
*
|
||||
* If downloaded then delete from db
|
||||
*/
|
||||
|
||||
const downloadChapter = (
|
||||
downloadStatus,
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterUrl,
|
||||
chapterName
|
||||
) => {
|
||||
setDownloading({ downloading: true, chapterUrl: chapterUrl });
|
||||
downloadOrDeleteChapter(
|
||||
downloadStatus,
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterUrl
|
||||
).then((res) => {
|
||||
setDownloading({ downloading: false });
|
||||
ToastAndroid.show(
|
||||
!downloadStatus
|
||||
? `Downloaded ${chapterName}`
|
||||
: `Deleted ${chapterName}`,
|
||||
ToastAndroid.SHORT
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getNovel(libraryStatus, extensionId, novelUrl);
|
||||
}, []);
|
||||
|
||||
const renderChapterCard = ({ item }) => (
|
||||
<ChapterCard
|
||||
novelUrl={novelUrl}
|
||||
extensionId={extensionId}
|
||||
chapter={item}
|
||||
downloadChapter={downloadChapter}
|
||||
downloading={downloading}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<FlatList
|
||||
data={!loading && chapters}
|
||||
keyExtractor={(item) => item.chapterUrl}
|
||||
removeClippedSubviews={true}
|
||||
maxToRenderPerBatch={5}
|
||||
windowSize={15}
|
||||
initialNumToRender={7}
|
||||
renderItem={renderChapterCard}
|
||||
ListHeaderComponent={
|
||||
<NovelInfoHeader
|
||||
theme={theme}
|
||||
item={{ novelName, novelCover }}
|
||||
novel={novel}
|
||||
noOfChapters={chapters.length}
|
||||
insertNovelInLibrary={insertNovelInLibrary}
|
||||
loading={loading}
|
||||
bottomSheetRef={_panel}
|
||||
/>
|
||||
}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={fetching}
|
||||
// onRefresh={onRefresh}
|
||||
colors={[theme.textColorPrimary]}
|
||||
progressBackgroundColor={theme.colorPrimary}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Portal>
|
||||
<BottomSheet
|
||||
novelUrl={novelUrl}
|
||||
bottomSheetRef={_panel}
|
||||
sortAndFilterChapters={sortAndFilterChapters}
|
||||
/>
|
||||
</Portal>
|
||||
</View>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
const mapStateToProps = (state) => ({
|
||||
theme: state.themeReducer.theme,
|
||||
novel: state.novelReducer.novel,
|
||||
chapters: state.novelReducer.chapters,
|
||||
loading: state.novelReducer.loading,
|
||||
fetching: state.novelReducer.fetching,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
getNovel,
|
||||
insertNovelInLibrary,
|
||||
sortAndFilterChapters,
|
||||
})(Novel);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
@@ -1,32 +1,29 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { StyleSheet, View, Text } from "react-native";
|
||||
import { List, RadioButton, Checkbox } from "react-native-paper";
|
||||
import Bottomsheet from "rn-sliding-up-panel";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const BottomSheetHandle = () => (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "rgba(255,255,255,0.25)",
|
||||
height: 4,
|
||||
width: 50,
|
||||
borderRadius: 50,
|
||||
top: 10,
|
||||
alignSelf: "center",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export const BottomSheet = ({
|
||||
bottomSheetRef,
|
||||
sort,
|
||||
filter,
|
||||
sortChapters,
|
||||
filterChapters,
|
||||
sortAndFilterChapters,
|
||||
novelUrl,
|
||||
}) => {
|
||||
const checkBoxStyle = { flexDirection: "row", alignItems: "center" };
|
||||
|
||||
const [chapterFlags, setChapterFlags] = useState({ sort: "", filter: "" });
|
||||
|
||||
const sortChapters = (sort) => {
|
||||
setChapterFlags({ ...chapterFlags, sort: sort });
|
||||
sortAndFilterChapters(novelUrl, chapterFlags.filter, chapterFlags.sort);
|
||||
};
|
||||
|
||||
const filterChapters = (filter) => {
|
||||
setChapterFlags({ ...chapterFlags, filter: filter });
|
||||
sortAndFilterChapters(novelUrl, chapterFlags.filter, chapterFlags.sort);
|
||||
};
|
||||
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
return (
|
||||
@@ -39,14 +36,14 @@ export const BottomSheet = ({
|
||||
style={[
|
||||
styles.contentContainer,
|
||||
{
|
||||
backgroundColor: theme.colorDarkPrimary,
|
||||
backgroundColor: theme.colorPrimary,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<BottomSheetHandle />
|
||||
<View style={styles.bottomSheetHandle} />
|
||||
<List.Subheader
|
||||
style={{
|
||||
color: theme.textColorPrimaryDark,
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 20,
|
||||
}}
|
||||
>
|
||||
@@ -55,7 +52,7 @@ export const BottomSheet = ({
|
||||
<View>
|
||||
<RadioButton.Group
|
||||
onValueChange={(newValue) => sortChapters(newValue)}
|
||||
value={sort}
|
||||
value={chapterFlags.sort}
|
||||
c
|
||||
>
|
||||
<View
|
||||
@@ -77,7 +74,7 @@ export const BottomSheet = ({
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimaryDark,
|
||||
color: theme.textColorPrimary,
|
||||
}}
|
||||
>
|
||||
Newest to oldest
|
||||
@@ -96,7 +93,7 @@ export const BottomSheet = ({
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimaryDark,
|
||||
color: theme.textColorPrimary,
|
||||
}}
|
||||
>
|
||||
Oldest to newest
|
||||
@@ -108,7 +105,7 @@ export const BottomSheet = ({
|
||||
|
||||
<List.Subheader
|
||||
style={{
|
||||
color: theme.textColorPrimaryDark,
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 20,
|
||||
paddingVertical: 10,
|
||||
}}
|
||||
@@ -117,45 +114,51 @@ export const BottomSheet = ({
|
||||
</List.Subheader>
|
||||
<View style={checkBoxStyle}>
|
||||
<Checkbox.Item
|
||||
status={filter === "" ? "checked" : "unchecked"}
|
||||
status={
|
||||
chapterFlags.filter === "" ? "checked" : "unchecked"
|
||||
}
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => filterChapters("")}
|
||||
/>
|
||||
<Text style={{ color: theme.textColorPrimaryDark }}>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Show all
|
||||
</Text>
|
||||
</View>
|
||||
<View style={checkBoxStyle}>
|
||||
<Checkbox.Item
|
||||
status={
|
||||
filter === "AND `read`=1" ? "checked" : "unchecked"
|
||||
chapterFlags.filter === "AND `read`=1"
|
||||
? "checked"
|
||||
: "unchecked"
|
||||
}
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => filterChapters("AND `read`=1")}
|
||||
/>
|
||||
<Text style={{ color: theme.textColorPrimaryDark }}>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Show read chapters
|
||||
</Text>
|
||||
</View>
|
||||
<View style={checkBoxStyle}>
|
||||
<Checkbox.Item
|
||||
status={
|
||||
filter === "AND `read`=0" ? "checked" : "unchecked"
|
||||
chapterFlags.filter === "AND `read`=0"
|
||||
? "checked"
|
||||
: "unchecked"
|
||||
}
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => filterChapters("AND `read`=0")}
|
||||
/>
|
||||
<Text style={{ color: theme.textColorPrimaryDark }}>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Show unread chapters
|
||||
</Text>
|
||||
</View>
|
||||
<View style={checkBoxStyle}>
|
||||
<Checkbox.Item
|
||||
status={
|
||||
filter === "AND downloaded=1"
|
||||
chapterFlags.filter === "AND downloaded=1"
|
||||
? "checked"
|
||||
: "unchecked"
|
||||
}
|
||||
@@ -163,7 +166,7 @@ export const BottomSheet = ({
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => filterChapters("AND downloaded=1")}
|
||||
/>
|
||||
<Text style={{ color: theme.textColorPrimaryDark }}>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Show downloaded chapters
|
||||
</Text>
|
||||
</View>
|
||||
@@ -175,5 +178,15 @@ export const BottomSheet = ({
|
||||
const styles = StyleSheet.create({
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
borderTopRightRadius: 8,
|
||||
borderTopLeftRadius: 8,
|
||||
},
|
||||
bottomSheetHandle: {
|
||||
backgroundColor: "rgba(255,255,255,0.25)",
|
||||
height: 4,
|
||||
width: 50,
|
||||
borderRadius: 50,
|
||||
top: 10,
|
||||
alignSelf: "center",
|
||||
},
|
||||
});
|
||||
@@ -1,12 +1,11 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { View, Text, ActivityIndicator } from "react-native";
|
||||
import { View, Text, ActivityIndicator, StyleSheet } from "react-native";
|
||||
import { TouchableRipple, IconButton } from "react-native-paper";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const ChapterCard = ({
|
||||
navigation,
|
||||
novelUrl,
|
||||
chapter,
|
||||
downloadChapter,
|
||||
@@ -17,17 +16,11 @@ const ChapterCard = ({
|
||||
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
const navigation = useNavigation();
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
style={{
|
||||
paddingHorizontal: 15,
|
||||
paddingVertical: 7,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
// borderTopColor: "rgba(255,255,255,.12)",
|
||||
// borderTopWidth: 1,
|
||||
}}
|
||||
style={styles.chapterCardContainer}
|
||||
onPress={() =>
|
||||
navigation.navigate("ChapterItem", {
|
||||
chapterUrl: chapter.chapterUrl,
|
||||
@@ -36,14 +29,14 @@ const ChapterCard = ({
|
||||
chapterName: chapter.chapterName,
|
||||
})
|
||||
}
|
||||
rippleColor={theme.rippleColorDark}
|
||||
rippleColor={theme.rippleColor}
|
||||
>
|
||||
<>
|
||||
<View>
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
color: theme.textColorPrimaryDark,
|
||||
color: theme.textColorPrimary,
|
||||
},
|
||||
chapter.read === 1 && {
|
||||
color: theme.textColorHintDark,
|
||||
@@ -57,7 +50,7 @@ const ChapterCard = ({
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
color: theme.textColorSecondaryDark,
|
||||
color: theme.textColorSecondary,
|
||||
marginTop: 5,
|
||||
fontSize: 12,
|
||||
},
|
||||
@@ -89,7 +82,7 @@ const ChapterCard = ({
|
||||
animated
|
||||
color={
|
||||
downloaded
|
||||
? theme.textColorPrimaryDark
|
||||
? theme.textColorPrimary
|
||||
: theme.textColorHintDark
|
||||
}
|
||||
size={25}
|
||||
@@ -113,3 +106,13 @@ const ChapterCard = ({
|
||||
};
|
||||
|
||||
export default ChapterCard;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
chapterCardContainer: {
|
||||
paddingHorizontal: 15,
|
||||
paddingVertical: 7,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
},
|
||||
});
|
||||
+111
-113
@@ -15,21 +15,20 @@ import * as WebBrowser from "expo-web-browser";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
const NovelInfoHeader = ({
|
||||
item,
|
||||
novel,
|
||||
noOfChapters,
|
||||
insertNovelInLib,
|
||||
insertNovelInLibrary,
|
||||
loading,
|
||||
navigatingFrom,
|
||||
libraryStatus,
|
||||
bottomSheetRef,
|
||||
theme,
|
||||
}) => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const [more, setMore] = useState(navigatingFrom === 2 ? true : false);
|
||||
const [more, setMore] = useState(
|
||||
!loading && novel.libraryStatus !== 1 ? true : false
|
||||
);
|
||||
|
||||
const renderGenreChip = ({ item }) => (
|
||||
<Text
|
||||
@@ -38,7 +37,7 @@ const NovelInfoHeader = ({
|
||||
{
|
||||
color: theme.colorAccentDark,
|
||||
borderColor: theme.colorAccentDark,
|
||||
backgroundColor: theme.colorDarkPrimary,
|
||||
backgroundColor: theme.colorPrimary,
|
||||
},
|
||||
]}
|
||||
>
|
||||
@@ -47,23 +46,19 @@ const NovelInfoHeader = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={{ flexGrow: 1 }}>
|
||||
<ImageBackground
|
||||
source={{
|
||||
uri: item.novelCover,
|
||||
}}
|
||||
source={{ uri: item.novelCover }}
|
||||
style={styles.background}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={["rgba(0,0,0,0.2)", theme.colorDarkPrimaryDark]}
|
||||
colors={["rgba(0,0,0,0.2)", theme.colorPrimaryDark]}
|
||||
style={styles.linearGradient}
|
||||
>
|
||||
<View style={styles.detailsContainer}>
|
||||
<View>
|
||||
<Image
|
||||
source={{
|
||||
uri: item.novelCover,
|
||||
}}
|
||||
source={{ uri: item.novelCover }}
|
||||
style={styles.logo}
|
||||
/>
|
||||
</View>
|
||||
@@ -72,9 +67,7 @@ const NovelInfoHeader = ({
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.name,
|
||||
{
|
||||
color: theme.textColorPrimaryDark,
|
||||
},
|
||||
{ color: theme.textColorPrimary },
|
||||
]}
|
||||
>
|
||||
{item.novelName}
|
||||
@@ -83,22 +76,18 @@ const NovelInfoHeader = ({
|
||||
<>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondaryDark,
|
||||
color: theme.textColorSecondary,
|
||||
marginVertical: 3,
|
||||
fontSize: 14,
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
{novel["Author(s)"] &&
|
||||
novel["Author(s)"].replace(
|
||||
",",
|
||||
", "
|
||||
)}
|
||||
{novel["Author(s)"].replace(",", ", ")}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondaryDark,
|
||||
color: theme.textColorSecondary,
|
||||
marginVertical: 3,
|
||||
fontSize: 14,
|
||||
}}
|
||||
@@ -108,8 +97,7 @@ const NovelInfoHeader = ({
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondaryDark,
|
||||
// marginVertical: 3,
|
||||
color: theme.textColorSecondary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
@@ -124,19 +112,13 @@ const NovelInfoHeader = ({
|
||||
</ImageBackground>
|
||||
{!loading && (
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<View style={styles.buttonsContainer}>
|
||||
<Chip
|
||||
mode="outlined"
|
||||
icon={() => (
|
||||
<MaterialCommunityIcons
|
||||
name={
|
||||
libraryStatus
|
||||
novel.libraryStatus
|
||||
? "heart"
|
||||
: "heart-outline"
|
||||
}
|
||||
@@ -144,31 +126,24 @@ const NovelInfoHeader = ({
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
)}
|
||||
onPress={() => insertNovelInLib()}
|
||||
onPress={() =>
|
||||
insertNovelInLibrary(
|
||||
novel.libraryStatus,
|
||||
novel.novelUrl
|
||||
)
|
||||
}
|
||||
style={[
|
||||
{
|
||||
backgroundColor: theme.colorDarkPrimaryDark,
|
||||
marginRight: 2,
|
||||
// borderColor: "rgba(255,255,255,0.121)",
|
||||
// borderWidth: 1,
|
||||
justifyContent: "center",
|
||||
height: 30,
|
||||
alignItems: "center",
|
||||
marginLeft: 15,
|
||||
paddingHorizontal: 3,
|
||||
shadowOpacity: 0,
|
||||
shadowOffset: 0,
|
||||
},
|
||||
// libraryStatus && {
|
||||
// backgroundColor: "rgba(41,121,255,0.38)",
|
||||
// },
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
styles.toggleFavourite,
|
||||
]}
|
||||
textStyle={{
|
||||
fontWeight: "bold",
|
||||
color: theme.textColorPrimaryDark,
|
||||
color: theme.textColorPrimary,
|
||||
}}
|
||||
>
|
||||
{libraryStatus ? "In Library" : "Add to library"}
|
||||
{novel.libraryStatus
|
||||
? "In Library"
|
||||
: "Add to library"}
|
||||
</Chip>
|
||||
|
||||
<IconButton
|
||||
@@ -192,28 +167,25 @@ const NovelInfoHeader = ({
|
||||
size={21}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{novel.novelSummary !== "" && (
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 15,
|
||||
marginBottom: 10,
|
||||
// marginTop: 5,
|
||||
paddingHorizontal: 16,
|
||||
marginBottom: 8,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimaryDark,
|
||||
marginTop: 5,
|
||||
paddingVertical: 5,
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
style={[
|
||||
{ color: theme.textColorPrimary },
|
||||
styles.summaryHeader,
|
||||
]}
|
||||
>
|
||||
About
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondaryDark,
|
||||
color: theme.textColorSecondary,
|
||||
lineHeight: 20,
|
||||
}}
|
||||
numberOfLines={more ? 100 : 2}
|
||||
@@ -223,15 +195,13 @@ const NovelInfoHeader = ({
|
||||
{novel.novelSummary}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.colorAccentDark,
|
||||
fontWeight: "bold",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
right: 15,
|
||||
backgroundColor: theme.colorDarkPrimaryDark,
|
||||
paddingLeft: 5,
|
||||
}}
|
||||
style={[
|
||||
{
|
||||
color: theme.colorAccentDark,
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
},
|
||||
styles.moreBtn,
|
||||
]}
|
||||
onPress={() => setMore(!more)}
|
||||
>
|
||||
{more ? "Less " : "More "}
|
||||
@@ -243,12 +213,9 @@ const NovelInfoHeader = ({
|
||||
style={
|
||||
novel.novelSummary === ""
|
||||
? { marginTop: 20 }
|
||||
: { marginTop: 10 }
|
||||
: { marginTop: 12 }
|
||||
}
|
||||
contentContainerStyle={{
|
||||
paddingHorizontal: 15,
|
||||
paddingBottom: 15,
|
||||
}}
|
||||
contentContainerStyle={styles.genreContainer}
|
||||
horizontal
|
||||
data={novel["Genre(s)"] && novel["Genre(s)"].split(",")}
|
||||
keyExtractor={(item) => item}
|
||||
@@ -258,12 +225,10 @@ const NovelInfoHeader = ({
|
||||
|
||||
<Button
|
||||
color="white"
|
||||
style={{
|
||||
backgroundColor: theme.colorAccentDark,
|
||||
marginHorizontal: 15,
|
||||
marginTop: 8,
|
||||
marginBottom: 15,
|
||||
}}
|
||||
style={[
|
||||
{ backgroundColor: theme.colorAccentDark },
|
||||
styles.startButton,
|
||||
]}
|
||||
uppercase={false}
|
||||
labelStyle={{ letterSpacing: 0 }}
|
||||
onPress={() =>
|
||||
@@ -275,73 +240,58 @@ const NovelInfoHeader = ({
|
||||
})
|
||||
}
|
||||
>
|
||||
{novel.unread && novel.unread === 1
|
||||
{novel.unread === 1
|
||||
? `Start reading ${novel.lastReadName}`
|
||||
: `Continue reading ${novel.lastReadName}`}
|
||||
</Button>
|
||||
|
||||
<TouchableRipple
|
||||
style={{
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
paddingRight: 12.5,
|
||||
}}
|
||||
style={styles.bottomsheet}
|
||||
onPress={() =>
|
||||
bottomSheetRef.current.show({ velocity: -1.5 })
|
||||
}
|
||||
rippleColor={theme.rippleColorDark}
|
||||
rippleColor={theme.rippleColor}
|
||||
>
|
||||
<>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimaryDark,
|
||||
paddingHorizontal: 15,
|
||||
paddingVertical: 5,
|
||||
fontSize: 16,
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
style={[
|
||||
{ color: theme.textColorPrimary },
|
||||
styles.chapters,
|
||||
]}
|
||||
>
|
||||
{noOfChapters + " Chapters "}
|
||||
{noOfChapters + " Chapters"}
|
||||
</Text>
|
||||
<IconButton
|
||||
icon="filter-variant"
|
||||
color={theme.textColorPrimaryDark}
|
||||
color={theme.textColorPrimary}
|
||||
size={24}
|
||||
/>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
theme: state.themeReducer.theme,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(NovelInfoHeader);
|
||||
export default NovelInfoHeader;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
nameContainer: {
|
||||
flex: 1,
|
||||
marginHorizontal: 15,
|
||||
paddingTop: 10,
|
||||
marginHorizontal: 16,
|
||||
paddingTop: 8,
|
||||
},
|
||||
background: {
|
||||
height: 285,
|
||||
// backgroundColor: theme.colorDarkPrimaryDark,
|
||||
},
|
||||
linearGradient: {
|
||||
// flex: 1,
|
||||
height: 286,
|
||||
},
|
||||
detailsContainer: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
margin: 15,
|
||||
margin: 16,
|
||||
paddingTop: 90,
|
||||
},
|
||||
logo: {
|
||||
@@ -364,4 +314,52 @@ const styles = StyleSheet.create({
|
||||
fontWeight: "bold",
|
||||
fontSize: 18,
|
||||
},
|
||||
buttonsContainer: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
},
|
||||
toggleFavourite: {
|
||||
height: 32,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginLeft: 16,
|
||||
marginRight: 4,
|
||||
paddingHorizontal: 4,
|
||||
borderWidth: 0,
|
||||
elevation: 0,
|
||||
},
|
||||
chapters: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 4,
|
||||
fontSize: 16,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
bottomsheet: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
paddingRight: 12,
|
||||
},
|
||||
summaryHeader: {
|
||||
marginTop: 5,
|
||||
paddingVertical: 5,
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
moreBtn: {
|
||||
fontWeight: "bold",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
right: 16,
|
||||
paddingLeft: 6,
|
||||
},
|
||||
startButton: {
|
||||
marginTop: 8,
|
||||
marginBottom: 16,
|
||||
marginHorizontal: 16,
|
||||
},
|
||||
genreContainer: {
|
||||
paddingHorizontal: 15,
|
||||
paddingBottom: 15,
|
||||
},
|
||||
});
|
||||
@@ -1,252 +0,0 @@
|
||||
import React, { useState, useCallback, useRef } from "react";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
FlatList,
|
||||
RefreshControl,
|
||||
ToastAndroid,
|
||||
} from "react-native";
|
||||
import { Provider, Portal } from "react-native-paper";
|
||||
|
||||
import ChapterCard from "../components/novel/ChapterCard";
|
||||
import NovelInfoHeader from "../components/novel/NovelHeader";
|
||||
import { BottomSheet } from "../components/novel/BottomSheet";
|
||||
|
||||
import { fetchNovelFromSource, fetchChaptersFromSource } from "../services/api";
|
||||
import {
|
||||
insertNovelInfoInDb,
|
||||
insertChaptersInDb,
|
||||
getChaptersFromDb,
|
||||
checkNovelInDb,
|
||||
getNovelInfoFromDb,
|
||||
toggleFavourite,
|
||||
downloadOrDeleteChapter,
|
||||
} from "../services/db";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const NovelItem = ({ route, navigation }) => {
|
||||
const item = route.params;
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
const {
|
||||
extensionId,
|
||||
novelUrl,
|
||||
navigatingFrom,
|
||||
novelName,
|
||||
novelCover,
|
||||
} = route.params;
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
const [novel, setNovel] = useState(item);
|
||||
const [chapters, setChapters] = useState();
|
||||
|
||||
const [libraryStatus, setlibraryStatus] = useState(item.libraryStatus);
|
||||
|
||||
const [sort, setSort] = useState("");
|
||||
const [filter, setFilter] = useState("");
|
||||
|
||||
const [downloading, setDownloading] = useState({
|
||||
downloading: false,
|
||||
chapterUrl: "",
|
||||
});
|
||||
|
||||
let _panel = useRef(null); // Bottomsheet ref
|
||||
|
||||
/**
|
||||
* Fetch novel and chapters from source and set loading false
|
||||
*/
|
||||
|
||||
const getNovelFromSource = async (extensionId, novelUrl) => {
|
||||
await fetchNovelFromSource(extensionId, novelUrl).then((response) => {
|
||||
setNovel(response);
|
||||
insertNovelInfoInDb(response);
|
||||
});
|
||||
await fetchChaptersFromSource(extensionId, novelUrl).then(
|
||||
(response) => {
|
||||
setChapters(response);
|
||||
insertChaptersInDb(novelUrl, response);
|
||||
}
|
||||
);
|
||||
setlibraryStatus(0);
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get chapters from db and set loading false
|
||||
*/
|
||||
|
||||
const getChapters = async (novelUrl) => {
|
||||
await getChaptersFromDb(novelUrl, filter, sort).then((response) =>
|
||||
setChapters(response)
|
||||
);
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get novel and chapters from database
|
||||
*/
|
||||
|
||||
const getNovelFromDb = async () => {
|
||||
await getNovelInfoFromDb(novelUrl).then((res) => {
|
||||
setNovel(res);
|
||||
setlibraryStatus(res.libraryStatus);
|
||||
});
|
||||
getChapters(novelUrl);
|
||||
};
|
||||
|
||||
/**
|
||||
* Insert or remove novel from library
|
||||
*/
|
||||
|
||||
const insertNovelInLib = async () => {
|
||||
toggleFavourite(libraryStatus, novelUrl).then((res) => {
|
||||
setlibraryStatus(res);
|
||||
ToastAndroid.show(
|
||||
res ? "Added to library" : "Removed from library",
|
||||
ToastAndroid.SHORT
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Download chapter from source
|
||||
*
|
||||
* If downloaded then delete from db
|
||||
*/
|
||||
|
||||
const downloadChapter = (
|
||||
downloadStatus,
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterUrl,
|
||||
chapterName
|
||||
) => {
|
||||
setDownloading({ downloading: true, chapterUrl: chapterUrl });
|
||||
downloadOrDeleteChapter(
|
||||
downloadStatus,
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterUrl
|
||||
).then((res) => {
|
||||
setDownloading({ downloading: false });
|
||||
ToastAndroid.show(
|
||||
!downloadStatus
|
||||
? `Downloaded ${chapterName}`
|
||||
: `Deleted ${chapterName}`,
|
||||
ToastAndroid.SHORT
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the novel is in db. If not, fetch from source
|
||||
*
|
||||
* If navigating from library then directly get chapters from db
|
||||
*/
|
||||
|
||||
const checkIfExistsInDb = () => {
|
||||
if (navigatingFrom === 1) {
|
||||
getChapters(novelUrl);
|
||||
} else {
|
||||
setRefreshing(true);
|
||||
checkNovelInDb(novelUrl).then((res) => {
|
||||
if (res) {
|
||||
getNovelFromDb(novelUrl);
|
||||
} else {
|
||||
getNovelFromSource(extensionId, novelUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
checkIfExistsInDb();
|
||||
}, [sort, filter])
|
||||
);
|
||||
|
||||
const onRefresh = async () => {
|
||||
setRefreshing(true);
|
||||
checkIfExistsInDb();
|
||||
};
|
||||
|
||||
const renderChapterCard = ({ item }) => (
|
||||
<ChapterCard
|
||||
navigation={navigation}
|
||||
novelUrl={novelUrl}
|
||||
extensionId={extensionId}
|
||||
chapter={item}
|
||||
downloadChapter={downloadChapter}
|
||||
downloading={downloading}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorDarkPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<FlatList
|
||||
data={chapters}
|
||||
extraData={[sort, filter]}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyExtractor={(item) => item.chapterUrl}
|
||||
removeClippedSubviews={true}
|
||||
maxToRenderPerBatch={10}
|
||||
windowSize={15}
|
||||
initialNumToRender={7}
|
||||
renderItem={renderChapterCard}
|
||||
ListHeaderComponent={() => (
|
||||
<NovelInfoHeader
|
||||
item={{
|
||||
novelCover: novelCover,
|
||||
novelName: novelName,
|
||||
}}
|
||||
novel={novel}
|
||||
noOfChapters={!loading && chapters.length}
|
||||
libraryStatus={libraryStatus}
|
||||
insertNovelInLib={insertNovelInLib}
|
||||
navigatingFrom={navigatingFrom}
|
||||
loading={loading}
|
||||
bottomSheetRef={_panel}
|
||||
/>
|
||||
)}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
colors={[theme.textColorPrimaryDark]}
|
||||
progressBackgroundColor={theme.colorDarkPrimary}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Portal>
|
||||
<BottomSheet
|
||||
bottomSheetRef={_panel}
|
||||
sort={sort}
|
||||
filter={filter}
|
||||
sortChapters={setSort}
|
||||
filterChapters={setFilter}
|
||||
/>
|
||||
</Portal>
|
||||
</View>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default NovelItem;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
@@ -1,70 +1,38 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import { StyleSheet, View, FlatList, ActivityIndicator } from "react-native";
|
||||
import { Appbar, Provider, Portal } from "react-native-paper";
|
||||
import { Provider, Portal } from "react-native-paper";
|
||||
|
||||
import NovelCover from "../../../components/common/NovelCover";
|
||||
import SearchBar from "../../../components/common/SearchBar";
|
||||
import { SearchAppbar } from "../../../components/common/Appbar";
|
||||
import { BottomSheet } from "./filters/BottomSheet";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import * as SQLite from "expo-sqlite";
|
||||
const db = SQLite.openDatabase("lnreader.db");
|
||||
const BoxNovel = ({ navigation }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
const library = useSelector((state) => state.libraryReducer.novels);
|
||||
|
||||
const AllNovels = ({ navigation }) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loadingMore, setLoadingMore] = useState(false);
|
||||
|
||||
const [novels, setNovels] = useState([]);
|
||||
const [libraryNovels, setlibraryNovels] = useState([]);
|
||||
|
||||
const [sort, setSort] = useState("rating");
|
||||
const [pageNo, setPageNo] = useState(1);
|
||||
|
||||
const [searchBar, setSearchBar] = useState(false);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const [searched, setSearched] = useState(0);
|
||||
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
let bottomSheetRef = useRef(null);
|
||||
|
||||
const getNovels = () => {
|
||||
fetch(
|
||||
`https://lnreader-extensions.herokuapp.com/api/1/novels/${pageNo}/?o=${sort}`
|
||||
`https://lnreader-extensions.herokuapp.com/api/1/novels/1/?o=${sort}`
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
setPageNo(pageNo + 1);
|
||||
setNovels((novels) => novels.concat(json));
|
||||
setNovels(json);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const getLibraryNovels = () => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"SELECT novelUrl FROM LibraryTable WHERE libraryStatus = 1 AND extensionId = 1",
|
||||
null,
|
||||
(tx, { rows: { _array } }) => {
|
||||
setlibraryNovels(_array);
|
||||
},
|
||||
(tx, error) => console.log(error)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const onEndReached = ({
|
||||
layoutMeasurement,
|
||||
contentOffset,
|
||||
contentSize,
|
||||
}) => {
|
||||
const paddingToBottom = 5;
|
||||
return (
|
||||
layoutMeasurement.height + contentOffset.y >=
|
||||
contentSize.height - paddingToBottom
|
||||
);
|
||||
};
|
||||
|
||||
const getSearchResults = (searchText) => {
|
||||
setLoading(true);
|
||||
fetch(
|
||||
@@ -78,92 +46,33 @@ const AllNovels = ({ navigation }) => {
|
||||
};
|
||||
|
||||
const checkIFInLibrary = (id) => {
|
||||
return libraryNovels.some((obj) => obj.novelUrl === id);
|
||||
return library.some((obj) => obj.novelUrl === id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getLibraryNovels();
|
||||
getNovels();
|
||||
}, [sort]);
|
||||
|
||||
const sortNovels = () => {
|
||||
setLoading(true);
|
||||
if (searched) {
|
||||
getSearchResults();
|
||||
} else {
|
||||
getNovels();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
<Appbar.Header style={{ backgroundColor: theme.colorDarkPrimary }}>
|
||||
{!searchBar ? (
|
||||
<>
|
||||
<Appbar.BackAction
|
||||
onPress={() => navigation.goBack()}
|
||||
color={theme.textColorPrimaryDark}
|
||||
/>
|
||||
|
||||
<Appbar.Content
|
||||
title="Box Novel"
|
||||
titleStyle={{ color: theme.textColorPrimaryDark }}
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon="magnify"
|
||||
onPress={() => setSearchBar(true)}
|
||||
color={theme.textColorPrimaryDark}
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon="filter-variant"
|
||||
onPress={() => _panel.show({ velocity: -1.5 })}
|
||||
color={theme.textColorPrimaryDark}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Appbar.BackAction
|
||||
onPress={() => {
|
||||
if (searched) {
|
||||
setLoading(true);
|
||||
setPageNo(1);
|
||||
setNovels([]);
|
||||
getNovels();
|
||||
}
|
||||
setSearchBar(false);
|
||||
setSearchText("");
|
||||
}}
|
||||
color={theme.textColorPrimaryDark}
|
||||
/>
|
||||
<SearchBar
|
||||
searchText={searchText}
|
||||
onChangeText={(text) => setSearchText(text)}
|
||||
onSubmitEditing={() => {
|
||||
if (searchText !== "") {
|
||||
setSort("rating");
|
||||
getSearchResults(searchText);
|
||||
setSearched(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{searchText !== "" && (
|
||||
<Appbar.Action
|
||||
icon="close"
|
||||
onPress={() => {
|
||||
setSearchText("");
|
||||
}}
|
||||
color={theme.textColorPrimaryDark}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Appbar.Header>
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorDarkPrimaryDark },
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<SearchAppbar
|
||||
screen="Extension"
|
||||
placeholder="Search BoxNovel"
|
||||
searchText={searchText}
|
||||
setSearchText={setSearchText}
|
||||
getSearchResults={getSearchResults}
|
||||
getNovels={getNovels}
|
||||
setLoading={setLoading}
|
||||
onFilter={() =>
|
||||
bottomSheetRef.current.show({ velocity: -1.5 })
|
||||
}
|
||||
/>
|
||||
{loading ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<ActivityIndicator
|
||||
@@ -178,23 +87,11 @@ const AllNovels = ({ navigation }) => {
|
||||
data={novels}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyExtractor={(item) => item.novelUrl}
|
||||
ListFooterComponent={() =>
|
||||
loadingMore && (
|
||||
<ActivityIndicator
|
||||
color={theme.colorAccentDark}
|
||||
size="small"
|
||||
style={{ marginTop: 20, marginBottom: 80 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
renderItem={({ item }) => (
|
||||
<NovelCover
|
||||
item={item}
|
||||
onPress={() => {
|
||||
navigation.navigate("NovelItem", {
|
||||
...item,
|
||||
navigatingFrom: 2,
|
||||
});
|
||||
navigation.navigate("NovelItem", item);
|
||||
}}
|
||||
libraryStatus={
|
||||
checkIFInLibrary(item.novelUrl)
|
||||
@@ -203,33 +100,27 @@ const AllNovels = ({ navigation }) => {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
onScroll={({ nativeEvent }) => {
|
||||
if (onEndReached(nativeEvent)) {
|
||||
setLoadingMore(true);
|
||||
getNovels();
|
||||
console.log("End Reached");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<Portal>
|
||||
<BottomSheet
|
||||
bottomSheetRef={(c) => (_panel = c)}
|
||||
bottomSheetRef={bottomSheetRef}
|
||||
setSort={setSort}
|
||||
sort={sort}
|
||||
setLoading={setLoading}
|
||||
/>
|
||||
</Portal>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AllNovels;
|
||||
export default BoxNovel;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 3,
|
||||
padding: 4,
|
||||
},
|
||||
|
||||
contentContainer: {
|
||||
|
||||
@@ -5,14 +5,18 @@ import Bottomsheet from "rn-sliding-up-panel";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
export const BottomSheet = ({
|
||||
bottomSheetRef,
|
||||
setSort,
|
||||
sort,
|
||||
setRefreshing,
|
||||
}) => {
|
||||
export const BottomSheet = ({ bottomSheetRef, setSort, sort, setLoading }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
const sorting = [
|
||||
{ label: "Latest", sortFlag: "latest" },
|
||||
{ label: "A-Z", sortFlag: "aplhabet" },
|
||||
{ label: "Rating", sortFlag: "rating" },
|
||||
{ label: "Trending", sortFlag: "trending" },
|
||||
{ label: "Most Views", sortFlag: "views" },
|
||||
{ label: "New", sortFlag: "new-manga" },
|
||||
];
|
||||
|
||||
return (
|
||||
<Bottomsheet
|
||||
ref={bottomSheetRef}
|
||||
@@ -22,20 +26,14 @@ export const BottomSheet = ({
|
||||
<View
|
||||
style={[
|
||||
styles.contentContainer,
|
||||
{
|
||||
backgroundColor: theme.colorDarkPrimary,
|
||||
},
|
||||
{ backgroundColor: theme.colorPrimary },
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: theme.textColorHintDark,
|
||||
height: 5,
|
||||
width: 30,
|
||||
borderRadius: 50,
|
||||
top: 10,
|
||||
alignSelf: "center",
|
||||
}}
|
||||
style={[
|
||||
styles.bottomSheetHandle,
|
||||
{ backgroundColor: theme.textColorPrimary },
|
||||
]}
|
||||
/>
|
||||
<List.Section
|
||||
title="Sort"
|
||||
@@ -43,135 +41,32 @@ export const BottomSheet = ({
|
||||
color: theme.colorAccentDark,
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
style={{
|
||||
color: theme.colorAccentDark,
|
||||
// backgroundColor: theme.colorAccentDark,
|
||||
}}
|
||||
style={{ color: theme.colorAccentDark }}
|
||||
expanded
|
||||
>
|
||||
<List.Item
|
||||
right={(props) =>
|
||||
sort === "latest" && (
|
||||
<List.Icon
|
||||
{...props}
|
||||
size
|
||||
icon="arrow-up"
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
)
|
||||
}
|
||||
title="Latest"
|
||||
titleStyle={{
|
||||
fontSize: 15,
|
||||
color: theme.textColorPrimaryDark,
|
||||
}}
|
||||
onPress={() => setSort("latest")}
|
||||
/>
|
||||
<List.Item
|
||||
right={(props) =>
|
||||
sort === "alphabet" && (
|
||||
<List.Icon
|
||||
{...props}
|
||||
size
|
||||
icon="arrow-up"
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
)
|
||||
}
|
||||
title="A-Z"
|
||||
titleStyle={{
|
||||
fontSize: 15,
|
||||
color: theme.textColorPrimaryDark,
|
||||
}}
|
||||
onPress={() => {
|
||||
setSort("alphabet");
|
||||
setRefreshing(true);
|
||||
}}
|
||||
/>
|
||||
<List.Item
|
||||
right={(props) =>
|
||||
sort === "rating" && (
|
||||
<List.Icon
|
||||
{...props}
|
||||
size
|
||||
icon="arrow-up"
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
)
|
||||
}
|
||||
title="Rating"
|
||||
titleStyle={{
|
||||
fontSize: 15,
|
||||
color: theme.textColorPrimaryDark,
|
||||
}}
|
||||
onPress={() => {
|
||||
setSort("rating");
|
||||
setRefreshing(true);
|
||||
}}
|
||||
/>
|
||||
<List.Item
|
||||
right={(props) =>
|
||||
sort === "trending" && (
|
||||
<List.Icon
|
||||
{...props}
|
||||
size
|
||||
icon="arrow-up"
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
)
|
||||
}
|
||||
title="Trending"
|
||||
titleStyle={{
|
||||
fontSize: 15,
|
||||
color: theme.textColorPrimaryDark,
|
||||
}}
|
||||
onPress={() => {
|
||||
setSort("trending");
|
||||
setRefreshing(true);
|
||||
}}
|
||||
/>
|
||||
<List.Item
|
||||
right={(props) =>
|
||||
sort === "views" && (
|
||||
<List.Icon
|
||||
{...props}
|
||||
size
|
||||
icon="arrow-up"
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
)
|
||||
}
|
||||
title="Most Views"
|
||||
titleStyle={{
|
||||
fontSize: 15,
|
||||
color: theme.textColorPrimaryDark,
|
||||
}}
|
||||
onPress={() => {
|
||||
setSort("views");
|
||||
setRefreshing(true);
|
||||
}}
|
||||
/>
|
||||
<List.Item
|
||||
right={(props) =>
|
||||
sort === "new-manga" && (
|
||||
<List.Icon
|
||||
{...props}
|
||||
size
|
||||
icon="arrow-up"
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
)
|
||||
}
|
||||
title="New"
|
||||
titleStyle={{
|
||||
fontSize: 15,
|
||||
color: theme.textColorPrimaryDark,
|
||||
}}
|
||||
onPress={() => {
|
||||
setSort("new-manga");
|
||||
setRefreshing(true);
|
||||
}}
|
||||
/>
|
||||
{sorting.map((item, index) => (
|
||||
<List.Item
|
||||
key={index}
|
||||
right={() =>
|
||||
sort === item.sortFlag && (
|
||||
<List.Icon
|
||||
icon="arrow-up"
|
||||
color={theme.colorAccentDark}
|
||||
style={styles.sortArrow}
|
||||
/>
|
||||
)
|
||||
}
|
||||
title={item.label}
|
||||
titleStyle={{
|
||||
fontSize: 15,
|
||||
color: theme.textColorPrimary,
|
||||
}}
|
||||
onPress={() => {
|
||||
setLoading(true);
|
||||
setSort(item.sortFlag);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</List.Section>
|
||||
</View>
|
||||
</Bottomsheet>
|
||||
@@ -181,5 +76,19 @@ export const BottomSheet = ({
|
||||
const styles = StyleSheet.create({
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
borderTopRightRadius: 8,
|
||||
borderTopLeftRadius: 8,
|
||||
},
|
||||
bottomSheetHandle: {
|
||||
opacity: 0.5,
|
||||
height: 4,
|
||||
width: 32,
|
||||
borderRadius: 50,
|
||||
top: 8,
|
||||
alignSelf: "center",
|
||||
},
|
||||
sortArrow: {
|
||||
marginVertical: 0,
|
||||
paddingVertical: 0,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,28 +1,23 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { StyleSheet, View, FlatList, ActivityIndicator } from "react-native";
|
||||
import { Appbar, Provider } from "react-native-paper";
|
||||
import { Provider } from "react-native-paper";
|
||||
|
||||
import NovelCover from "../../../components/common/NovelCover";
|
||||
import SearchBar from "../../../components/common/SearchBar";
|
||||
import { SearchAppbar } from "../../../components/common/Appbar";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import * as SQLite from "expo-sqlite";
|
||||
const db = SQLite.openDatabase("lnreader.db");
|
||||
import EmptyView from "../../../components/common/EmptyView";
|
||||
|
||||
const ReadLightNovel = ({ navigation }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
const library = useSelector((state) => state.libraryReducer.novels);
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [novels, setNovels] = useState();
|
||||
const [libraryNovels, setlibraryNovels] = useState([]);
|
||||
|
||||
const [searchBar, setSearchBar] = useState(false);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const [searched, setSearched] = useState(false);
|
||||
|
||||
const getNovels = () => {
|
||||
fetch(`https://lnreader-extensions.herokuapp.com/api/2/novels/`)
|
||||
.then((response) => response.json())
|
||||
@@ -44,95 +39,32 @@ const ReadLightNovel = ({ navigation }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const getLibraryNovels = () => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"SELECT novelUrl FROM LibraryTable WHERE libraryStatus = 1 AND extensionId = 2",
|
||||
null,
|
||||
(tx, { rows: { _array } }) => {
|
||||
setlibraryNovels(_array);
|
||||
},
|
||||
(tx, error) => console.log(error)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const checkIFInLibrary = (id) => {
|
||||
return libraryNovels.some((obj) => obj.novelUrl === id);
|
||||
return library.some((obj) => obj.novelUrl === id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getNovels();
|
||||
getLibraryNovels();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
<Appbar.Header style={{ backgroundColor: theme.colorDarkPrimary }}>
|
||||
{!searchBar ? (
|
||||
<>
|
||||
<Appbar.BackAction
|
||||
color={theme.textColorPrimaryDark}
|
||||
onPress={() => navigation.goBack()}
|
||||
/>
|
||||
|
||||
<Appbar.Content
|
||||
color={theme.textColorPrimaryDark}
|
||||
title="ReadLightNovel"
|
||||
titleStyle={{ color: theme.textColorPrimaryDark }}
|
||||
/>
|
||||
<Appbar.Action
|
||||
color={theme.textColorPrimaryDark}
|
||||
icon="magnify"
|
||||
onPress={() => setSearchBar(true)}
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon="filter-variant"
|
||||
disabled
|
||||
color={theme.textColorPrimaryDark}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Appbar.BackAction
|
||||
onPress={() => {
|
||||
if (searched) {
|
||||
setLoading(true);
|
||||
getNovels();
|
||||
}
|
||||
setSearchBar(false);
|
||||
setSearchText("");
|
||||
}}
|
||||
color={theme.textColorPrimaryDark}
|
||||
/>
|
||||
<SearchBar
|
||||
searchText={searchText}
|
||||
onChangeText={(text) => setSearchText(text)}
|
||||
onSubmitEditing={() => {
|
||||
if (searchText !== "") {
|
||||
getSearchResults(searchText);
|
||||
setSearched(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{searchText !== "" && (
|
||||
<Appbar.Action
|
||||
icon="close"
|
||||
onPress={() => {
|
||||
setSearchText("");
|
||||
}}
|
||||
color={theme.textColorPrimaryDark}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Appbar.Header>
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorDarkPrimaryDark },
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<SearchAppbar
|
||||
screen="Extension"
|
||||
placeholder="Search ReadLightNovel"
|
||||
searchText={searchText}
|
||||
setSearchText={setSearchText}
|
||||
getSearchResults={getSearchResults}
|
||||
getNovels={getNovels}
|
||||
setLoading={setLoading}
|
||||
/>
|
||||
|
||||
{loading ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<ActivityIndicator
|
||||
@@ -142,7 +74,7 @@ const ReadLightNovel = ({ navigation }) => {
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
contentContainerStyle={styles.list}
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
numColumns={3}
|
||||
data={novels}
|
||||
showsVerticalScrollIndicator={false}
|
||||
@@ -151,10 +83,7 @@ const ReadLightNovel = ({ navigation }) => {
|
||||
<NovelCover
|
||||
item={item}
|
||||
onPress={() =>
|
||||
navigation.navigate("NovelItem", {
|
||||
...item,
|
||||
navigatingFrom: 2,
|
||||
})
|
||||
navigation.navigate("NovelItem", item)
|
||||
}
|
||||
libraryStatus={
|
||||
checkIFInLibrary(item.novelUrl)
|
||||
@@ -163,6 +92,15 @@ const ReadLightNovel = ({ navigation }) => {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
ListEmptyComponent={
|
||||
searchText !== "" &&
|
||||
novels.length === 0 && (
|
||||
<EmptyView
|
||||
icon="(; ̄Д ̄)"
|
||||
description="No results found"
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -13,29 +13,6 @@ export const getReaderTheme = async () => {
|
||||
return theme;
|
||||
};
|
||||
|
||||
export const setAppTheme = async (val) => {
|
||||
AsyncStorage.setItem("@application_theme", JSON.stringify(val));
|
||||
};
|
||||
|
||||
export const getAppTheme = async () => {
|
||||
let theme = await AsyncStorage.getItem("@application_theme").then(
|
||||
(value) => {
|
||||
if (value) return JSON.parse(value);
|
||||
}
|
||||
);
|
||||
|
||||
return theme;
|
||||
};
|
||||
|
||||
export const getDisplayMode = async () => {
|
||||
let displayMode = await AsyncStorage.getItem("@display_mode");
|
||||
return displayMode;
|
||||
};
|
||||
|
||||
export const setDisplayMode = async (val) => {
|
||||
AsyncStorage.setItem("@display_mode", val);
|
||||
};
|
||||
|
||||
export const setStatusBar = () => {
|
||||
AsyncStorage.getItem("@application_theme").then((value) => {
|
||||
if (JSON.parse(value) === "lightTheme") {
|
||||
|
||||
+14
-3
@@ -20,6 +20,7 @@ export const fetchNovelFromSource = async (extensionId, novelUrl) => {
|
||||
sourceUrl: res.sourceUrl,
|
||||
source: res.sourceName,
|
||||
unread: 1,
|
||||
libraryStatus: 0,
|
||||
lastRead: res.novelChapters[0].chapterUrl,
|
||||
lastReadName: res.novelChapters[0].chapterName,
|
||||
extensionId: res.extensionId,
|
||||
@@ -53,12 +54,22 @@ export const fetchChapterFromSource = async (
|
||||
chapterUrl
|
||||
) => {
|
||||
const url = `https://lnreader-extensions.herokuapp.com/api/${extensionId}/novel/${novelUrl}${chapterUrl}`;
|
||||
console.log(
|
||||
`https://lnreader-extensions.herokuapp.com/api/${extensionId}/novel/${novelUrl}${chapterUrl}`
|
||||
);
|
||||
|
||||
let res = await fetch(url);
|
||||
let chapter = await res.json();
|
||||
|
||||
return chapter;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch list of extensions
|
||||
*/
|
||||
|
||||
export const fetchExtensionList = async () => {
|
||||
const url = `https://lnreader-extensions.herokuapp.com/api/`;
|
||||
|
||||
let res = await fetch(url);
|
||||
let extensions = await res.json();
|
||||
|
||||
return extensions;
|
||||
};
|
||||
|
||||
+134
-36
@@ -28,7 +28,7 @@ export const createTables = async () => {
|
||||
);
|
||||
|
||||
tx.executeSql(
|
||||
"CREATE TABLE IF NOT EXISTS DownloadsTable (downloadId INTEGER PRIMARY KEY AUTOINCREMENT, chapterUrl VARCHAR(255) UNIQUE, novelUrl VARCHAR(255), chapterName VARCHAR(255), chapterText VARCHAR(255), nextChapter VARCHAR(255), prevChapter VARCHAR(255), FOREIGN KEY (novelUrl) REFERENCES LibraryTable(novelUrl) ON DELETE CASCADE)",
|
||||
"CREATE TABLE IF NOT EXISTS DownloadsTable (downloadId INTEGER PRIMARY KEY AUTOINCREMENT, chapterUrl VARCHAR(255), novelUrl VARCHAR(255), chapterName VARCHAR(255), chapterText VARCHAR(255), nextChapter VARCHAR(255), prevChapter VARCHAR(255), FOREIGN KEY (novelUrl) REFERENCES LibraryTable(novelUrl) ON DELETE CASCADE, UNIQUE(chapterUrl, novelUrl))",
|
||||
null,
|
||||
(tx, results) => console.log("Downloads Table Created"),
|
||||
(tx, error) => console.log(error)
|
||||
@@ -71,7 +71,7 @@ export const createIndexes = async () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const insertNovelInfoInDb = (novel) => {
|
||||
export const insertNovelInfoInDb = async (novel) => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"INSERT INTO LibraryTable (novelUrl, novelName, novelCover, novelSummary, `Author(s)`, `Genre(s)`, Status, extensionId, lastRead, lastReadName, sourceUrl, source) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
@@ -89,15 +89,13 @@ export const insertNovelInfoInDb = (novel) => {
|
||||
novel.sourceUrl,
|
||||
novel.source,
|
||||
],
|
||||
(txObj, res) => {
|
||||
console.log("Inserted Novel Info in Db");
|
||||
},
|
||||
(txObj, res) => {},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const insertChaptersInDb = (novelUrl, chapters) => {
|
||||
export const insertChaptersInDb = async (novelUrl, chapters) => {
|
||||
db.transaction((tx) => {
|
||||
chapters.map((chapter) =>
|
||||
tx.executeSql(
|
||||
@@ -107,11 +105,12 @@ export const insertChaptersInDb = (novelUrl, chapters) => {
|
||||
chapter.chapterName,
|
||||
chapter.releaseDate,
|
||||
novelUrl,
|
||||
]
|
||||
],
|
||||
(txObj, res) => {},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
)
|
||||
);
|
||||
});
|
||||
console.log("Inserted Chapters in Db");
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -180,18 +179,14 @@ export const checkNovelInDb = (novelUrl) => {
|
||||
*/
|
||||
|
||||
export const toggleFavourite = async (libraryStatus, novelUrl) => {
|
||||
return new Promise((resolve, reject) =>
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"UPDATE LibraryTable SET libraryStatus = ? WHERE novelUrl=?",
|
||||
[!libraryStatus, novelUrl],
|
||||
(tx, res) => {
|
||||
resolve(!libraryStatus);
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
})
|
||||
);
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"UPDATE LibraryTable SET libraryStatus = ? WHERE novelUrl=?",
|
||||
[!libraryStatus, novelUrl],
|
||||
(tx, res) => {},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -213,8 +208,8 @@ export const downloadOrDeleteChapter = async (
|
||||
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
`UPDATE ChapterTable SET downloaded = 1 WHERE chapterUrl = ?`,
|
||||
[chapterUrl]
|
||||
`UPDATE ChapterTable SET downloaded = 1 WHERE chapterUrl = ? AND novelUrl = ?`,
|
||||
[chapterUrl, novelUrl]
|
||||
);
|
||||
tx.executeSql(
|
||||
`INSERT INTO DownloadsTable (chapterUrl, novelUrl, chapterName, chapterText, prevChapter, nextChapter) VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
@@ -233,12 +228,12 @@ export const downloadOrDeleteChapter = async (
|
||||
} else {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
`UPDATE ChapterTable SET downloaded = 0 WHERE chapterUrl = ?`,
|
||||
[chapterUrl]
|
||||
`UPDATE ChapterTable SET downloaded = 0 WHERE chapterUrl = ? AND novelUrl= ?`,
|
||||
[chapterUrl, novelUrl]
|
||||
);
|
||||
tx.executeSql(
|
||||
`DELETE FROM DownloadsTable WHERE chapterUrl = ?`,
|
||||
[chapterUrl],
|
||||
`DELETE FROM DownloadsTable WHERE chapterUrl = ? AND novelUrl = ?`,
|
||||
[chapterUrl, novelUrl],
|
||||
(tx, res) => console.log(`Chapter deleted`),
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
@@ -254,7 +249,7 @@ export const getHistoryFromDb = async () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"SELECT HistoryTable.chapterUrl, HistoryTable.historyId, HistoryTable.chapterName, HistoryTable.lastRead, LibraryTable.novelName, LibraryTable.novelCover, LibraryTable.novelUrl, LibraryTable.extensionId, LibraryTable.libraryStatus FROM HistoryTable INNER JOIN LibraryTable ON HistoryTable.novelUrl = LibraryTable.novelUrl ORDER BY HistoryTable.lastRead DESC",
|
||||
"SELECT HistoryTable.chapterUrl, HistoryTable.historyId, HistoryTable.chapterName, HistoryTable.lastRead, LibraryTable.novelName, LibraryTable.libraryStatus, LibraryTable.novelCover, LibraryTable.novelUrl, LibraryTable.extensionId, LibraryTable.libraryStatus FROM HistoryTable INNER JOIN LibraryTable ON HistoryTable.novelUrl = LibraryTable.novelUrl ORDER BY HistoryTable.lastRead DESC",
|
||||
null,
|
||||
(txObj, { rows: { _array } }) => {
|
||||
resolve(_array);
|
||||
@@ -265,14 +260,9 @@ export const getHistoryFromDb = async () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteChapterHistory = (novelId) => {
|
||||
export const deleteChapterHistory = async (novelId) => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"DELETE FROM HistoryTable WHERE novelUrl = ?",
|
||||
[novelId],
|
||||
(txObj, res) => {},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
tx.executeSql("DELETE FROM HistoryTable WHERE novelUrl = ?", [novelId]);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -316,14 +306,14 @@ export const searchInLibrary = async (searchText) => {
|
||||
|
||||
export const deleteHistory = () => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql("DELETE FROM HistoryTable");
|
||||
tx.executeSql("DELETE FROM HistoryTable; VACCUM;");
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteNovelsNotInLibrary = () => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"DELETE FROM LibraryTable WHERE libraryStatus=0",
|
||||
"DELETE FROM LibraryTable WHERE libraryStatus = 0",
|
||||
null,
|
||||
(txObj, res) => {
|
||||
console.log("Deleted");
|
||||
@@ -373,3 +363,111 @@ export const deleteDatabase = () => {
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const downloadChapterFromSource = async (
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterUrl
|
||||
) => {
|
||||
const downloadUrl = `https://lnreader-extensions.herokuapp.com/api/${extensionId}/novel/${novelUrl}${chapterUrl}`;
|
||||
|
||||
const response = await fetch(downloadUrl);
|
||||
const chapter = await response.json();
|
||||
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
`UPDATE ChapterTable SET downloaded = 1 WHERE chapterUrl = ? AND novelUrl = ?`,
|
||||
[chapterUrl, novelUrl]
|
||||
);
|
||||
tx.executeSql(
|
||||
`INSERT INTO DownloadsTable (chapterUrl, novelUrl, chapterName, chapterText, prevChapter, nextChapter) VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
chapterUrl,
|
||||
novelUrl,
|
||||
chapter.chapterName,
|
||||
chapter.chapterText,
|
||||
chapter.prevChapter,
|
||||
chapter.nextChapter,
|
||||
],
|
||||
(tx, res) => console.log(`Downloaded Chapter ${chapterUrl}`),
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteChapterFromDb = (extensionId, novelUrl, chapterUrl) => {
|
||||
const updateIsDownloadedQuery = `UPDATE ChapterTable SET downloaded = 0 WHERE chapterUrl = ? AND novelUrl = ? AND extensionId = ?`;
|
||||
const deleteChapterQuery = `DELETE FROM DownloadsTable WHERE chapterUrl = ? AND novelUrl = ? AND extensionId = ?`;
|
||||
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(updateIsDownloadedQuery, [
|
||||
chapterUrl,
|
||||
novelUrl,
|
||||
extensionId,
|
||||
]);
|
||||
tx.executeSql(
|
||||
deleteChapterQuery,
|
||||
[chapterUrl, novelUrl, extensionId],
|
||||
(tx, res) => console.log(`Chapter deleted`),
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const insertIntoHistory = async (chapterUrl, chapterName, novelUrl) => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"INSERT OR REPLACE INTO HistoryTable (chapterUrl, novelUrl, chapterName, lastRead) VALUES ( ?, ?, ?, (datetime('now','localtime')))",
|
||||
[chapterUrl, novelUrl, chapterName]
|
||||
);
|
||||
tx.executeSql(
|
||||
"UPDATE LibraryTable SET lastRead = ?, lastReadName = ?, unread = 0 WHERE novelUrl = ?",
|
||||
[chapterUrl, chapterName, novelUrl]
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const chapterRead = async (chapterUrl, novelUrl) => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"UPDATE ChapterTable SET `read` = 1 WHERE chapterUrl = ? AND novelUrl = ?",
|
||||
[chapterUrl, novelUrl],
|
||||
(tx, res) => {},
|
||||
(tx, error) => console.log(error)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const isChapterDownloaded = async (chapterUrl, novelUrl) => {
|
||||
return new Promise((resolve, reject) =>
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"SELECT * FROM DownloadsTable WHERE chapterUrl=? AND novelUrl=?",
|
||||
[chapterUrl, novelUrl],
|
||||
(txObj, res) => {
|
||||
if (res.rows.length !== 0) {
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const getChapterFromDb = async (chapterUrl, novelUrl) => {
|
||||
return new Promise((resolve, reject) =>
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
`SELECT * FROM DownloadsTable WHERE chapterUrl = ? AND novelUrl = ?`,
|
||||
[chapterUrl, novelUrl],
|
||||
(tx, results) => {
|
||||
resolve(results.rows.item(0));
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
+29
-25
@@ -2,63 +2,67 @@
|
||||
* Application Colors
|
||||
*/
|
||||
export const theme = {
|
||||
colorDarkPrimary: "#000000",
|
||||
colorDarkPrimaryDark: "#000000",
|
||||
colorPrimary: "#000000",
|
||||
colorPrimaryDark: "#000000",
|
||||
colorAccentDark: "#3399FF",
|
||||
textColorPrimaryDark: "#FFFFFF",
|
||||
textColorSecondaryDark: "rgba(255,255,255,0.7)",
|
||||
textColorPrimary: "#FFFFFF",
|
||||
textColorSecondary: "rgba(255,255,255,0.7)",
|
||||
textColorHintDark: "rgba(255,255,255,0.5)",
|
||||
rippleColorDark: "rgba(255,255,255,0.2)",
|
||||
rippleColor: "rgba(255,255,255,0.2)",
|
||||
};
|
||||
|
||||
/**
|
||||
* Light Theme
|
||||
*/
|
||||
export const lightTheme = {
|
||||
colorDarkPrimary: "#FFFFFF",
|
||||
colorDarkPrimaryDark: "#FAFAFA",
|
||||
colorPrimary: "#FFFFFF",
|
||||
colorPrimaryDark: "#FAFAFA",
|
||||
colorAccentDark: "#2979FF",
|
||||
textColorPrimaryDark: "#000000",
|
||||
textColorSecondaryDark: "rgba(0,0,0,0.54)",
|
||||
textColorPrimary: "#000000",
|
||||
textColorSecondary: "rgba(0,0,0,0.54)",
|
||||
textColorHintDark: "rgba(0,0,0,0.38)",
|
||||
rippleColorDark: "#C2C2C2",
|
||||
rippleColor: "#C2C2C2",
|
||||
searchBarColor: "#FFFFFF",
|
||||
};
|
||||
|
||||
/**
|
||||
* Dark Theme
|
||||
*/
|
||||
export const darkTheme = {
|
||||
colorDarkPrimary: "#242529",
|
||||
colorDarkPrimaryDark: "#202125",
|
||||
colorPrimary: "#242529",
|
||||
colorPrimaryDark: "#202125",
|
||||
colorAccentDark: "#3399FF",
|
||||
textColorPrimaryDark: "#FFFFFF",
|
||||
textColorSecondaryDark: "rgba(255,255,255,0.7)",
|
||||
textColorPrimary: "#FFFFFF",
|
||||
textColorSecondary: "rgba(255,255,255,0.7)",
|
||||
textColorHintDark: "rgba(255,255,255,0.5)",
|
||||
rippleColorDark: "rgba(255,255,255,0.2)",
|
||||
rippleColor: "rgba(255,255,255,0.2)",
|
||||
searchBarColor: "#303135",
|
||||
};
|
||||
|
||||
/**
|
||||
* Amoled Dark Theme
|
||||
*/
|
||||
export const amoledDarkTheme = {
|
||||
colorDarkPrimary: "#000000",
|
||||
colorDarkPrimaryDark: "#000000",
|
||||
colorPrimary: "#000000",
|
||||
colorPrimaryDark: "#000000",
|
||||
colorAccentDark: "#3399FF",
|
||||
textColorPrimaryDark: "#FFFFFF",
|
||||
textColorSecondaryDark: "rgba(255,255,255,0.7)",
|
||||
textColorPrimary: "#FFFFFF",
|
||||
textColorSecondary: "rgba(255,255,255,0.7)",
|
||||
textColorHintDark: "rgba(255,255,255,0.5)",
|
||||
rippleColorDark: "rgba(255,255,255,0.2)",
|
||||
rippleColor: "rgba(255,255,255,0.2)",
|
||||
searchBarColor: "#1F1F1F",
|
||||
};
|
||||
|
||||
/**
|
||||
* Midnight Dusk Theme
|
||||
*/
|
||||
export const midnightDuskTheme = {
|
||||
colorDarkPrimary: "#201F27",
|
||||
colorDarkPrimaryDark: "#16151D",
|
||||
colorPrimary: "#201F27",
|
||||
colorPrimaryDark: "#16151D",
|
||||
colorAccentDark: "#F02475",
|
||||
textColorPrimaryDark: "#FFFFFF",
|
||||
textColorSecondaryDark: "rgba(255,255,255,0.7)",
|
||||
textColorPrimary: "#FFFFFF",
|
||||
textColorSecondary: "rgba(255,255,255,0.7)",
|
||||
textColorHintDark: "rgba(255,255,255,0.5)",
|
||||
rippleColorDark: "rgba(255,255,255,0.2)",
|
||||
rippleColor: "rgba(255,255,255,0.2)",
|
||||
searchBarColor: "#201F27",
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user