Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c0b599452 | |||
| ac26c67361 | |||
| d205572a5e | |||
| e5d52fcd9d | |||
| 61424e533a | |||
| c3ef170385 | |||
| 430c7a7341 | |||
| e4b934061b | |||
| 3579671ade | |||
| 893d229c25 | |||
| ea3f5ac7ee | |||
| e4027efbb1 | |||
| f5dd7153cc | |||
| 38b0097261 | |||
| 2f7215512f | |||
| 9a96a34cda | |||
| a7f6d2cd3c | |||
| 5dfe6a7ab8 |
@@ -0,0 +1,45 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
install-and-test:
|
||||
name: Install dependencies
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install npm dependencies
|
||||
run: |
|
||||
npm install
|
||||
|
||||
build-android:
|
||||
name: Build app
|
||||
needs: install-and-test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Cache Gradle Wrapper
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
|
||||
|
||||
- name: Cache Gradle Dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-caches-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-caches-
|
||||
|
||||
- name: Make Gradlew Executable
|
||||
run: cd android && chmod +x ./gradlew
|
||||
|
||||
- name: Build Android Release
|
||||
run: |
|
||||
cd android && ./gradlew assembleRelease --no-daemon
|
||||
@@ -136,7 +136,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.1.0"
|
||||
versionName "1.1.1"
|
||||
}
|
||||
splits {
|
||||
abi {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "LNReader",
|
||||
"slug": "LNReader",
|
||||
"scheme": "lnreader",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"orientation": "default",
|
||||
"icon": "./assets/icon.png",
|
||||
"splash": {
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,78 @@
|
||||
import React from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
|
||||
import { IconButton } from "react-native-paper";
|
||||
|
||||
const actions = [
|
||||
{
|
||||
icon: "download-outline",
|
||||
onPress: () => {
|
||||
dispatch(
|
||||
downloadAllChaptersAction(
|
||||
novel.sourceId,
|
||||
novel.novelUrl,
|
||||
selected
|
||||
)
|
||||
);
|
||||
setSelected([]);
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: "trash-can-outline",
|
||||
onPress: () => {
|
||||
dispatch(deleteAllChaptersAction(selected));
|
||||
setSelected([]);
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: "bookmark-outline",
|
||||
onPress: () => {
|
||||
dispatch(bookmarkChapterAction(selected));
|
||||
setSelected([]);
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: "check",
|
||||
onPress: () => {
|
||||
dispatch(markChaptersRead(selected, novel.novelId, sort, filter));
|
||||
setSelected([]);
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: "check-outline",
|
||||
onPress: () => {
|
||||
dispatch(markChapterUnreadAction(selected, novel.novelId));
|
||||
setSelected([]);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const Actionbar = ({ actions, theme }) => {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.actionbarContainer,
|
||||
{ backgroundColor: theme.colorPrimary },
|
||||
]}
|
||||
>
|
||||
{actions.map((action) => (
|
||||
<IconButton icon={action.icon} onPress={action.onPress} />
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
actionbarContainer: {
|
||||
position: "absolute",
|
||||
width: Dimensions.get("window").width - 32,
|
||||
bottom: 0,
|
||||
margin: 16,
|
||||
borderRadius: 6,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4,
|
||||
elevation: 2,
|
||||
},
|
||||
});
|
||||
@@ -3,7 +3,14 @@ import { StyleSheet, View, Text, Image } from "react-native";
|
||||
import FastImage from "react-native-fast-image";
|
||||
import { TouchableRipple } from "react-native-paper";
|
||||
|
||||
const ListView = ({ item, downloadBadge, unreadBadge, theme, onPress }) => {
|
||||
const ListView = ({
|
||||
item,
|
||||
downloadBadge,
|
||||
unreadBadge,
|
||||
inLibraryBadge,
|
||||
theme,
|
||||
onPress,
|
||||
}) => {
|
||||
return (
|
||||
<TouchableRipple
|
||||
borderless
|
||||
@@ -29,6 +36,7 @@ const ListView = ({ item, downloadBadge, unreadBadge, theme, onPress }) => {
|
||||
<View style={styles.badgeContainer}>
|
||||
{downloadBadge}
|
||||
{unreadBadge}
|
||||
{inLibraryBadge}
|
||||
</View>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
|
||||
@@ -43,7 +43,7 @@ const NovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
displayMode === 0 && (
|
||||
<View style={styles.titleContainer}>
|
||||
<LinearGradient
|
||||
colors={["transparent", "rgba(0,0,0,0.6)"]}
|
||||
colors={["transparent", "rgba(0,0,0,0.7)"]}
|
||||
style={styles.linearGradient}
|
||||
>
|
||||
<Text
|
||||
@@ -69,7 +69,10 @@ const NovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
!showDownloadBadges && {
|
||||
borderRadius: 4,
|
||||
},
|
||||
{ backgroundColor: theme.colorAccent },
|
||||
{
|
||||
backgroundColor: theme.colorAccent,
|
||||
color: theme.colorButtonText,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.chaptersUnread}
|
||||
@@ -95,6 +98,22 @@ const NovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
</Text>
|
||||
);
|
||||
|
||||
const inLibraryBadge = () =>
|
||||
libraryStatus && (
|
||||
<Text
|
||||
style={[
|
||||
styles.inLibraryBadge,
|
||||
{
|
||||
backgroundColor: theme.colorAccent,
|
||||
color: theme.colorButtonText,
|
||||
borderRadius: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
In library
|
||||
</Text>
|
||||
);
|
||||
|
||||
return displayMode !== 2 ? (
|
||||
<View style={{ flex: 1 / getNovelsPerRow() }}>
|
||||
<TouchableRipple
|
||||
@@ -105,19 +124,36 @@ const NovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
onPress={onPress}
|
||||
>
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
zIndex: 1,
|
||||
top: 8,
|
||||
left: 8,
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
{inLibraryBadge()}
|
||||
{downloadBadge()}
|
||||
{unreadBadge()}
|
||||
</View>
|
||||
<FastImage
|
||||
source={{ uri: item.novelCover }}
|
||||
style={[
|
||||
{ height: getHeight(), borderRadius: 4 },
|
||||
libraryStatus && { opacity: 0.5 },
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 4,
|
||||
left: 4,
|
||||
right: 4,
|
||||
}}
|
||||
>
|
||||
<View style={{ flexDirection: "row", margin: 4 }}>
|
||||
{downloadBadge()}
|
||||
{unreadBadge()}
|
||||
</View>
|
||||
{compactTitle()}
|
||||
</FastImage>
|
||||
</View>
|
||||
{comfortableTitle()}
|
||||
</>
|
||||
</TouchableRipple>
|
||||
@@ -127,6 +163,7 @@ const NovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
item={item}
|
||||
downloadBadge={downloadBadge()}
|
||||
unreadBadge={unreadBadge()}
|
||||
inLibraryBadge={inLibraryBadge()}
|
||||
theme={theme}
|
||||
onPress={onPress}
|
||||
/>
|
||||
@@ -138,7 +175,6 @@ export default NovelCover;
|
||||
const styles = StyleSheet.create({
|
||||
titleContainer: {
|
||||
flex: 1,
|
||||
justifyContent: "flex-end",
|
||||
borderRadius: 4,
|
||||
},
|
||||
title: {
|
||||
@@ -186,4 +222,9 @@ const styles = StyleSheet.create({
|
||||
paddingHorizontal: 4,
|
||||
fontSize: 12,
|
||||
},
|
||||
inLibraryBadge: {
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 4,
|
||||
fontSize: 12,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import React from "react";
|
||||
import { Pressable, StyleSheet, View, Text } from "react-native";
|
||||
|
||||
import { Switch, List } from "react-native-paper";
|
||||
|
||||
const SwitchSetting = ({ label, description, onPress, theme, icon, value }) => (
|
||||
<Pressable
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
style={styles.container}
|
||||
onPress={onPress}
|
||||
>
|
||||
<View style={{ flexDirection: "row", flex: 1 }}>
|
||||
{icon && (
|
||||
<List.Icon
|
||||
color={theme.colorAccent}
|
||||
icon={icon}
|
||||
style={{ margin: 0 }}
|
||||
/>
|
||||
)}
|
||||
<View style={[icon && { marginLeft: 16 }]}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 16,
|
||||
flex: 1,
|
||||
textAlignVertical: "center",
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
{description && (
|
||||
<Text style={{ color: theme.textColorSecondary }}>
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<Switch
|
||||
value={value}
|
||||
onValueChange={onPress}
|
||||
color={theme.colorAccent}
|
||||
style={{ marginRight: 8 }}
|
||||
/>
|
||||
</Pressable>
|
||||
);
|
||||
|
||||
export default SwitchSetting;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
});
|
||||
@@ -1,9 +1,13 @@
|
||||
import * as SQLite from "expo-sqlite";
|
||||
const db = SQLite.openDatabase("lnreader.db");
|
||||
|
||||
import BackgroundService from "react-native-background-actions";
|
||||
|
||||
import { fetchChapters, fetchNovel } from "../../services/Source/source";
|
||||
import { insertChapters } from "./ChapterQueries";
|
||||
|
||||
import { showToast } from "../../hooks/showToast";
|
||||
|
||||
const insertNovelQuery = `INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
||||
|
||||
export const insertNovel = async (novel) => {
|
||||
@@ -127,33 +131,67 @@ export const restoreLibrary = async (novel) => {
|
||||
const migrateNovelQuery = `INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre, followed) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
||||
|
||||
export const migrateNovel = async (sourceId, novelUrl) => {
|
||||
const novel = await fetchNovel(sourceId, novelUrl);
|
||||
try {
|
||||
const novel = await fetchNovel(sourceId, novelUrl);
|
||||
|
||||
db.transaction((tx) =>
|
||||
tx.executeSql(
|
||||
migrateNovelQuery,
|
||||
[
|
||||
novel.novelUrl,
|
||||
novel.sourceUrl,
|
||||
novel.sourceId,
|
||||
novel.source,
|
||||
novel.novelName,
|
||||
novel.novelCover,
|
||||
novel.novelSummary,
|
||||
novel.author,
|
||||
novel.artist,
|
||||
novel.status,
|
||||
novel.genre,
|
||||
1,
|
||||
],
|
||||
async (txObj, { insertId }) => {
|
||||
const chapters = await fetchChapters(
|
||||
novel.sourceId,
|
||||
novel.novelUrl
|
||||
);
|
||||
await insertChapters(insertId, chapters);
|
||||
const options = {
|
||||
taskName: "Migration",
|
||||
taskTitle: `Migrating ${novel.novelName} to new source`,
|
||||
taskDesc: novel.source,
|
||||
taskIcon: {
|
||||
name: "notification_icon",
|
||||
type: "drawable",
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
)
|
||||
);
|
||||
color: "#00adb5",
|
||||
parameters: {
|
||||
delay: 1000,
|
||||
},
|
||||
progressBar: {
|
||||
max: 1,
|
||||
value: 0,
|
||||
indeterminate: true,
|
||||
},
|
||||
};
|
||||
|
||||
const veryIntensiveTask = async () => {
|
||||
await new Promise(async (resolve) => {
|
||||
db.transaction((tx) =>
|
||||
tx.executeSql(
|
||||
migrateNovelQuery,
|
||||
[
|
||||
novel.novelUrl,
|
||||
novel.sourceUrl,
|
||||
novel.sourceId,
|
||||
novel.source,
|
||||
novel.novelName,
|
||||
novel.novelCover,
|
||||
novel.novelSummary,
|
||||
novel.author,
|
||||
novel.artist,
|
||||
novel.status,
|
||||
novel.genre,
|
||||
1,
|
||||
],
|
||||
async (txObj, { insertId }) => {
|
||||
const chapters = await fetchChapters(
|
||||
novel.sourceId,
|
||||
novel.novelUrl
|
||||
);
|
||||
await insertChapters(insertId, chapters);
|
||||
resolve();
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
await BackgroundService.start(veryIntensiveTask, options);
|
||||
await BackgroundService.updateNotification({
|
||||
progressBar: { value: 1, indeterminate: false },
|
||||
});
|
||||
await BackgroundService.stop();
|
||||
} catch (error) {
|
||||
showToast(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ export const githubUpdateChecker = () => {
|
||||
};
|
||||
|
||||
const isNewVersion = (versionTag) => {
|
||||
let currentVersion = "v1.1.0";
|
||||
let currentVersion = "v1.1.1";
|
||||
const regex = /[^\\d.]/;
|
||||
|
||||
let newVersion = versionTag.replace(regex, "");
|
||||
|
||||
@@ -26,7 +26,7 @@ const historyReducer = (state = initialState, action) => {
|
||||
// ),
|
||||
// };
|
||||
case CLEAR_HISTORY:
|
||||
return { ...state, loading: [] };
|
||||
return { ...state, loading: false, history: [] };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
midnightDuskTheme,
|
||||
limeTheme,
|
||||
irisBlueTheme,
|
||||
hotPinkTheme,
|
||||
strawberryDaiquiri,
|
||||
} from "../../theme/theme";
|
||||
|
||||
const themes = {
|
||||
@@ -21,6 +23,8 @@ const themes = {
|
||||
3: midnightDuskTheme,
|
||||
4: limeTheme,
|
||||
5: irisBlueTheme,
|
||||
6: hotPinkTheme,
|
||||
7: strawberryDaiquiri,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -59,6 +63,8 @@ const initialState = {
|
||||
swipeGestures: true,
|
||||
incognitoMode: false,
|
||||
searchAllSources: false,
|
||||
textSelectable: false,
|
||||
updateLibraryOnLaunch: false,
|
||||
};
|
||||
|
||||
const settingsReducer = (state = initialState, action) => {
|
||||
|
||||
@@ -18,15 +18,13 @@ import {
|
||||
import SourceCard from "./components/SourceCard";
|
||||
import { showToast } from "../../hooks/showToast";
|
||||
import { Searchbar } from "../../components/Searchbar";
|
||||
import MalCard from "./components/MalCard";
|
||||
import NovelUpdatesCard from "./components/NovelUpdatesCard";
|
||||
import EmptyView from "../../components/EmptyView";
|
||||
import DiscoverCard from "./discover/DiscoverCard";
|
||||
|
||||
const Browse = ({ navigation }) => {
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
// const [globalSearch, setGlobalSearch] = useState(false);
|
||||
// const [searchResult, setSearchResults] = useState();
|
||||
|
||||
let {
|
||||
sources,
|
||||
search,
|
||||
@@ -64,12 +62,12 @@ const Browse = ({ navigation }) => {
|
||||
dispatch(searchSourcesAction(text));
|
||||
};
|
||||
|
||||
// const onSubmitEditing = () => {};
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<SourceCard
|
||||
item={item}
|
||||
isPinned={isPinned(item.sourceId)}
|
||||
dispatch={dispatch}
|
||||
navigation={navigation}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
@@ -124,9 +122,27 @@ const Browse = ({ navigation }) => {
|
||||
Discover
|
||||
</Text>
|
||||
{showNovelUpdates && (
|
||||
<NovelUpdatesCard theme={theme} />
|
||||
<DiscoverCard
|
||||
label="Novel Updates"
|
||||
onPress={() =>
|
||||
navigation.navigate(
|
||||
"BrowseNovelUpdates"
|
||||
)
|
||||
}
|
||||
icon={require("../../../assets/novelupdates.png")}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
{showMyAnimeList && (
|
||||
<DiscoverCard
|
||||
label="MyAnimeList"
|
||||
onPress={() =>
|
||||
navigation.navigate("BrowseMal")
|
||||
}
|
||||
icon={require("../../../assets/mal.png")}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
{showMyAnimeList && <MalCard theme={theme} />}
|
||||
</>
|
||||
)}
|
||||
{pinned.length > 0 && (
|
||||
|
||||
@@ -53,25 +53,6 @@ const SourceNovels = ({ navigation, route }) => {
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
{/* <Portal>
|
||||
<MigrationDialog
|
||||
migrationDialog={migrationDialog}
|
||||
hideMigrationDialog={hideMigrationDIalog}
|
||||
theme={theme}
|
||||
selectedNovel={selectedNovel}
|
||||
loading={loading}
|
||||
migrationNovel={migrationNovel}
|
||||
/>
|
||||
<SourceToMigrateDialog
|
||||
selectedNovel={selectedNovel}
|
||||
sourceToMigrateDialog={sourceToMigrateDialog}
|
||||
hideSourceToMigrateDialog={hideSourceToMigrateDialog}
|
||||
sources={sources}
|
||||
theme={theme}
|
||||
showMigrationDialog={showMigrationDialog}
|
||||
selectSourceToMigrate={selectSourceToMigrate}
|
||||
/>
|
||||
</Portal> */}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
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";
|
||||
|
||||
const MalCard = ({ theme }) => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const navigateToExtension = () => navigation.navigate("BrowseMal");
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
style={styles.extensionCard}
|
||||
onPress={navigateToExtension}
|
||||
rippleColor={theme.rippleColor}
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
source={require("../../../../assets/mal.png")}
|
||||
style={styles.extensionIcon}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
<View style={styles.extensionDetails}>
|
||||
<View>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
MyAnimeList
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={{ flexDirection: "row", alignItems: "center" }}
|
||||
>
|
||||
<Button
|
||||
labelStyle={{ letterSpacing: 0 }}
|
||||
uppercase={false}
|
||||
color={theme.colorAccent}
|
||||
onPress={navigateToExtension}
|
||||
>
|
||||
Browse
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
);
|
||||
};
|
||||
|
||||
export default MalCard;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
extensionCard: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginVertical: 4,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
extensionIcon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 8,
|
||||
},
|
||||
extensionDetails: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginLeft: 16,
|
||||
},
|
||||
sourceStatus: {
|
||||
color: "#C14033",
|
||||
fontSize: 12,
|
||||
marginLeft: 5,
|
||||
},
|
||||
});
|
||||
@@ -1,80 +0,0 @@
|
||||
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";
|
||||
|
||||
const NovelUpdatesCard = ({ theme }) => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const navigateToExtension = () => navigation.navigate("BrowseNovelUpdates");
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
style={styles.extensionCard}
|
||||
onPress={navigateToExtension}
|
||||
rippleColor={theme.rippleColor}
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
source={require("../../../../assets/novelupdates.png")}
|
||||
style={styles.extensionIcon}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
<View style={styles.extensionDetails}>
|
||||
<View>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
Novel Updates
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={{ flexDirection: "row", alignItems: "center" }}
|
||||
>
|
||||
<Button
|
||||
labelStyle={{ letterSpacing: 0 }}
|
||||
uppercase={false}
|
||||
color={theme.colorAccent}
|
||||
onPress={navigateToExtension}
|
||||
>
|
||||
Browse
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
);
|
||||
};
|
||||
|
||||
export default NovelUpdatesCard;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
extensionCard: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginVertical: 4,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
extensionIcon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 8,
|
||||
},
|
||||
extensionDetails: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginLeft: 16,
|
||||
},
|
||||
sourceStatus: {
|
||||
color: "#C14033",
|
||||
fontSize: 12,
|
||||
marginLeft: 5,
|
||||
},
|
||||
});
|
||||
@@ -1,82 +1,74 @@
|
||||
import React from "react";
|
||||
import { View, Text, Image, StyleSheet } from "react-native";
|
||||
import { TouchableRipple, Button, IconButton } from "react-native-paper";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { View, Text, Image, StyleSheet, Pressable } from "react-native";
|
||||
|
||||
import { Button, IconButton } from "react-native-paper";
|
||||
|
||||
import { pinSourceAction } from "../../../redux/source/source.actions";
|
||||
import FastImage from "react-native-fast-image";
|
||||
|
||||
const ExtensionCard = ({ item, theme, isPinned }) => {
|
||||
const ExtensionCard = ({ item, theme, isPinned, dispatch, navigation }) => {
|
||||
const { sourceId, sourceName, icon, lang } = item;
|
||||
|
||||
const navigation = useNavigation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const navigateToExtension = () =>
|
||||
navigation.navigate("Extension", {
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName,
|
||||
sourceUrl: item.url,
|
||||
url: item.url,
|
||||
});
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
<Pressable
|
||||
style={styles.extensionCard}
|
||||
onPress={navigateToExtension}
|
||||
rippleColor={theme.rippleColor}
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
source={{ uri: icon }}
|
||||
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,
|
||||
}}
|
||||
>
|
||||
{lang}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={{ flexDirection: "row", alignItems: "center" }}
|
||||
<Image
|
||||
source={{ uri: icon }}
|
||||
style={styles.extensionIcon}
|
||||
// resizeMode="contain"
|
||||
/>
|
||||
<View style={styles.extensionDetails}>
|
||||
<View>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
labelStyle={{ letterSpacing: 0 }}
|
||||
uppercase={false}
|
||||
color={theme.colorAccent}
|
||||
onPress={navigateToExtension}
|
||||
>
|
||||
Browse
|
||||
</Button>
|
||||
<IconButton
|
||||
icon={isPinned ? "pin" : "pin-outline"}
|
||||
animated
|
||||
size={21}
|
||||
onPress={() => dispatch(pinSourceAction(sourceId))}
|
||||
color={
|
||||
isPinned
|
||||
? theme.colorAccent
|
||||
: theme.textColorSecondary
|
||||
}
|
||||
style={{ margin: 2 }}
|
||||
/>
|
||||
</View>
|
||||
{sourceName}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{lang}
|
||||
</Text>
|
||||
</View>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
||||
<Button
|
||||
labelStyle={{ letterSpacing: 0 }}
|
||||
uppercase={false}
|
||||
color={theme.colorAccent}
|
||||
onPress={navigateToExtension}
|
||||
>
|
||||
Browse
|
||||
</Button>
|
||||
<IconButton
|
||||
icon={isPinned ? "pin" : "pin-outline"}
|
||||
animated
|
||||
size={21}
|
||||
onPress={() => dispatch(pinSourceAction(sourceId))}
|
||||
color={
|
||||
isPinned
|
||||
? theme.colorAccent
|
||||
: theme.textColorSecondary
|
||||
}
|
||||
style={{ margin: 2 }}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -93,7 +85,7 @@ const styles = StyleSheet.create({
|
||||
extensionIcon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 8,
|
||||
borderRadius: 4,
|
||||
},
|
||||
extensionDetails: {
|
||||
flex: 1,
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, Pressable, Text, View, FlatList } from "react-native";
|
||||
import FastImage from "react-native-fast-image";
|
||||
import { Row } from "../../../components/Common";
|
||||
|
||||
const GenreChip = ({ children, theme }) => (
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
<Text
|
||||
style={[styles.genreChip, { color: theme.textColorSecondary }]}
|
||||
numberOfLines={2}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{`${"Score "}`}
|
||||
</Text>
|
||||
{children}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
const BrowseMalCard = ({ novel, theme, onPress }) => {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: theme.colorPrimary,
|
||||
borderRadius: 8,
|
||||
elevation: 1,
|
||||
marginVertical: 4,
|
||||
marginHorizontal: 8,
|
||||
}}
|
||||
>
|
||||
<Pressable
|
||||
style={styles.novelUpdatesCard}
|
||||
onPress={onPress}
|
||||
android_ripple={{
|
||||
color: theme.rippleColor,
|
||||
borderless: true,
|
||||
}}
|
||||
>
|
||||
<FastImage
|
||||
source={{ uri: novel.novelCover }}
|
||||
style={{
|
||||
width: 130,
|
||||
height: 180,
|
||||
borderTopLeftRadius: 8,
|
||||
borderBottomLeftRadius: 8,
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{ color: theme.textColorPrimary, fontSize: 17 }}
|
||||
>
|
||||
{novel.novelName}
|
||||
</Text>
|
||||
<GenreChip theme={theme}>{novel.score}</GenreChip>
|
||||
{novel.info && (
|
||||
<>
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{ color: theme.textColorSecondary },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{`${"Type "}`}
|
||||
</Text>
|
||||
{novel.info[1]}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
marginBottom: 4,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{ color: theme.textColorSecondary },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{`${"Published "}`}
|
||||
</Text>
|
||||
{novel.info[2]}
|
||||
</Text>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default BrowseMalCard;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
novelUpdatesCard: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
},
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
genreChip: {
|
||||
marginTop: 4,
|
||||
fontSize: 13,
|
||||
// fontStyle: "italic",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import React from "react";
|
||||
import { View, Text, Image, StyleSheet, Pressable } from "react-native";
|
||||
|
||||
import { Button } from "react-native-paper";
|
||||
|
||||
const DiscoverCard = ({ icon, label, onPress, theme }) => {
|
||||
return (
|
||||
<Pressable
|
||||
style={styles.discoverCard}
|
||||
onPress={onPress}
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
>
|
||||
<Image source={icon} style={styles.discoverIcon} />
|
||||
<View style={styles.label}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
<Button
|
||||
labelStyle={{ letterSpacing: 0 }}
|
||||
uppercase={false}
|
||||
color={theme.colorAccent}
|
||||
onPress={onPress}
|
||||
>
|
||||
Browse
|
||||
</Button>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
export default DiscoverCard;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
discoverCard: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginVertical: 4,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
discoverIcon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 4,
|
||||
},
|
||||
label: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginLeft: 16,
|
||||
},
|
||||
});
|
||||
@@ -1,15 +1,8 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
Button,
|
||||
} from "react-native";
|
||||
import { StyleSheet, View, ActivityIndicator, FlatList } from "react-native";
|
||||
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
|
||||
import NovelCover from "../../../components/NovelCover";
|
||||
import { Searchbar } from "../../../components/Searchbar";
|
||||
import ErrorView from "../../../components/ErrorView";
|
||||
|
||||
@@ -17,6 +10,7 @@ import { useTheme, useSettings } from "../../../hooks/reduxHooks";
|
||||
import { showToast } from "../../../hooks/showToast";
|
||||
import { getDeviceOrientation } from "../../../services/utils/helpers";
|
||||
import { scrapeSearchResults, scrapeTopNovels } from "./MyAnimeListScraper";
|
||||
import BrowseMalCard from "./BrowseMalCard";
|
||||
|
||||
const BrowseMalScreen = ({ navigation, route }) => {
|
||||
const theme = useTheme();
|
||||
@@ -28,7 +22,6 @@ const BrowseMalScreen = ({ navigation, route }) => {
|
||||
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const baseUrl = "https://api.jikan.moe/v3/";
|
||||
const malUrl = "https://myanimelist.net/topmanga.php?type=lightnovels";
|
||||
|
||||
const getNovels = async (limit) => {
|
||||
@@ -72,8 +65,9 @@ const BrowseMalScreen = ({ navigation, route }) => {
|
||||
}, []);
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<NovelCover
|
||||
item={{ novelName: item.novelName, novelCover: item.novelCover }}
|
||||
<BrowseMalCard
|
||||
novel={item}
|
||||
theme={theme}
|
||||
onPress={() =>
|
||||
navigation.navigate("GlobalSearch", {
|
||||
novelName: item.novelName,
|
||||
@@ -153,8 +147,6 @@ const BrowseMalScreen = ({ navigation, route }) => {
|
||||
) : (
|
||||
<FlatList
|
||||
contentContainerStyle={styles.novelsContainer}
|
||||
numColumns={getNovelsPerRow()}
|
||||
key={getNovelsPerRow()}
|
||||
data={novels}
|
||||
keyExtractor={(item) => item.novelName}
|
||||
renderItem={renderItem}
|
||||
@@ -189,7 +181,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
novelsContainer: {
|
||||
flexGrow: 1,
|
||||
paddingVertical: 8,
|
||||
paddingBottom: 8,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -22,7 +22,12 @@ const scrapeTopNovels = async (pageNo) => {
|
||||
".jpg";
|
||||
const novelName = $(this).find("h3").text();
|
||||
|
||||
const novel = { novelName, novelCover };
|
||||
const score = $(this)
|
||||
.find(".js-top-ranking-score-col > span.score-label")
|
||||
.text();
|
||||
|
||||
const info = $(this).find("div.information").text().split(/\s\s+/);
|
||||
const novel = { novelName, novelCover, score, info };
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
@@ -58,7 +63,9 @@ const scrapeSearchResults = async (searchTerm) => {
|
||||
novelId[8].split(".")[0] +
|
||||
".jpg";
|
||||
|
||||
const novel = { novelName, novelCover };
|
||||
const score = $(this).find("td:nth-child(5)").text().trim();
|
||||
|
||||
const novel = { novelName, novelCover, score };
|
||||
|
||||
novels.push(novel);
|
||||
}
|
||||
|
||||
@@ -4,53 +4,103 @@ import FastImage from "react-native-fast-image";
|
||||
import { Row } from "../../../components/Common";
|
||||
|
||||
const GenreChip = ({ children, theme }) => (
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{
|
||||
color: theme.colorAccent,
|
||||
borderColor: theme.colorAccent,
|
||||
},
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
<Text
|
||||
style={[styles.genreChip, { color: theme.textColorSecondary }]}
|
||||
numberOfLines={2}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{`${"Genre "}`}
|
||||
</Text>
|
||||
{children}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
const NovelUpdatesNovelCard = ({ novel, theme, onPress }) => {
|
||||
return (
|
||||
<Pressable
|
||||
style={styles.novelUpdatesCard}
|
||||
onPress={onPress}
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: theme.colorPrimary,
|
||||
borderRadius: 8,
|
||||
elevation: 1,
|
||||
marginHorizontal: 12,
|
||||
marginVertical: 4,
|
||||
}}
|
||||
>
|
||||
<View style={{ flex: 1, flexDirection: "row" }}>
|
||||
<Pressable
|
||||
style={styles.novelUpdatesCard}
|
||||
onPress={onPress}
|
||||
android_ripple={{ color: theme.rippleColor, borderless: true }}
|
||||
>
|
||||
<FastImage
|
||||
source={{ uri: novel.novelCover }}
|
||||
style={{ width: 85, height: 120, borderRadius: 4 }}
|
||||
style={{
|
||||
width: 130,
|
||||
height: 180,
|
||||
borderTopLeftRadius: 8,
|
||||
borderBottomLeftRadius: 8,
|
||||
}}
|
||||
/>
|
||||
<View style={{ flex: 1, paddingLeft: 16 }}>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{ color: theme.textColorPrimary, fontSize: 16 }}
|
||||
style={{ color: theme.textColorPrimary, fontSize: 17 }}
|
||||
>
|
||||
{novel.novelName}
|
||||
</Text>
|
||||
|
||||
<GenreChip theme={theme}>{novel.genres}</GenreChip>
|
||||
<View style={{ flexDirection: "row", marginBottom: 4 }}>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{ color: theme.textColorSecondary },
|
||||
]}
|
||||
numberOfLines={2}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{`${"Chapter Count "}`}
|
||||
</Text>
|
||||
{novel.chapterCount}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 13,
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 12,
|
||||
color: theme.textColorSecondary,
|
||||
marginTop: 8,
|
||||
}}
|
||||
numberOfLines={3}
|
||||
numberOfLines={4}
|
||||
>
|
||||
{novel.novelSummary}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -59,14 +109,14 @@ export default NovelUpdatesNovelCard;
|
||||
const styles = StyleSheet.create({
|
||||
novelUpdatesCard: {
|
||||
flex: 1,
|
||||
padding: 16,
|
||||
flexDirection: "row",
|
||||
},
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
genreChip: {
|
||||
marginVertical: 4,
|
||||
fontSize: 12,
|
||||
marginTop: 4,
|
||||
fontSize: 13,
|
||||
// fontStyle: "italic",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -34,7 +34,31 @@ const scrapeTopNovels = async (pageNo) => {
|
||||
const novelSummary =
|
||||
$(this).find(".search_genre")[0].nextSibling.nodeValue;
|
||||
|
||||
const novel = { novelName, novelCover, genres, novelSummary };
|
||||
let chapterCount = "";
|
||||
|
||||
$(this)
|
||||
.find(".search_stats > span")
|
||||
.each(function () {
|
||||
let detail = $(this).text();
|
||||
|
||||
if (detail.includes("Chapters")) {
|
||||
chapterCount = detail.replace(" Chapters", "");
|
||||
}
|
||||
});
|
||||
|
||||
// const chapterCount = $(this)
|
||||
// .find('i[title="Chapter Count"]')
|
||||
// const updateFrequency = $(this)
|
||||
// .find('i[title="Updates Frequency"]')
|
||||
|
||||
const novel = {
|
||||
novelName,
|
||||
novelCover,
|
||||
genres,
|
||||
novelSummary,
|
||||
chapterCount,
|
||||
// updateFrequency,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
@@ -77,7 +101,25 @@ const scrapeSearchResults = async (searchTerm) => {
|
||||
const novelSummary =
|
||||
$(this).find(".search_genre")[0].nextSibling.nodeValue;
|
||||
|
||||
const novel = { novelName, novelCover, genres, novelSummary };
|
||||
let chapterCount = "";
|
||||
|
||||
$(this)
|
||||
.find(".search_stats > span")
|
||||
.each(function () {
|
||||
let detail = $(this).text();
|
||||
|
||||
if (detail.includes("Chapters")) {
|
||||
chapterCount = detail.replace(" Chapters", "");
|
||||
}
|
||||
});
|
||||
|
||||
const novel = {
|
||||
novelName,
|
||||
novelCover,
|
||||
genres,
|
||||
novelSummary,
|
||||
chapterCount,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
@@ -42,7 +42,10 @@ const GlobalSearch = ({ route, navigation }) => {
|
||||
const library = useLibrary();
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
novelName && onSubmitEditing();
|
||||
|
||||
return () => (mounted = false);
|
||||
}, []);
|
||||
|
||||
const clearSearchbar = () => setSearchText("");
|
||||
@@ -50,44 +53,46 @@ const GlobalSearch = ({ route, navigation }) => {
|
||||
|
||||
const onSubmitEditing = async () => {
|
||||
setSearchResults([]);
|
||||
setProgress(0);
|
||||
|
||||
let globalSearchSources = searchAllSources ? sources : pinnedSources;
|
||||
|
||||
globalSearchSources.map((item, index) =>
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
for (let i = 0; i < globalSearchSources.length; i++) {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const source = getSource(item.sourceId);
|
||||
const data = await source.searchNovels(
|
||||
encodeURI(searchText)
|
||||
);
|
||||
const source = getSource(globalSearchSources[i].sourceId);
|
||||
const data = await source.searchNovels(encodeURI(searchText));
|
||||
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName,
|
||||
lang: item.lang,
|
||||
novels: data,
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
showToast(error.message);
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName,
|
||||
lang: item.lang,
|
||||
novels: [],
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
}
|
||||
}, 1000 * index)
|
||||
);
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: globalSearchSources[i].sourceId,
|
||||
sourceName: globalSearchSources[i].sourceName,
|
||||
lang: globalSearchSources[i].lang,
|
||||
novels: data,
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
setProgress(
|
||||
(progress) => progress + 1 / globalSearchSources.length
|
||||
);
|
||||
} catch (error) {
|
||||
showToast(
|
||||
globalSearchSources[i].sourceName + ": " + error.message
|
||||
);
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: globalSearchSources[i].sourceId,
|
||||
sourceName: globalSearchSources[i].sourceName,
|
||||
lang: globalSearchSources[i].lang,
|
||||
novels: [],
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
@@ -111,15 +116,8 @@ const GlobalSearch = ({ route, navigation }) => {
|
||||
onSubmitEditing={onSubmitEditing}
|
||||
clearSearchbar={clearSearchbar}
|
||||
/>
|
||||
{progress < 1 && progress > 0 && pinned && (
|
||||
<ProgressBar
|
||||
color={
|
||||
progress > 0.9
|
||||
? theme.colorPrimaryDark
|
||||
: theme.colorAccent
|
||||
}
|
||||
progress={progress}
|
||||
/>
|
||||
{progress > 0 && (
|
||||
<ProgressBar color={theme.colorAccent} progress={progress} />
|
||||
)}
|
||||
<FlatList
|
||||
contentContainerStyle={{
|
||||
@@ -135,12 +133,12 @@ const GlobalSearch = ({ route, navigation }) => {
|
||||
{!loading && (
|
||||
<EmptyView
|
||||
icon="__φ(..)"
|
||||
description={`Search a novel in your pinned sources\n${
|
||||
description={`Search a novel in ${
|
||||
searchAllSources
|
||||
? "(Search all sources)"
|
||||
? "all sources"
|
||||
: pinned.length === 0
|
||||
? "(No sources pinned)"
|
||||
: ""
|
||||
? "pinned sources\n(No sources pinned)"
|
||||
: "pinned sources"
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -111,12 +111,12 @@ const MigrationNovelList = ({ data, theme, library, navigation }) => {
|
||||
textTransform: "none",
|
||||
}}
|
||||
theme={{ colors: { primary: theme.colorAccent } }}
|
||||
onPress={() => {
|
||||
migrateNovel(
|
||||
onPress={async () => {
|
||||
hideMigrateNovelDialog();
|
||||
await migrateNovel(
|
||||
selectedNovel.sourceId,
|
||||
selectedNovel.novelUrl
|
||||
);
|
||||
hideMigrateNovelDialog();
|
||||
showToast(
|
||||
`${selectedNovel.novelName} migrated to new source.`
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
ActivityIndicator,
|
||||
} from "react-native";
|
||||
import { ProgressBar } from "react-native-paper";
|
||||
import { useLibrary, useTheme } from "../../../hooks/reduxHooks";
|
||||
import { useLibrary, useSettings, useTheme } from "../../../hooks/reduxHooks";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import EmptyView from "../../../components/EmptyView";
|
||||
@@ -26,7 +26,13 @@ const MigrationNovels = ({ navigation, route }) => {
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchResults, setSearchResults] = useState("");
|
||||
const { sources, pinned } = useSelector((state) => state.sourceReducer);
|
||||
const { searchAllSources = false } = useSettings();
|
||||
let {
|
||||
sources,
|
||||
pinned,
|
||||
filters = [],
|
||||
} = useSelector((state) => state.sourceReducer);
|
||||
sources = sources.filter((source) => filters.indexOf(source.lang) === -1);
|
||||
|
||||
const library = useLibrary();
|
||||
|
||||
@@ -36,41 +42,43 @@ const MigrationNovels = ({ navigation, route }) => {
|
||||
source.sourceId !== sourceId
|
||||
);
|
||||
|
||||
const getSearchResults = () => {
|
||||
pinnedSources.map((item, index) =>
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const getSearchResults = async () => {
|
||||
let migrationSources = searchAllSources ? sources : pinnedSources;
|
||||
for (let i = 0; i < migrationSources.length; i++) {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const source = getSource(item.sourceId);
|
||||
const data = await source.searchNovels(novelName);
|
||||
const source = getSource(migrationSources[i].sourceId);
|
||||
const data = await source.searchNovels(novelName);
|
||||
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName,
|
||||
sourceLanguage: item.sourceLanguage,
|
||||
novels: data,
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
showToast(error.message);
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: migrationSources[i].sourceId,
|
||||
sourceName: migrationSources[i].sourceName,
|
||||
sourceLanguage: migrationSources[i].lang,
|
||||
novels: data,
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
setProgress(
|
||||
(progress) => progress + 1 / migrationSources.length
|
||||
);
|
||||
} catch (error) {
|
||||
showToast(error.message);
|
||||
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName,
|
||||
sourceLanguage: item.sourceLanguage,
|
||||
novels: [],
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
}
|
||||
}, 1000 * index)
|
||||
);
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: migrationSources[i].sourceId,
|
||||
sourceName: migrationSources[i].sourceName,
|
||||
sourceLanguage: migrationSources[i].lang,
|
||||
novels: [],
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -102,7 +110,7 @@ const MigrationNovels = ({ navigation, route }) => {
|
||||
title={novelName}
|
||||
onBackAction={() => navigation.goBack()}
|
||||
/>
|
||||
{progress < 1 && pinned && (
|
||||
{progress > 0 && (
|
||||
<ProgressBar color={theme.colorAccent} progress={progress} />
|
||||
)}
|
||||
<FlatList
|
||||
|
||||
@@ -70,7 +70,11 @@ const Chapter = ({ route, navigation }) => {
|
||||
const showScrollPercentage = useSelector(
|
||||
(state) => state.settingsReducer.showScrollPercentage
|
||||
);
|
||||
const { swipeGestures = true, incognitoMode = false } = useSettings();
|
||||
const {
|
||||
swipeGestures = true,
|
||||
incognitoMode = false,
|
||||
textSelectable = false,
|
||||
} = useSettings();
|
||||
|
||||
const [hidden, setHidden] = useState(true);
|
||||
|
||||
@@ -85,7 +89,6 @@ const Chapter = ({ route, navigation }) => {
|
||||
|
||||
const [scrollPercentage, setScrollPercentage] = useState(0);
|
||||
const [firstLayout, setFirstLayout] = useState(true);
|
||||
const [selectText, setSelectText] = useState(false);
|
||||
|
||||
const [contentSize, setContentSize] = useState(0);
|
||||
|
||||
@@ -218,6 +221,14 @@ const Chapter = ({ route, navigation }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const hideHeader = () => {
|
||||
if (!hidden) {
|
||||
StatusBar.setHidden(true);
|
||||
hideNavigationBar();
|
||||
}
|
||||
setHidden(!hidden);
|
||||
};
|
||||
|
||||
const readerStyles = [
|
||||
{
|
||||
paddingVertical: 16,
|
||||
@@ -341,8 +352,8 @@ const Chapter = ({ route, navigation }) => {
|
||||
<Text
|
||||
style={readerStyles}
|
||||
onLayout={scrollToSavedProgress}
|
||||
selectable={selectText}
|
||||
onPress={() => setHidden(!hidden)}
|
||||
selectable={textSelectable}
|
||||
onPress={hideHeader}
|
||||
>
|
||||
{chapter.chapterText
|
||||
.trim()
|
||||
@@ -357,8 +368,7 @@ const Chapter = ({ route, navigation }) => {
|
||||
reader={reader}
|
||||
dispatch={dispatch}
|
||||
bottomSheetRef={readerSheetRef}
|
||||
selectText={selectText}
|
||||
setSelectText={setSelectText}
|
||||
selectText={textSelectable}
|
||||
showScrollPercentage={showScrollPercentage}
|
||||
/>
|
||||
</Portal>
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
import React from "react";
|
||||
import { Dimensions, View, Text, StyleSheet } from "react-native";
|
||||
|
||||
import Slider from "@react-native-community/slider";
|
||||
import { getDeviceOrientation } from "../../../services/utils/helpers";
|
||||
|
||||
const ReaderSeekbar = ({
|
||||
theme,
|
||||
hide,
|
||||
contentSize,
|
||||
scrollPercentage,
|
||||
setLoading,
|
||||
scrollViewRef,
|
||||
}) => {
|
||||
const onSlidingComplete = (value) => {
|
||||
setLoading(true);
|
||||
scrollViewRef.current.scrollTo({
|
||||
x: 0,
|
||||
y: Math.round((value * contentSize) / 100),
|
||||
animated: false,
|
||||
});
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
if (hide) {
|
||||
return null;
|
||||
} else if (getDeviceOrientation() === "potrait") {
|
||||
return (
|
||||
<View style={styles.verticalSliderContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
marginLeft: 16,
|
||||
transform: [{ rotate: "-90deg" }],
|
||||
}}
|
||||
>
|
||||
{scrollPercentage}
|
||||
</Text>
|
||||
<Slider
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 40,
|
||||
}}
|
||||
minimumValue={0}
|
||||
maximumValue={100}
|
||||
step={1}
|
||||
value={scrollPercentage}
|
||||
onSlidingComplete={onSlidingComplete}
|
||||
thumbTintColor={theme.colorAccent}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor={theme.textColorPrimary}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
marginRight: 16,
|
||||
transform: [{ rotate: "-90deg" }],
|
||||
}}
|
||||
>
|
||||
100
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<View style={styles.horizontalSliderContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
marginLeft: 16,
|
||||
}}
|
||||
>
|
||||
{scrollPercentage}
|
||||
</Text>
|
||||
<Slider
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 40,
|
||||
}}
|
||||
minimumValue={0}
|
||||
maximumValue={100}
|
||||
step={1}
|
||||
value={scrollPercentage}
|
||||
onSlidingComplete={onSlidingComplete}
|
||||
thumbTintColor={theme.colorAccent}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor={theme.textColorPrimary}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
marginRight: 16,
|
||||
}}
|
||||
>
|
||||
100
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default ReaderSeekbar;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
verticalSliderContainer: {
|
||||
position: "absolute",
|
||||
zIndex: 2,
|
||||
right: -(Dimensions.get("window").width / 2) + 40,
|
||||
bottom: 300,
|
||||
backgroundColor: "rgba(0,0,0,0.7)",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 8,
|
||||
transform: [{ rotate: "90deg" }],
|
||||
},
|
||||
horizontalSliderContainer: {
|
||||
position: "absolute",
|
||||
zIndex: 2,
|
||||
bottom: 100,
|
||||
backgroundColor: "rgba(0,0,0,0.7)",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 8,
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import { StyleSheet, View, Text, Dimensions } from "react-native";
|
||||
import { StyleSheet, View, Text, Dimensions, Pressable } from "react-native";
|
||||
import { ToggleButton, IconButton, Menu, Switch } from "react-native-paper";
|
||||
import Slider from "@react-native-community/slider";
|
||||
import Bottomsheet from "rn-sliding-up-panel";
|
||||
@@ -18,7 +18,6 @@ const ReaderSheet = ({
|
||||
dispatch,
|
||||
bottomSheetRef,
|
||||
selectText,
|
||||
setSelectText,
|
||||
showScrollPercentage,
|
||||
}) => {
|
||||
const [fontMenu, setFontMenu] = useState(false);
|
||||
@@ -69,6 +68,7 @@ const ReaderSheet = ({
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
<ReaderSettingTitle title="Reader Theme" />
|
||||
@@ -146,7 +146,7 @@ const ReaderSheet = ({
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
paddingHorizontal: 16,
|
||||
marginTop: 16,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
<ReaderSettingTitle title="Text Align" />
|
||||
@@ -217,7 +217,7 @@ const ReaderSheet = ({
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
paddingHorizontal: 16,
|
||||
marginTop: 16,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
<ReaderSettingTitle title="Padding" />
|
||||
@@ -267,7 +267,7 @@ const ReaderSheet = ({
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
paddingHorizontal: 16,
|
||||
marginTop: 16,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
<ReaderSettingTitle title="Line Height" />
|
||||
@@ -314,13 +314,17 @@ const ReaderSheet = ({
|
||||
/>
|
||||
</Row>
|
||||
</Row>
|
||||
<Row
|
||||
<Pressable
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
paddingHorizontal: 16,
|
||||
marginTop: 16,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
android_ripple={{ color: theme.colorAccent }}
|
||||
onPress={openFontMenu}
|
||||
>
|
||||
<ReaderSettingTitle title="Font Style" />
|
||||
<Menu
|
||||
@@ -329,12 +333,11 @@ const ReaderSheet = ({
|
||||
contentStyle={{ backgroundColor: theme.menuColor }}
|
||||
anchor={
|
||||
<Text
|
||||
onPress={openFontMenu}
|
||||
style={{
|
||||
color: "white",
|
||||
paddingHorizontal: 16,
|
||||
fontSize: 16,
|
||||
fontFamily: reader.fontFamily,
|
||||
paddingHorizontal: 16,
|
||||
}}
|
||||
>
|
||||
{fonts.find(
|
||||
@@ -368,14 +371,25 @@ const ReaderSheet = ({
|
||||
/>
|
||||
))}
|
||||
</Menu>
|
||||
</Row>
|
||||
<Row
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
paddingHorizontal: 16,
|
||||
marginTop: 16,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
android_ripple={{ color: theme.colorAccent }}
|
||||
onPress={() =>
|
||||
dispatch(
|
||||
setAppSettings(
|
||||
"showScrollPercentage",
|
||||
!showScrollPercentage
|
||||
)
|
||||
)
|
||||
}
|
||||
>
|
||||
<ReaderSettingTitle title="Show Progress Percentage" />
|
||||
<Switch
|
||||
@@ -390,14 +404,22 @@ const ReaderSheet = ({
|
||||
}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</Row>
|
||||
<Row
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
paddingHorizontal: 16,
|
||||
marginTop: 16,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
android_ripple={{ color: theme.colorAccent }}
|
||||
onPress={() =>
|
||||
dispatch(
|
||||
setAppSettings("textSelectable", !selectText)
|
||||
)
|
||||
}
|
||||
>
|
||||
<View>
|
||||
<ReaderSettingTitle title="Select Text " />
|
||||
@@ -412,10 +434,17 @@ const ReaderSheet = ({
|
||||
</View>
|
||||
<Switch
|
||||
value={selectText}
|
||||
onValueChange={setSelectText}
|
||||
onValueChange={() =>
|
||||
dispatch(
|
||||
setAppSettings(
|
||||
"textSelectable",
|
||||
!selectText
|
||||
)
|
||||
)
|
||||
}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</Row>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
</Bottomsheet>
|
||||
|
||||
@@ -189,7 +189,8 @@ const Extension = ({ navigation, route }) => {
|
||||
onScroll={onScroll}
|
||||
ListFooterComponent={
|
||||
!searchText &&
|
||||
page < totalPages && (
|
||||
page < totalPages &&
|
||||
novels.length && (
|
||||
<View style={{ paddingVertical: 16 }}>
|
||||
<ActivityIndicator color={theme.colorAccent} />
|
||||
</View>
|
||||
|
||||
@@ -93,10 +93,10 @@ const History = ({ navigation }) => {
|
||||
paddingHorizontal: 16,
|
||||
}}
|
||||
data={searchText ? searchResults : history}
|
||||
keyExtractor={(index) => index.toString()}
|
||||
keyExtractor={(item, index) => item.date}
|
||||
renderItem={({ item }) => (
|
||||
<FlatList
|
||||
keyExtractor={(item, index) => index.toString()}
|
||||
keyExtractor={(item, index) => item.novelId.toString()}
|
||||
data={item.novels}
|
||||
renderItem={renderHistoryCard}
|
||||
ListHeaderComponent={
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import React, { useState, useCallback, useRef } from "react";
|
||||
import React, { useState, useCallback, useRef, useEffect } from "react";
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
RefreshControl,
|
||||
ActivityIndicator,
|
||||
TouchableOpacity,
|
||||
} from "react-native";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
|
||||
import { Searchbar } from "../../components/Searchbar";
|
||||
import NovelList from "../../components/NovelList";
|
||||
@@ -29,7 +27,11 @@ import LibraryFilterSheet from "./components/LibraryFilterSheet";
|
||||
const LibraryScreen = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const { showNumberOfNovels, incognitoMode = false } = useSettings();
|
||||
const {
|
||||
showNumberOfNovels,
|
||||
incognitoMode = false,
|
||||
updateLibraryOnLaunch = false,
|
||||
} = useSettings();
|
||||
|
||||
const libraryFilterSheetRef = useRef(null);
|
||||
|
||||
@@ -49,6 +51,10 @@ const LibraryScreen = ({ navigation }) => {
|
||||
}, [getLibraryAction, sort, filter])
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
updateLibraryOnLaunch && dispatch(updateLibraryAction());
|
||||
}, []);
|
||||
|
||||
const onRefresh = () => {
|
||||
setRefreshing(true);
|
||||
dispatch(updateLibraryAction());
|
||||
|
||||
@@ -17,12 +17,12 @@ const AboutScreen = ({ navigation }) => {
|
||||
<ListSection>
|
||||
<ListItem
|
||||
title="Version"
|
||||
description="Stable 1.1.0"
|
||||
description="Stable 1.1.1"
|
||||
theme={theme}
|
||||
/>
|
||||
<ListItem
|
||||
title="Build Time"
|
||||
description="20-06-21 21:45 PM"
|
||||
description="04-07-21 21:00 PM"
|
||||
theme={theme}
|
||||
/>
|
||||
<ListItem
|
||||
|
||||
@@ -10,12 +10,16 @@ import {
|
||||
} from "../../components/List";
|
||||
import { ScreenContainer } from "../../components/Common";
|
||||
|
||||
import { useTheme } from "../../hooks/reduxHooks";
|
||||
import { useSettings, useTheme } from "../../hooks/reduxHooks";
|
||||
import DisplayModeModal from "./components/DisplayModeModal";
|
||||
import GridSizeModal from "./components/GridSizeModal";
|
||||
import ThemeModal from "./components/ThemeModal";
|
||||
import { setAccentColor } from "../../redux/settings/settings.actions";
|
||||
import {
|
||||
setAccentColor,
|
||||
setAppSettings,
|
||||
} from "../../redux/settings/settings.actions";
|
||||
import ColorPickerModal from "../../components/ColorPickerModal";
|
||||
import SwitchSetting from "../../components/Switch/Switch";
|
||||
|
||||
const GenralSettings = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
@@ -25,6 +29,8 @@ const GenralSettings = ({ navigation }) => {
|
||||
(state) => state.settingsReducer
|
||||
);
|
||||
|
||||
const { updateLibraryOnLaunch = false } = useSettings();
|
||||
|
||||
const displayModeLabel = (displayMode) => {
|
||||
const label = {
|
||||
0: "Compact Grid",
|
||||
@@ -81,6 +87,20 @@ const GenralSettings = ({ navigation }) => {
|
||||
theme={theme}
|
||||
/>
|
||||
<Divider theme={theme} />
|
||||
<ListSubHeader theme={theme}>Library</ListSubHeader>
|
||||
<SwitchSetting
|
||||
label="Update library on launch"
|
||||
value={updateLibraryOnLaunch}
|
||||
onPress={() =>
|
||||
dispatch(
|
||||
setAppSettings(
|
||||
"updateLibraryOnLaunch",
|
||||
!updateLibraryOnLaunch
|
||||
)
|
||||
)
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<ListSubHeader theme={theme}>Theme</ListSubHeader>
|
||||
<ListItem
|
||||
title="Theme"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { StyleSheet, View, Text, Pressable } from "react-native";
|
||||
import { Portal, Modal, Checkbox } from "react-native-paper";
|
||||
import { setAppSettings } from "../../../redux/settings/settings.actions";
|
||||
|
||||
@@ -18,20 +18,39 @@ const DisplayModeModal = ({
|
||||
|
||||
const renderCheckboxes = () => {
|
||||
return displayModes.map((mode) => (
|
||||
<Checkbox.Item
|
||||
<View
|
||||
key={mode.displayMode}
|
||||
label={mode.label}
|
||||
labelStyle={{ color: theme.textColorPrimary }}
|
||||
status={
|
||||
displayMode === mode.displayMode ? "checked" : "unchecked"
|
||||
}
|
||||
mode="ios"
|
||||
uncheckedColor={theme.textColorSecondary}
|
||||
color={theme.colorAccent}
|
||||
onPress={() =>
|
||||
dispatch(setAppSettings("displayMode", mode.displayMode))
|
||||
}
|
||||
/>
|
||||
style={[
|
||||
{
|
||||
borderRadius: 8,
|
||||
overflow: "hidden",
|
||||
marginVertical: 4,
|
||||
},
|
||||
displayMode === mode.displayMode && {
|
||||
backgroundColor: theme.rippleColor,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Pressable
|
||||
android_ripple={{
|
||||
color: theme.rippleColor,
|
||||
}}
|
||||
style={{
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
onPress={() =>
|
||||
dispatch(
|
||||
setAppSettings("displayMode", mode.displayMode)
|
||||
)
|
||||
}
|
||||
>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
{mode.label}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
));
|
||||
};
|
||||
|
||||
|
||||
@@ -46,17 +46,6 @@ const GridSizeModal = ({
|
||||
>
|
||||
{`${novelsPerRow} per row`}
|
||||
</Text>
|
||||
{/* <Slider
|
||||
style={styles.slider}
|
||||
value={novelsPerRow}
|
||||
minimumValue={1}
|
||||
maximumValue={5}
|
||||
step={1}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor="#000000"
|
||||
thumbTintColor={theme.colorAccent}
|
||||
onValueChange={(value) => dispatch(setNovelsPerRow(value))}
|
||||
/> */}
|
||||
<RadioButtonGroup
|
||||
onValueChange={(value) => dispatch(setNovelsPerRow(value))}
|
||||
value={novelsPerRow}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { Pressable, StyleSheet, View, Text } from "react-native";
|
||||
import { Portal, Modal, Checkbox } from "react-native-paper";
|
||||
|
||||
import { setStatusBarStyle } from "expo-status-bar";
|
||||
@@ -8,30 +8,53 @@ import ImmersiveMode from "react-native-immersive-mode";
|
||||
|
||||
const ThemeModal = ({ themeModalVisible, hidethemeModal, dispatch, theme }) => {
|
||||
const themes = [
|
||||
{ id: 0, name: "AMOLED Dark", statusBar: "light" },
|
||||
{ id: 1, name: "Light", statusBar: "dark" },
|
||||
{ id: 7, name: "Strawberry Daiquiri", statusBar: "dark" },
|
||||
{ id: 2, name: "Dark", statusBar: "light" },
|
||||
{ id: 3, name: "Midnight Dusk", statusBar: "light" },
|
||||
{ id: 4, name: "Lime", statusBar: "light" },
|
||||
{ id: 4, name: "Green Apple", statusBar: "light" },
|
||||
{ id: 5, name: "Iris Blue", statusBar: "light" },
|
||||
{ id: 0, name: "AMOLED Dark", statusBar: "light" },
|
||||
{ id: 6, name: "AMOLED Hot Pink", statusBar: "light" },
|
||||
];
|
||||
|
||||
const renderThemeCheckboxes = () =>
|
||||
themes.map((item) => (
|
||||
<Checkbox.Item
|
||||
key={item.id}
|
||||
label={item.name}
|
||||
labelStyle={{ color: theme.textColorPrimary }}
|
||||
status={theme.id === item.id ? "checked" : "unchecked"}
|
||||
mode="ios"
|
||||
color={theme.colorAccent}
|
||||
const ThemeItem = ({ item }) => (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
borderRadius: 8,
|
||||
overflow: "hidden",
|
||||
marginVertical: 4,
|
||||
},
|
||||
theme.id === item.id && {
|
||||
backgroundColor: theme.rippleColor,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Pressable
|
||||
android_ripple={{
|
||||
color: theme.rippleColor,
|
||||
}}
|
||||
style={{
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
onPress={() => {
|
||||
dispatch(setAppTheme(item.id));
|
||||
// setStatusBarStyle(item.statusBar);
|
||||
setStatusBarStyle(item.statusBar);
|
||||
ImmersiveMode.setBarColor(item.colorPrimary);
|
||||
}}
|
||||
/>
|
||||
));
|
||||
>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
|
||||
const renderThemeCheckboxes = () =>
|
||||
themes.map((item) => <ThemeItem item={item} key={item.id} />);
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
|
||||
@@ -38,6 +38,7 @@ import ChaptersSettingsSheet from "./components/ChaptersSettingsSheet";
|
||||
import TrackSheet from "./components/Tracker/TrackSheet";
|
||||
import ChapterActionsSheet from "./components/ChapterActionsSheet";
|
||||
import { Row } from "../../components/Common";
|
||||
import JumpToChapterModal from "./components/JumpToChapterModal";
|
||||
|
||||
const Novel = ({ route, navigation }) => {
|
||||
const item = route.params;
|
||||
@@ -51,6 +52,7 @@ const Novel = ({ route, navigation }) => {
|
||||
const [selected, setSelected] = useState([]);
|
||||
const [downloadMenu, showDownloadMenu] = useState(false);
|
||||
|
||||
let flatlistRef = useRef(null);
|
||||
let chaptersSettingsSheetRef = useRef(null);
|
||||
let trackerSheetRef = useRef(null);
|
||||
let chapterActionsSheetRef = useRef(null);
|
||||
@@ -86,6 +88,8 @@ const Novel = ({ route, navigation }) => {
|
||||
/>
|
||||
);
|
||||
|
||||
const [jumpToChapterModal, showJumpToChapterModal] = useState(false);
|
||||
|
||||
const renderItem = ({ item, index }) => (
|
||||
<ChapterItem
|
||||
theme={theme}
|
||||
@@ -217,6 +221,15 @@ const Novel = ({ route, navigation }) => {
|
||||
}
|
||||
/>
|
||||
</Menu>
|
||||
<IconButton
|
||||
icon="text-box-search-outline"
|
||||
color="white"
|
||||
size={21}
|
||||
style={{
|
||||
marginTop: StatusBar.currentHeight + 8,
|
||||
}}
|
||||
onPress={() => showJumpToChapterModal(true)}
|
||||
/>
|
||||
<IconButton
|
||||
icon="share-variant"
|
||||
color="white"
|
||||
@@ -235,6 +248,7 @@ const Novel = ({ route, navigation }) => {
|
||||
</View>
|
||||
)}
|
||||
<FlatList
|
||||
ref={(ref) => (flatlistRef.current = ref)}
|
||||
data={!loading && chapters}
|
||||
keyExtractor={(item) => item.chapterId.toString()}
|
||||
removeClippedSubviews={true}
|
||||
@@ -362,6 +376,14 @@ const Novel = ({ route, navigation }) => {
|
||||
)}
|
||||
{!loading && (
|
||||
<Portal>
|
||||
<JumpToChapterModal
|
||||
modalVisible={jumpToChapterModal}
|
||||
hideModal={() => showJumpToChapterModal(false)}
|
||||
theme={theme}
|
||||
chapters={chapters}
|
||||
novel={novel}
|
||||
navigation={navigation}
|
||||
/>
|
||||
<ChaptersSettingsSheet
|
||||
novelUrl={novelUrl}
|
||||
bottomSheetRef={chaptersSettingsSheetRef}
|
||||
|
||||
@@ -89,16 +89,14 @@ const FollowButton = ({ theme, onPress, followed }) => (
|
||||
>
|
||||
<IconButton
|
||||
icon={followed ? "heart" : "heart-outline"}
|
||||
color={followed ? theme.colorAccent : theme.textColorSecondary}
|
||||
color={followed ? theme.colorAccent : theme.textColorHint}
|
||||
size={24}
|
||||
style={{ margin: 0 }}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: followed
|
||||
? theme.colorAccent
|
||||
: theme.textColorSecondary,
|
||||
color: followed ? theme.colorAccent : theme.textColorHint,
|
||||
}}
|
||||
>
|
||||
{followed ? "In Library" : "Add to library"}
|
||||
@@ -123,16 +121,14 @@ const TrackerButton = ({ theme, isTracked, onPress }) => (
|
||||
>
|
||||
<IconButton
|
||||
icon={isTracked ? "check" : "sync"}
|
||||
color={isTracked ? theme.colorAccent : theme.textColorSecondary}
|
||||
color={isTracked ? theme.colorAccent : theme.textColorHint}
|
||||
size={24}
|
||||
style={{ margin: 0 }}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: isTracked
|
||||
? theme.colorAccent
|
||||
: theme.textColorSecondary,
|
||||
color: isTracked ? theme.colorAccent : theme.textColorHint,
|
||||
}}
|
||||
>
|
||||
{isTracked ? "Tracked" : "Tracking"}
|
||||
|
||||
@@ -38,7 +38,8 @@ export default NovelSummary;
|
||||
const styles = StyleSheet.create({
|
||||
summaryContainer: {
|
||||
paddingHorizontal: 16,
|
||||
marginVertical: 16,
|
||||
marginTop: 8,
|
||||
marginBottom: 16,
|
||||
},
|
||||
// summaryHeader: {
|
||||
// marginTop: 5,
|
||||
|
||||
@@ -24,7 +24,7 @@ const ReadButton = ({ navigation, novel, chapters, theme, lastRead }) => {
|
||||
sourceId: novel.sourceId,
|
||||
chapterName: lastRead.chapterName,
|
||||
novelName: novel.novelName,
|
||||
novelName: lastRead.bookmark,
|
||||
bookmark: lastRead.bookmark,
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import React, { useState } from "react";
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
|
||||
import { Modal, Portal, Switch, TextInput } from "react-native-paper";
|
||||
|
||||
const JumpToChapterModal = ({
|
||||
theme,
|
||||
hideModal,
|
||||
modalVisible,
|
||||
chapters,
|
||||
navigation,
|
||||
novel,
|
||||
}) => {
|
||||
const [mode, setMode] = useState(0);
|
||||
|
||||
const [text, setText] = useState();
|
||||
const [error, setError] = useState();
|
||||
|
||||
const onDismiss = () => {
|
||||
hideModal();
|
||||
setText();
|
||||
setError();
|
||||
};
|
||||
|
||||
const onSubmit = () => {
|
||||
if (!mode) {
|
||||
if (text > 0 && text <= chapters.length) {
|
||||
navigation.navigate("Chapter", {
|
||||
sourceId: novel.sourceId,
|
||||
novelUrl: novel.novelUrl,
|
||||
novelId: novel.novelId,
|
||||
chapterId: chapters[text - 1].chapterId,
|
||||
chapterUrl: chapters[text - 1].chapterUrl,
|
||||
chapterName: chapters[text - 1].chapterName,
|
||||
novelName: novel.novelName,
|
||||
bookmark: chapters[text - 1].bookmark,
|
||||
});
|
||||
hideModal();
|
||||
} else {
|
||||
setError(
|
||||
`Enter a valid chapter number (<= ${chapters.length})`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const chapter = chapters.find((chap) =>
|
||||
chap.chapterName.toLowerCase().includes(text.toLowerCase())
|
||||
);
|
||||
|
||||
if (chapter) {
|
||||
navigation.navigate("Chapter", {
|
||||
sourceId: novel.sourceId,
|
||||
novelUrl: novel.novelUrl,
|
||||
novelId: chapter.novelId,
|
||||
chapterId: chapter.chapterId,
|
||||
chapterUrl: chapter.chapterUrl,
|
||||
chapterName: chapter.chapterName,
|
||||
novelName: novel.novelName,
|
||||
bookmark: chapter.bookmark,
|
||||
});
|
||||
hideModal();
|
||||
} else {
|
||||
setError(`Enter a valid chapter name`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeText = (text) => setText(text);
|
||||
|
||||
const textInputTheme = {
|
||||
colors: {
|
||||
primary: theme.colorAccent,
|
||||
placeholder: theme.textColorHint,
|
||||
text: theme.textColorPrimary,
|
||||
background: theme.colorPrimary,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={modalVisible}
|
||||
onDismiss={onDismiss}
|
||||
contentContainerStyle={[
|
||||
styles.modalContainer,
|
||||
{ backgroundColor: theme.colorPrimary },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.modalTitle,
|
||||
{ color: theme.textColorPrimary },
|
||||
]}
|
||||
>
|
||||
Jump to Chapter
|
||||
</Text>
|
||||
<TextInput
|
||||
value={text}
|
||||
placeholder={
|
||||
mode
|
||||
? "Chapter Name"
|
||||
: `Chapter Number (<= ${chapters.length})`
|
||||
}
|
||||
onChangeText={onChangeText}
|
||||
onSubmitEditing={onSubmit}
|
||||
mode="outlined"
|
||||
theme={textInputTheme}
|
||||
underlineColor={theme.textColorHint}
|
||||
dense
|
||||
error={error}
|
||||
/>
|
||||
<Text style={styles.errorText}>{error}</Text>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
marginTop: 4,
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Chapter Name
|
||||
</Text>
|
||||
<Switch
|
||||
value={mode}
|
||||
onValueChange={() => setMode(!mode)}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</View>
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
export default JumpToChapterModal;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modalContainer: {
|
||||
margin: 30,
|
||||
padding: 20,
|
||||
borderRadius: 8,
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 18,
|
||||
marginBottom: 16,
|
||||
},
|
||||
errorText: {
|
||||
color: "#FF0033",
|
||||
paddingTop: 8,
|
||||
},
|
||||
});
|
||||
@@ -129,14 +129,14 @@ const NovelInfoHeader = ({
|
||||
>
|
||||
<IconButton
|
||||
icon="swap-vertical-variant"
|
||||
color={theme.textColorSecondary}
|
||||
color={theme.textColorHint}
|
||||
size={24}
|
||||
style={{ margin: 0 }}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: theme.textColorSecondary,
|
||||
color: theme.textColorHint,
|
||||
}}
|
||||
>
|
||||
Migrate
|
||||
@@ -166,14 +166,14 @@ const NovelInfoHeader = ({
|
||||
>
|
||||
<IconButton
|
||||
icon="earth"
|
||||
color={theme.textColorSecondary}
|
||||
color={theme.textColorHint}
|
||||
size={24}
|
||||
style={{ margin: 0 }}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: theme.textColorSecondary,
|
||||
color: theme.textColorHint,
|
||||
}}
|
||||
>
|
||||
WebView
|
||||
|
||||
@@ -1,36 +1,40 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View, Text, Image } from "react-native";
|
||||
import { StyleSheet, View, Text, Image, Pressable } from "react-native";
|
||||
import { TouchableRipple, IconButton } from "react-native-paper";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { untrackNovel } from "../../../../redux/tracker/tracker.actions";
|
||||
|
||||
export const AddMalTrackingCard = ({ theme, setTrackSearchDialog }) => (
|
||||
<View
|
||||
style={[
|
||||
styles.addCardContainer,
|
||||
{ backgroundColor: theme.colorPrimary },
|
||||
]}
|
||||
>
|
||||
<View style={styles.addCardContainer}>
|
||||
<Image
|
||||
source={require("../../../../../assets/mal.png")}
|
||||
style={styles.trackerIcon}
|
||||
/>
|
||||
<TouchableRipple
|
||||
style={styles.rippleContainer}
|
||||
borderless
|
||||
onPress={() => setTrackSearchDialog(true)}
|
||||
rippleColor={theme.colorAccent}
|
||||
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
borderRadius: 4,
|
||||
overflow: "hidden",
|
||||
marginHorizontal: 16,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
flex: 1,
|
||||
textAlignVertical: "center",
|
||||
color: theme.colorAccent,
|
||||
}}
|
||||
<Pressable
|
||||
style={styles.rippleContainer}
|
||||
android_ripple={{ color: theme.rippleColor, borderless: true }}
|
||||
onPress={() => setTrackSearchDialog(true)}
|
||||
>
|
||||
Add Tracking
|
||||
</Text>
|
||||
</TouchableRipple>
|
||||
<Text
|
||||
style={{
|
||||
textAlignVertical: "center",
|
||||
color: theme.colorAccent,
|
||||
}}
|
||||
>
|
||||
Add Tracking
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -59,7 +63,7 @@ export const MalTrackItemCard = ({
|
||||
>
|
||||
<Image
|
||||
source={require("../../../../../assets/mal.png")}
|
||||
style={[styles.trackerIcon, { borderBottomLeftRadius: 0 }]}
|
||||
style={styles.trackerIcon}
|
||||
/>
|
||||
<View style={styles.listItemContainer}>
|
||||
<Text
|
||||
@@ -145,21 +149,17 @@ const styles = StyleSheet.create({
|
||||
trackerIcon: {
|
||||
height: 50,
|
||||
width: 50,
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
borderRadius: 8,
|
||||
},
|
||||
rippleContainer: {
|
||||
flex: 1,
|
||||
paddingVertical: 8,
|
||||
alignItems: "center",
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
},
|
||||
addCardContainer: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
margin: 8,
|
||||
borderRadius: 4,
|
||||
elevation: 2,
|
||||
margin: 16,
|
||||
},
|
||||
cardContainer: {
|
||||
margin: 8,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { searchNovels } from "../../../../services/Trackers/myAnimeList";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import { trackNovel } from "../../../../redux/tracker/tracker.actions";
|
||||
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
|
||||
const TrackSearchDialog = ({
|
||||
trackSearchDialog,
|
||||
@@ -48,6 +49,19 @@ const TrackSearchDialog = ({
|
||||
borderless
|
||||
>
|
||||
<>
|
||||
{selectedNovel && selectedNovel.id === item.node.id && (
|
||||
<MaterialCommunityIcons
|
||||
name="check-circle"
|
||||
color={theme.colorAccent}
|
||||
size={24}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
right: 8,
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Image
|
||||
source={{ uri: item.node.main_picture.large }}
|
||||
style={{ height: 150, width: 100, borderRadius: 4 }}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import React from "react";
|
||||
import { View, Text, Image, StyleSheet } from "react-native";
|
||||
import { View, Text, StyleSheet, Pressable } from "react-native";
|
||||
import FastImage from "react-native-fast-image";
|
||||
import { TouchableRipple } from "react-native-paper";
|
||||
|
||||
const UpdateCard = ({ item, theme, onPress }) => {
|
||||
return (
|
||||
<TouchableRipple
|
||||
<Pressable
|
||||
style={styles.updateCard}
|
||||
onPress={onPress}
|
||||
rippleColor={theme.rippleColor}
|
||||
borderless
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
>
|
||||
<>
|
||||
<FastImage
|
||||
@@ -41,7 +39,7 @@ const UpdateCard = ({ item, theme, onPress }) => {
|
||||
</Text>
|
||||
</View>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ const revalidateMalToken = async (refreshToken) => {
|
||||
refresh_token: refreshToken,
|
||||
};
|
||||
|
||||
const res = await fetch(url, {
|
||||
const res = await fetch(myAnimeListConfig.tokenUrl, {
|
||||
headers,
|
||||
method: "POST",
|
||||
body: qs.stringify(body),
|
||||
|
||||
@@ -175,7 +175,9 @@ export const updateAllNovels = async () => {
|
||||
}
|
||||
});
|
||||
|
||||
await BackgroundService.start(veryIntensiveTask, options);
|
||||
if (libraryNovels.length > 0) {
|
||||
await BackgroundService.start(veryIntensiveTask, options);
|
||||
}
|
||||
};
|
||||
|
||||
export const parseChapterNumber = (chapterName) => {
|
||||
|
||||
@@ -6,6 +6,7 @@ export const fonts = [
|
||||
{ fontFamily: "noto-sans", name: "Noto Sans" },
|
||||
{ fontFamily: "open-sans", name: "Open Sans" },
|
||||
{ fontFamily: "pt-serif", name: "PT Serif" },
|
||||
{ fontFamily: "productsans-regular", name: "Product Sans" },
|
||||
];
|
||||
|
||||
export const dateFormat = {
|
||||
|
||||
@@ -95,7 +95,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.summary = $(".description-summary > div.summary__content")
|
||||
.text()
|
||||
.replace(/[\t\n]/g, "");
|
||||
.replace(/[\t\n]|Description|Summary/g, "");
|
||||
|
||||
let novelChapters = [];
|
||||
|
||||
|
||||
@@ -72,7 +72,10 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.novelCover = $("#thumbnail > img").attr("src");
|
||||
|
||||
novel.summary = $("div#Description").text();
|
||||
novel.summary = $("div#Description")
|
||||
.text()
|
||||
.replace("Description", "")
|
||||
.trim();
|
||||
|
||||
novel.genre = $("div#Genre")
|
||||
.text()
|
||||
|
||||
@@ -73,7 +73,16 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.artist = null;
|
||||
|
||||
novel.Status = null;
|
||||
novel.status = null;
|
||||
|
||||
novel.status = $("li")
|
||||
.filter(function () {
|
||||
return $(this).find("label").text().trim() === "Status:";
|
||||
})
|
||||
.text()
|
||||
.includes("Completed")
|
||||
? "Completed"
|
||||
: "Ongoing";
|
||||
|
||||
let novelChapters = [];
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
case "Author":
|
||||
novel.author = detail.trim();
|
||||
break;
|
||||
case "Artist":
|
||||
case "Novel":
|
||||
novel.status = detail.trim();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ const popularNovels = async (page) => {
|
||||
};
|
||||
|
||||
const parseNovelAndChapters = async (novelUrl) => {
|
||||
const url = `${baseUrl}${novelUrl}.html`;
|
||||
const url = `${baseUrl}${novelUrl.replace("/", "")}.html`;
|
||||
|
||||
console.log(url);
|
||||
|
||||
@@ -78,12 +78,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
.text()
|
||||
.replace(/[\t\n]/g, "");
|
||||
|
||||
// novel.Alternative = $("[title='Alternative names']")
|
||||
// .next()
|
||||
// .text()
|
||||
// .replace(/[\t\n]/g, "");
|
||||
|
||||
let novelSummary = $(".inner").text();
|
||||
let novelSummary = $(".inner").text().trim();
|
||||
novel.summary = novelSummary;
|
||||
|
||||
let novelChapters = [];
|
||||
@@ -92,8 +87,6 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
$("h3.tit").each(function (res) {
|
||||
if ($(this).find("a").text() === novel.novelName) {
|
||||
// console.log($(this).find("a").text());
|
||||
// console.log(novel.novelName);
|
||||
latestChapter = $(this).next().find("span.s3").text().match(/\d+/);
|
||||
}
|
||||
});
|
||||
@@ -118,7 +111,12 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
};
|
||||
|
||||
const parseChapter = async (novelUrl, chapterUrl) => {
|
||||
const url = `${baseUrl}${novelUrl}/${chapterUrl}.html`;
|
||||
let novelId = novelUrl.replace("/", "");
|
||||
|
||||
const url = `${baseUrl}${novelId}/${chapterUrl}.html`;
|
||||
console.log(novelUrl, chapterUrl);
|
||||
|
||||
console.log(url);
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
@@ -130,6 +128,8 @@ const parseChapter = async (novelUrl, chapterUrl) => {
|
||||
let chapterText = $("div.txt").html();
|
||||
chapterText = htmlToText(chapterText);
|
||||
|
||||
console.log(chapterText);
|
||||
|
||||
let nextChapter = null;
|
||||
|
||||
if ($("[title='Read Next chapter']").length) {
|
||||
|
||||
+13
-3
@@ -74,10 +74,20 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
novel.genre += $(this).text();
|
||||
});
|
||||
|
||||
novel.author = $(".book-sidebar__author > .book-sidebar__info")
|
||||
function capitalizeFirstLetter(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
const info = $(".book-sidebar__author > .book-sidebar__info")
|
||||
.text()
|
||||
.replace(/[\t\n]/g, "")
|
||||
.trim();
|
||||
.trim()
|
||||
.split("・");
|
||||
|
||||
if (info) {
|
||||
novel.author = info[0];
|
||||
|
||||
novel.status = capitalizeFirstLetter(info[1]);
|
||||
}
|
||||
|
||||
let novelChapters = [];
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
$("div.header-stats > span").each(function (result) {
|
||||
if ($(this).find("small").text() === "Status") {
|
||||
novel.Status = $(this).find("strong").text();
|
||||
novel.status = $(this).find("strong").text();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -113,22 +113,18 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
let novelChapters = [];
|
||||
|
||||
let firstChapterNo, totalChapters;
|
||||
|
||||
totalChapters = $(".header-stats > span").first().text().match(/\d+/)[0];
|
||||
firstChapterNo = $(".chapter-no").first().text().match(/\d+/)[0];
|
||||
|
||||
for (let i = firstChapterNo; i <= totalChapters; i++) {
|
||||
const chapterName = "Chapter " + i;
|
||||
|
||||
const releaseDate = null;
|
||||
|
||||
const chapterUrl = "chapter-" + i;
|
||||
$("ul.chapter-list > li").each(function (result) {
|
||||
const chapterName = $(this).find(".chapter-title").text().trim();
|
||||
const releaseDate = $(this).find("time").text().trim();
|
||||
const chapterUrl = $(this)
|
||||
.find("a")
|
||||
.attr("href")
|
||||
.replace(`/novel/${novelUrl}`, "");
|
||||
|
||||
const chapter = { chapterName, releaseDate, chapterUrl };
|
||||
|
||||
novelChapters.push(chapter);
|
||||
}
|
||||
});
|
||||
|
||||
novel.chapters = novelChapters;
|
||||
|
||||
|
||||
@@ -7,7 +7,16 @@ const popularNovels = async (page) => {
|
||||
let totalPages = 10;
|
||||
const url = `${baseUrl}/alltime-rank/page/${page}`;
|
||||
|
||||
const result = await fetch(url);
|
||||
let headers = new Headers({
|
||||
referer: "https://www.mtlnovel.com/",
|
||||
"User-Agent":
|
||||
"'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
|
||||
});
|
||||
|
||||
const result = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
});
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
@@ -37,7 +46,16 @@ const popularNovels = async (page) => {
|
||||
const parseNovelAndChapters = async (novelUrl) => {
|
||||
const url = `${baseUrl}/${novelUrl}`;
|
||||
|
||||
const result = await fetch(url);
|
||||
let headers = new Headers({
|
||||
referer: "https://www.mtlnovel.com/alltime-rank/",
|
||||
"User-Agent":
|
||||
"'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
|
||||
});
|
||||
|
||||
const result = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
});
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
@@ -66,7 +84,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
.text()
|
||||
.replace("Auhtor:", "");
|
||||
|
||||
novel.Status = $("tr > td")
|
||||
novel.status = $("tr > td")
|
||||
.filter(function () {
|
||||
return $(this).prev().text().trim() === "Status";
|
||||
})
|
||||
|
||||
@@ -63,7 +63,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.novelCover = baseUrl + $("div.book > img").attr("src");
|
||||
|
||||
novel.summary = $("div.desc-text").text();
|
||||
novel.summary = $("div.desc-text").text().trim();
|
||||
|
||||
novel.author = $("div.info > div > h3")
|
||||
.filter(function () {
|
||||
@@ -81,7 +81,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.artist = null;
|
||||
|
||||
novel.Status = $("div.info > div > h3")
|
||||
novel.status = $("div.info > div > h3")
|
||||
.filter(function () {
|
||||
return $(this).text().trim() === "Status:";
|
||||
})
|
||||
|
||||
@@ -65,7 +65,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.artist = null;
|
||||
|
||||
novel.Status = $("span.blue").first().next().text().replace("Status:", "");
|
||||
novel.status = $("span.blue").first().next().text().replace("Status:", "");
|
||||
|
||||
let novelChapters = [];
|
||||
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
import cheerio from "react-native-cheerio";
|
||||
import { htmlToText } from "../helpers/htmlToText";
|
||||
import moment from "moment";
|
||||
|
||||
const baseUrl = "https://www.novelpassion.com/";
|
||||
|
||||
const popularNovels = async (page) => {
|
||||
const totalPages = 42;
|
||||
let url = baseUrl + "category/all?p=" + page + "&s=1&f=4";
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
|
||||
let novels = [];
|
||||
|
||||
$("div.j_bookList.lis-mn.ddc")
|
||||
.find("li")
|
||||
.each(function (result) {
|
||||
const novelName = $(this).find("h3").text();
|
||||
const novelCover =
|
||||
"https://www.novelpassion.com" +
|
||||
$(this).find("img").attr("src");
|
||||
|
||||
let novelUrl = $(this).find("div.pr > a").attr("href");
|
||||
novelUrl = novelUrl.replace("/novel/", "");
|
||||
|
||||
const novel = {
|
||||
sourceId: 33,
|
||||
novelName,
|
||||
novelCover,
|
||||
novelUrl,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return { totalPages, novels };
|
||||
};
|
||||
|
||||
const parseNovelAndChapters = async (novelUrl) => {
|
||||
const url = baseUrl + "novel/" + novelUrl;
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
|
||||
let novel = {};
|
||||
|
||||
novel.sourceId = 33;
|
||||
|
||||
novel.sourceName = "NovelPassion";
|
||||
|
||||
novel.url = url;
|
||||
|
||||
novel.novelUrl = novelUrl;
|
||||
|
||||
novelName = $("h2.mh").text();
|
||||
novel.novelName = novelName;
|
||||
|
||||
novel.novelCover = baseUrl + $("i.g_thumb > img").attr("src");
|
||||
|
||||
let novelSummary = $("div.j_synopsis").text().trim();
|
||||
novel.summary = novelSummary;
|
||||
|
||||
$("address.lh20.m14.pr").each(function (result) {
|
||||
detailName = $(this).find("p.ell.dib.vam").text().trim();
|
||||
|
||||
detail = $(this).find("div.dns").text().trim();
|
||||
|
||||
switch (detailName) {
|
||||
case "Categories:":
|
||||
novel.genre = detail.trim().replace(/ - /g, ",");
|
||||
break;
|
||||
case "Author:":
|
||||
novel.author = detail.trim().replace(/ - /g, ", ");
|
||||
break;
|
||||
}
|
||||
|
||||
if (detailName.includes("Status:")) {
|
||||
novel.status = detailName.replace("Status:", "").trim();
|
||||
}
|
||||
});
|
||||
|
||||
let novelChapters = [];
|
||||
|
||||
$(".content-list")
|
||||
.find("li")
|
||||
.each(function (result) {
|
||||
const chapterName = $(this).find("span.sp1").text();
|
||||
|
||||
let releaseDate = $(this).find("i.sp2").text();
|
||||
releaseDate = moment(new Date(releaseDate)).format("MM/DD/YY");
|
||||
|
||||
const chapterUrl = $(this)
|
||||
.find("a")
|
||||
.attr("href")
|
||||
.replace("/novel/" + novelUrl + "/", "");
|
||||
|
||||
novelChapters.push({
|
||||
chapterName,
|
||||
releaseDate,
|
||||
chapterUrl,
|
||||
});
|
||||
});
|
||||
|
||||
novel.chapters = novelChapters.reverse();
|
||||
|
||||
return novel;
|
||||
};
|
||||
|
||||
const parseChapter = async (novelUrl, chapterUrl) => {
|
||||
const url = `${baseUrl}novel/${novelUrl}/${chapterUrl}`;
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
|
||||
let chapterName = $("div.cha-content").find("strong").text();
|
||||
|
||||
let chapterText = $("div.cha-content").html();
|
||||
chapterText = htmlToText(chapterText);
|
||||
|
||||
let nextChapter = null;
|
||||
let prevChapter = null;
|
||||
|
||||
const chapter = {
|
||||
sourceId: 33,
|
||||
novelUrl,
|
||||
chapterUrl,
|
||||
chapterName,
|
||||
chapterText,
|
||||
nextChapter,
|
||||
prevChapter,
|
||||
};
|
||||
|
||||
return chapter;
|
||||
};
|
||||
|
||||
const searchNovels = async (searchTerm) => {
|
||||
const url = baseUrl + "search?keyword=" + searchTerm;
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
|
||||
let novels = [];
|
||||
|
||||
$("div.j_bookList.lis-mn.ddc")
|
||||
.find("li")
|
||||
.each(function (result) {
|
||||
const novelName = $(this).find("h3").text();
|
||||
const novelCover =
|
||||
"https://www.novelpassion.com" +
|
||||
$(this).find("img").attr("src");
|
||||
|
||||
let novelUrl = $(this).find("div.pr > a").attr("href");
|
||||
novelUrl = novelUrl.replace("/novel/", "");
|
||||
|
||||
const novel = {
|
||||
sourceId: 33,
|
||||
novelName,
|
||||
novelCover,
|
||||
novelUrl,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
const NovelPassionScraper = {
|
||||
popularNovels,
|
||||
parseNovelAndChapters,
|
||||
parseChapter,
|
||||
searchNovels,
|
||||
};
|
||||
|
||||
export default NovelPassionScraper;
|
||||
@@ -56,7 +56,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.genre = $("div.book-catalog > span.txt").text();
|
||||
|
||||
novel.Status = $("div.book-state > span.txt").text();
|
||||
novel.status = $("div.book-state > span.txt").text();
|
||||
|
||||
novel.author = $("div.author > span.name").text();
|
||||
|
||||
|
||||
@@ -61,17 +61,17 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
$(".novel-detail-item").each(function (result) {
|
||||
const detailName = $(this).find(".novel-detail-header > h6").text();
|
||||
const detail = $(this).find(".novel-detail-body").text();
|
||||
const detail = $(this).find(".novel-detail-body").text().trim();
|
||||
|
||||
switch (detailName) {
|
||||
case "Genre":
|
||||
genre = detail.trim().replace(/[\t\n]/g, ",");
|
||||
genre = detail.replace(/[\t\n]/g, ",");
|
||||
break;
|
||||
case "Author(s)":
|
||||
author = detail.trim();
|
||||
author = detail;
|
||||
break;
|
||||
case "Artist(s)":
|
||||
artist = detail.trim();
|
||||
artist = detail;
|
||||
break;
|
||||
case "Description":
|
||||
summary = detail;
|
||||
|
||||
@@ -56,7 +56,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.genre = $("div.book-catalog > span.txt").text();
|
||||
|
||||
novel.Status = $("div.book-state > span.txt").text();
|
||||
novel.status = $("div.book-state > span.txt").text();
|
||||
|
||||
novel.author = $("div.author > span.name").text();
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.novelCover = $("div.book > img").attr("src");
|
||||
|
||||
novel.summary = $("div.desc-text").text();
|
||||
novel.summary = $("div.desc-text").text().trim();
|
||||
|
||||
novel.author = $("li > h3")
|
||||
.filter(function () {
|
||||
@@ -79,7 +79,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.artist = null;
|
||||
|
||||
novel.Status = $("li > h3")
|
||||
novel.status = $("li > h3")
|
||||
.filter(function () {
|
||||
return $(this).text().trim() === "Status:";
|
||||
})
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import cheerio from "react-native-cheerio";
|
||||
import { htmlToText } from "../helpers/htmlToText";
|
||||
import moment from "moment";
|
||||
|
||||
const baseUrl = "https://www.royalroad.com/";
|
||||
|
||||
const popularNovels = async (page) => {
|
||||
const totalPages = 1846;
|
||||
let url = baseUrl + "fictions/best-rated?page=" + page;
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
|
||||
let novels = [];
|
||||
|
||||
$("div.fiction-list-item.row").each(function (result) {
|
||||
const novelName = $(this).find("h2.fiction-title").text().trim();
|
||||
const novelCover = $(this).find("img").attr("src");
|
||||
|
||||
let novelUrl = $(this)
|
||||
.find("h2.fiction-title > a")
|
||||
.attr("href")
|
||||
.replace("/fiction/", "");
|
||||
|
||||
const novel = {
|
||||
sourceId: 34,
|
||||
novelName,
|
||||
novelCover,
|
||||
novelUrl,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return { totalPages, novels };
|
||||
};
|
||||
|
||||
const parseNovelAndChapters = async (novelUrl) => {
|
||||
const url = baseUrl + "fiction/" + novelUrl;
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
|
||||
let novel = {};
|
||||
|
||||
novel.sourceId = 34;
|
||||
|
||||
novel.sourceName = "Royal Road";
|
||||
|
||||
novel.url = url;
|
||||
|
||||
novel.novelUrl = novelUrl;
|
||||
|
||||
novelName = $("h1").text();
|
||||
novel.novelName = novelName;
|
||||
|
||||
novel.novelCover = $("img.thumbnail").attr("src");
|
||||
|
||||
novel.summary = $("div.description").text().trim();
|
||||
|
||||
novel.author = $("h1").next().text().replace("by ", "").trim();
|
||||
novel.genre = $("span.tags").text().trim().replace(/\n\s+/g, ",");
|
||||
novel.status = $(
|
||||
"div.fiction-info > div.portlet > .col-md-8 > .margin-bottom-10 > span"
|
||||
)
|
||||
.first()
|
||||
.next()
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
let novelChapters = [];
|
||||
|
||||
$("table#chapters > tbody")
|
||||
.find("tr")
|
||||
.each(function (result) {
|
||||
const chapterName = $(this).find("td").first().text().trim();
|
||||
const releaseDate = $(this).find("td").first().next().text().trim();
|
||||
|
||||
const chapterUrl = $(this)
|
||||
.find("td")
|
||||
.first()
|
||||
.find("a")
|
||||
.attr("href")
|
||||
.replace("/fiction/" + novelUrl + "/chapter/", "");
|
||||
|
||||
novelChapters.push({
|
||||
chapterName,
|
||||
releaseDate,
|
||||
chapterUrl,
|
||||
});
|
||||
});
|
||||
|
||||
novel.chapters = novelChapters;
|
||||
|
||||
return novel;
|
||||
};
|
||||
|
||||
const parseChapter = async (novelUrl, chapterUrl) => {
|
||||
const url = `${baseUrl}fiction/${novelUrl}/chapter/${chapterUrl}`;
|
||||
|
||||
console.log(url);
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
|
||||
let chapterName = $("div.chapter-content").find("strong").text();
|
||||
|
||||
let chapterText = $("div.chapter-content").html();
|
||||
chapterText = htmlToText(chapterText);
|
||||
|
||||
let nextChapter = null;
|
||||
let prevChapter = null;
|
||||
|
||||
const chapter = {
|
||||
sourceId: 34,
|
||||
novelUrl,
|
||||
chapterUrl,
|
||||
chapterName,
|
||||
chapterText,
|
||||
nextChapter,
|
||||
prevChapter,
|
||||
};
|
||||
|
||||
return chapter;
|
||||
};
|
||||
|
||||
const searchNovels = async (searchTerm) => {
|
||||
const url = baseUrl + "fictions/search?title=" + searchTerm;
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
|
||||
let novels = [];
|
||||
|
||||
$("div.fiction-list-item.row").each(function (result) {
|
||||
const novelName = $(this).find("h2.fiction-title").text().trim();
|
||||
let novelCover = $(this).find("img").attr("src");
|
||||
|
||||
if (novelCover.includes("Content")) {
|
||||
novelCover = baseUrl + novelCover;
|
||||
}
|
||||
|
||||
let novelUrl = $(this)
|
||||
.find("h2.fiction-title > a")
|
||||
.attr("href")
|
||||
.replace("/fiction/", "");
|
||||
|
||||
const novel = {
|
||||
sourceId: 34,
|
||||
novelName,
|
||||
novelCover,
|
||||
novelUrl,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
const RoyalRoadScraper = {
|
||||
popularNovels,
|
||||
parseNovelAndChapters,
|
||||
parseChapter,
|
||||
searchNovels,
|
||||
};
|
||||
|
||||
export default RoyalRoadScraper;
|
||||
@@ -0,0 +1,184 @@
|
||||
import cheerio from "react-native-cheerio";
|
||||
import { htmlToText } from "../helpers/htmlToText";
|
||||
|
||||
const baseUrl = "https://www.scribblehub.com/";
|
||||
|
||||
const popularNovels = async (page) => {
|
||||
const totalPages = 326;
|
||||
let url = baseUrl + "series-ranking/?sort=1&order=4&pg=" + page;
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
|
||||
let novels = [];
|
||||
|
||||
$("div.search_main_box").each(function (result) {
|
||||
const novelName = $(this).find("div.search_title > a").text();
|
||||
const novelCover = $(this).find("div.search_img > img").attr("src");
|
||||
|
||||
let novelUrl = $(this).find("div.search_title > a").attr("href");
|
||||
novelUrl = novelUrl.split("/");
|
||||
novelUrl = novelUrl[4] + "-" + novelUrl[5];
|
||||
|
||||
const novel = {
|
||||
sourceId: 35,
|
||||
novelName,
|
||||
novelCover,
|
||||
novelUrl,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return { totalPages, novels };
|
||||
};
|
||||
|
||||
const parseNovelAndChapters = async (novelUrl) => {
|
||||
const url = baseUrl + "read/" + novelUrl;
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
|
||||
let novel = {};
|
||||
|
||||
novel.sourceId = 35;
|
||||
|
||||
novel.sourceName = "Scribble Hub";
|
||||
|
||||
novel.url = url;
|
||||
|
||||
novel.novelUrl = novelUrl;
|
||||
|
||||
novel.novelName = $("div.fic_title").text();
|
||||
|
||||
novel.novelCover = $("div.fic_image > img").attr("src");
|
||||
|
||||
novel.summary = $("div.wi_fic_desc").text();
|
||||
|
||||
novel.genre = "";
|
||||
$("span.wi_fic_genre")
|
||||
.find("span")
|
||||
.each(function (res) {
|
||||
novel.genre += $(this).text() + ",";
|
||||
});
|
||||
if (novel.genre) {
|
||||
novel.genre = novel.genre.slice(0, -1);
|
||||
}
|
||||
|
||||
novel.status = $("span.rnd_stats").next().text().includes("Ongoing")
|
||||
? "Ongoing"
|
||||
: "Completed";
|
||||
|
||||
novel.author = $("span.auth_name_fic").text();
|
||||
|
||||
let formData = new FormData();
|
||||
formData.append("action", "wi_getreleases_pagination");
|
||||
formData.append("pagenum", "-1");
|
||||
formData.append("mypostid", novelUrl.split("-")[0]);
|
||||
|
||||
const data = await fetch(
|
||||
"https://www.scribblehub.com/wp-admin/admin-ajax.php",
|
||||
{
|
||||
method: "POST",
|
||||
body: formData,
|
||||
}
|
||||
);
|
||||
const text = await data.text();
|
||||
|
||||
$ = cheerio.load(text);
|
||||
|
||||
let novelChapters = [];
|
||||
|
||||
$(".toc_w").each(function (result) {
|
||||
const chapterName = $(this).find(".toc_a").text();
|
||||
const releaseDate = $(this).find(".fic_date_pub").text();
|
||||
|
||||
const chapterUrl = $(this).find("a").attr("href").split("/")[6];
|
||||
// .replace("/novel/" + novelUrl + "/", "");
|
||||
|
||||
novelChapters.push({
|
||||
chapterName,
|
||||
releaseDate,
|
||||
chapterUrl,
|
||||
});
|
||||
});
|
||||
|
||||
novel.chapters = novelChapters.reverse();
|
||||
|
||||
return novel;
|
||||
};
|
||||
|
||||
const parseChapter = async (novelUrl, chapterUrl) => {
|
||||
const url = `${baseUrl}read/${novelUrl}/chapter/${chapterUrl}`;
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
|
||||
let chapterName = $("div.chapter-title").text();
|
||||
|
||||
let chapterText = $("div.chp_raw").html();
|
||||
chapterText = htmlToText(chapterText);
|
||||
|
||||
let nextChapter = null;
|
||||
let prevChapter = null;
|
||||
|
||||
const chapter = {
|
||||
sourceId: 35,
|
||||
novelUrl,
|
||||
chapterUrl,
|
||||
chapterName,
|
||||
chapterText,
|
||||
nextChapter,
|
||||
prevChapter,
|
||||
};
|
||||
|
||||
return chapter;
|
||||
};
|
||||
|
||||
const searchNovels = async (searchTerm) => {
|
||||
const url =
|
||||
"https://www.scribblehub.com/?s=" +
|
||||
searchTerm +
|
||||
"&post_type=fictionposts";
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
|
||||
let novels = [];
|
||||
|
||||
$("div.search_main_box").each(function (result) {
|
||||
const novelName = $(this).find("div.search_title > a").text();
|
||||
const novelCover = $(this).find("div.search_img > img").attr("src");
|
||||
|
||||
let novelUrl = $(this).find("div.search_title > a").attr("href");
|
||||
novelUrl = novelUrl.split("/");
|
||||
novelUrl = novelUrl[4] + "-" + novelUrl[5];
|
||||
|
||||
const novel = {
|
||||
sourceId: 35,
|
||||
novelName,
|
||||
novelCover,
|
||||
novelUrl,
|
||||
};
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
return novels;
|
||||
};
|
||||
|
||||
const ScribbleHubScraper = {
|
||||
popularNovels,
|
||||
parseNovelAndChapters,
|
||||
parseChapter,
|
||||
searchNovels,
|
||||
};
|
||||
|
||||
export default ScribbleHubScraper;
|
||||
@@ -56,7 +56,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.genre = $("div.book-catalog > span.txt").text();
|
||||
|
||||
novel.Status = $("div.book-state > span.txt").text();
|
||||
novel.status = $("div.book-state > span.txt").text();
|
||||
|
||||
novel.author = $("div.author > span.name").text();
|
||||
|
||||
|
||||
@@ -86,7 +86,11 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
}
|
||||
});
|
||||
|
||||
let novelSummary = $(".description-summary > div.summary__content").text();
|
||||
$(".summary__content > p").first().remove();
|
||||
|
||||
let novelSummary = $(".description-summary > div.summary__content")
|
||||
.text()
|
||||
.replace("Synopsis", "");
|
||||
|
||||
novel.summary = novelSummary.replace(/[\t\n]/g, "");
|
||||
|
||||
|
||||
@@ -51,7 +51,13 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.novelCover = $("img.img-thumbnail").attr("src");
|
||||
|
||||
novel.summary = $(".fr-view > p").text();
|
||||
novel.summary = $("h3")
|
||||
.filter(function () {
|
||||
return $(this).text().trim() === "Synopsis";
|
||||
})
|
||||
.next()
|
||||
.text()
|
||||
.trim();
|
||||
|
||||
novel.author = $("div > dt")
|
||||
.filter(function () {
|
||||
@@ -72,7 +78,11 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.artist = null;
|
||||
|
||||
novel.Status = null;
|
||||
novel.status = null;
|
||||
|
||||
novel.status = $("div.fr-view.pt-10").text().includes("Complete")
|
||||
? "Completed"
|
||||
: "Ongoing";
|
||||
|
||||
let novelChapters = [];
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.novelCover = $("div.book > img").attr("src");
|
||||
|
||||
novel.summary = $("div.desc-text").text();
|
||||
novel.summary = $("div.desc-text").text().trim();
|
||||
|
||||
novel.author = $("div.info > div > h3")
|
||||
.filter(function () {
|
||||
@@ -79,12 +79,13 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.artist = null;
|
||||
|
||||
novel.Status = $("li > h3")
|
||||
novel.status = $("div.info > div")
|
||||
.filter(function () {
|
||||
return $(this).text().trim() === "Status:";
|
||||
return $(this).find("h3").text().trim() === "Status:";
|
||||
})
|
||||
.siblings()
|
||||
.text();
|
||||
.text()
|
||||
.replace("Status:", "")
|
||||
.replace(/\s/g, "");
|
||||
|
||||
const novelId = $("#rating").attr("data-novel-id");
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.genre = $("div.book-catalog > span.txt").text();
|
||||
|
||||
novel.Status = $("div.book-state > span.txt").text();
|
||||
novel.status = $("div.book-state > span.txt").text();
|
||||
|
||||
novel.author = $("div.author > span.name").text();
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.summary = $(".story-introduction-content").text();
|
||||
|
||||
novel.author = $(".content-reading > p").text();
|
||||
novel.author = $(".content-reading > p").text().trim();
|
||||
|
||||
novel.artist = null;
|
||||
|
||||
@@ -83,7 +83,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
novel.genre = novel.genre.split(",");
|
||||
novel.genre.pop();
|
||||
|
||||
novel.Status = novel.genre.pop();
|
||||
novel.status = novel.genre.pop();
|
||||
|
||||
novel.genre = novel.genre.join(",");
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
novel.author = $(this).text().replace("Autor:", "").trim();
|
||||
}
|
||||
if ($(this).text().includes("Estado:")) {
|
||||
novel.Status = $(this).text().replace("Estado: ", "").trim();
|
||||
novel.status = $(this).text().replace("Estado: ", "").trim();
|
||||
}
|
||||
|
||||
if ($(this).text().includes("Género:")) {
|
||||
|
||||
@@ -76,7 +76,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
novel.novelCover = $("div.separator").find("a").attr("href");
|
||||
|
||||
novel.artist = "";
|
||||
novel.Status = "";
|
||||
novel.status = "";
|
||||
|
||||
$("div > b").each(function (result) {
|
||||
const detailName = $(this).text();
|
||||
@@ -90,7 +90,7 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
}
|
||||
|
||||
if (detailName.includes("Estatus")) {
|
||||
novel.Status = detail.replace("Estatus: ", "");
|
||||
novel.status = detail.replace("Estatus: ", "");
|
||||
}
|
||||
if (detailName.includes("Géneros:")) {
|
||||
novel.genre = detail
|
||||
@@ -104,13 +104,16 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
$("div").each(function (result) {
|
||||
const detailName = $(this).text();
|
||||
|
||||
if (detailName.includes("Sinopsis")) {
|
||||
novel.summary = $(this)
|
||||
.next()
|
||||
.text()
|
||||
.replace("Sinopsis", "")
|
||||
.trim();
|
||||
novel.summary =
|
||||
$(this).next().text() !== ""
|
||||
? $(this).next().text().replace("Sinopsis", "").trim()
|
||||
: $(this)
|
||||
.next()
|
||||
.next()
|
||||
.text()
|
||||
.replace("Sinopsis", "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
if (detailName.includes("Lista de Capítulos")) {
|
||||
@@ -149,12 +152,8 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
};
|
||||
|
||||
const parseChapter = async (novelUrl, chapterUrl) => {
|
||||
let year = req.params.year;
|
||||
let month = req.params.month;
|
||||
const url = `${baseUrl}${year}/${month}/${chapterUrl}`;
|
||||
|
||||
console.log(url);
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ const parseNovelAndChapters = async (novUrl) => {
|
||||
novel.genre = genres.join(",");
|
||||
novel.author = item.nvl_writer;
|
||||
novel.summary = item.nvl_content;
|
||||
novel.Status = item.nvl_status;
|
||||
novel.status = item.nvl_status;
|
||||
|
||||
let novelChapters = [];
|
||||
|
||||
|
||||
@@ -76,8 +76,8 @@ const parseNovelAndChapters = async (novelUrl) => {
|
||||
|
||||
novel.genre = novel.Generos.replace(/, /g, ",");
|
||||
novel.author = novel.Autores;
|
||||
novel.Status = novel.Estado;
|
||||
novel.Status = null;
|
||||
novel.status = novel.Estado;
|
||||
novel.status = null;
|
||||
|
||||
novel.artist = novel["Artista(s)"];
|
||||
|
||||
|
||||
@@ -16,9 +16,6 @@ const htmlToText = (html) => {
|
||||
// Remove content in comments.
|
||||
.replace(/<!--.*?-->/gim, "")
|
||||
// Format anchor tags properly.
|
||||
// e.g.
|
||||
// input - <a class='ahref' href='http://pinetechlabs.com/' title='asdfqwer\"><b>asdf</b></a>
|
||||
// output - asdf (http://pinetechlabs.com/)
|
||||
.replace(
|
||||
/<\s*a[^>]*href=['"](.*?)['"][^>]*>([\s\S]*?)<\/\s*a\s*>/gi,
|
||||
"$2"
|
||||
@@ -41,29 +38,18 @@ const htmlToText = (html) => {
|
||||
};
|
||||
|
||||
const decodeHtmlEntity = (m, n) => {
|
||||
// Determine the character code of the entity. Range is 0 to 65535
|
||||
// (characters in JavaScript are Unicode, and entities can represent
|
||||
// Unicode characters).
|
||||
let code;
|
||||
|
||||
// Try to parse as numeric entity. This is done before named entities for
|
||||
// speed because associative array lookup in many JavaScript implementations
|
||||
// is a linear search.
|
||||
if (n.substr(0, 1) == "#") {
|
||||
// Try to parse as numeric entity
|
||||
if (n.substr(1, 1) == "x") {
|
||||
// Try to parse as hexadecimal
|
||||
code = parseInt(n.substr(2), 16);
|
||||
} else {
|
||||
// Try to parse as decimal
|
||||
code = parseInt(n.substr(1), 10);
|
||||
}
|
||||
} else {
|
||||
// Try to parse as named entity
|
||||
code = ENTITIES_MAP[n];
|
||||
}
|
||||
|
||||
// If still nothing, pass entity through
|
||||
return code === undefined || code === NaN
|
||||
? "&" + n + ";"
|
||||
: String.fromCharCode(code);
|
||||
|
||||
@@ -29,6 +29,9 @@ import YuukiTlsScraper from "./es/yuukitls";
|
||||
import NovelaWuxiaScraper from "./es/novelawuxia";
|
||||
import OasisTranslationsScraper from "./es/oasistranslations";
|
||||
import HasuTlScraper from "./es/hasutl";
|
||||
import NovelPassionScraper from "./en/novelpassion";
|
||||
import RoyalRoadScraper from "./en/royalroad";
|
||||
import ScribbleHubScraper from "./en/scribblehub";
|
||||
|
||||
export const sources = [
|
||||
/**
|
||||
@@ -196,6 +199,20 @@ export const sources = [
|
||||
icon: "https://github.com/rajarsheechatterjee/lnreader-extensions/blob/main/src/en/comrademao/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 33,
|
||||
url: "https://www.novelpassion.com/",
|
||||
sourceName: "NovelPassion",
|
||||
icon: "https://github.com/rajarsheechatterjee/lnreader-extensions/blob/main/src/en/novelpassion/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
{
|
||||
sourceId: 34,
|
||||
url: "https://www.royalroad.com/",
|
||||
sourceName: "Royal Road",
|
||||
icon: "https://github.com/rajarsheechatterjee/lnreader-extensions/blob/main/src/en/royalroad/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
|
||||
/**
|
||||
* Spanish
|
||||
@@ -257,6 +274,13 @@ export const sources = [
|
||||
icon: "https://github.com/rajarsheechatterjee/lnreader-extensions/blob/main/src/es/novelawuxia/icon.png?raw=true",
|
||||
lang: "Spanish",
|
||||
},
|
||||
{
|
||||
sourceId: 35,
|
||||
url: "https://www.scribblehub.com/",
|
||||
sourceName: "Scribble Hub",
|
||||
icon: "https://github.com/rajarsheechatterjee/lnreader-extensions/blob/main/src/en/scribblehub/icon.png?raw=true",
|
||||
lang: "English",
|
||||
},
|
||||
];
|
||||
|
||||
export const getSource = (sourceId) => {
|
||||
@@ -354,5 +378,14 @@ export const getSource = (sourceId) => {
|
||||
case 31: {
|
||||
return NovelaWuxiaScraper;
|
||||
}
|
||||
case 33: {
|
||||
return NovelPassionScraper;
|
||||
}
|
||||
case 34: {
|
||||
return RoyalRoadScraper;
|
||||
}
|
||||
case 35: {
|
||||
return ScribbleHubScraper;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -11,4 +11,5 @@ export const fonts = {
|
||||
"noto-sans-bold": require("../../assets/fonts/NotoSans-Bold.ttf"),
|
||||
"open-sans": require("../../assets/fonts/OpenSans-Regular.ttf"),
|
||||
"pt-serif": require("../../assets/fonts/PTSerif-Regular.ttf"),
|
||||
"productsans-regular": require("../../assets/fonts/productsans_regular.ttf"),
|
||||
};
|
||||
|
||||
+54
-8
@@ -23,7 +23,7 @@ export const lightTheme = {
|
||||
textColorPrimary: "#000000",
|
||||
textColorSecondary: "rgba(0,0,0,0.54)",
|
||||
textColorHint: "rgba(0,0,0,0.38)",
|
||||
rippleColor: "rgba(0,0,0,0.06)",
|
||||
rippleColor: "rgba(41,121,255,0.12)",
|
||||
searchBarColor: "#FFFFFF",
|
||||
menuColor: "#FFFFFF",
|
||||
dividerColor: "rgba(0,0,0,0.1)",
|
||||
@@ -45,7 +45,7 @@ export const darkTheme = {
|
||||
textColorPrimary: "#FFFFFF",
|
||||
textColorSecondary: "rgba(255,255,255,0.7)",
|
||||
textColorHint: "rgba(255,255,255,0.5)",
|
||||
rippleColor: "rgba(255,255,255,0.06)",
|
||||
rippleColor: "rgba(41,121,255,0.12)",
|
||||
searchBarColor: "#303135",
|
||||
menuColor: "#242529",
|
||||
filterColor: "#FFEB3B",
|
||||
@@ -67,7 +67,7 @@ export const amoledDarkTheme = {
|
||||
textColorPrimary: "#FFFFFF",
|
||||
textColorSecondary: "rgba(255,255,255,0.7)",
|
||||
textColorHint: "rgba(255,255,255,0.5)",
|
||||
rippleColor: "rgba(255,255,255,0.12)",
|
||||
rippleColor: "rgba(41,121,255,0.12)",
|
||||
searchBarColor: "#1F1F1F",
|
||||
menuColor: "#242529",
|
||||
filterColor: "#FFEB3B",
|
||||
@@ -89,7 +89,7 @@ export const midnightDuskTheme = {
|
||||
textColorPrimary: "#FFFFFF",
|
||||
textColorSecondary: "rgba(255,255,255,0.7)",
|
||||
textColorHint: "rgba(255,255,255,0.5)",
|
||||
rippleColor: "rgba(255,255,255,0.06)",
|
||||
rippleColor: "rgba(240,36,117,0.12)",
|
||||
searchBarColor: "#201F27",
|
||||
menuColor: "#201F27",
|
||||
filterColor: "#FFEB3B",
|
||||
@@ -105,14 +105,14 @@ export const midnightDuskTheme = {
|
||||
|
||||
export const limeTheme = {
|
||||
id: 4,
|
||||
name: "Lime",
|
||||
name: "Green Apple",
|
||||
colorPrimary: "#201F27",
|
||||
colorPrimaryDark: "#16151D",
|
||||
colorAccent: "#4AF88A",
|
||||
colorAccent: "#48E484",
|
||||
textColorPrimary: "#FFFFFF",
|
||||
textColorSecondary: "rgba(255,255,255,0.7)",
|
||||
textColorHint: "rgba(255,255,255,0.5)",
|
||||
rippleColor: "rgba(255,255,255,0.06)",
|
||||
rippleColor: "rgba(72,228,132,0.12)",
|
||||
searchBarColor: "#201F27",
|
||||
menuColor: "#201F27",
|
||||
filterColor: "#FFEB3B",
|
||||
@@ -122,6 +122,52 @@ export const limeTheme = {
|
||||
statusBar: "light",
|
||||
};
|
||||
|
||||
/**
|
||||
* Hot Pink Theme
|
||||
*/
|
||||
|
||||
export const hotPinkTheme = {
|
||||
id: 6,
|
||||
name: "AMOLED Hot Pink",
|
||||
colorPrimary: "#000000",
|
||||
colorPrimaryDark: "#000000",
|
||||
colorAccent: "#FF3399",
|
||||
textColorPrimary: "#FFFFFF",
|
||||
textColorSecondary: "rgba(255,255,255,0.7)",
|
||||
textColorHint: "rgba(255,255,255,0.5)",
|
||||
rippleColor: "rgba(255,51,153,0.12)",
|
||||
searchBarColor: "#1F1F1F",
|
||||
menuColor: "#242529",
|
||||
filterColor: "#FFEB3B",
|
||||
dividerColor: "rgba(255,255,255,0.15)",
|
||||
colorDisabled: "rgba(33,33,33,255)",
|
||||
colorButtonText: "#FFFFFF",
|
||||
statusBar: "light",
|
||||
};
|
||||
|
||||
/**
|
||||
* Strawberry Daiquiri
|
||||
*/
|
||||
|
||||
export const strawberryDaiquiri = {
|
||||
id: 7,
|
||||
name: "Strawberry Daiquiri",
|
||||
colorPrimary: "#FFFFFF",
|
||||
colorPrimaryDark: "#FAFAFA",
|
||||
colorAccent: "#ED4A65",
|
||||
textColorPrimary: "#000000",
|
||||
textColorSecondary: "rgba(0,0,0,0.54)",
|
||||
textColorHint: "rgba(0,0,0,0.38)",
|
||||
rippleColor: "rgba(237,74,101,0.12)",
|
||||
searchBarColor: "#FFFFFF",
|
||||
menuColor: "#FFFFFF",
|
||||
dividerColor: "rgba(0,0,0,0.1)",
|
||||
filterColor: "#FFC107",
|
||||
colorDisabled: "rgba(220,220,220,255)",
|
||||
colorButtonText: "#FFFFFF",
|
||||
statusBar: "dark",
|
||||
};
|
||||
|
||||
export const irisBlueTheme = {
|
||||
id: 5,
|
||||
name: "Iris Blue",
|
||||
@@ -131,7 +177,7 @@ export const irisBlueTheme = {
|
||||
textColorPrimary: "#FFFFFF",
|
||||
textColorSecondary: "rgba(255,255,255,0.7)",
|
||||
textColorHint: "rgba(255,255,255,0.5)",
|
||||
rippleColor: "rgba(255,255,255,0.06)",
|
||||
rippleColor: "rgba(0,173,181,0.12)",
|
||||
searchBarColor: "#393e46",
|
||||
menuColor: "#393e46",
|
||||
filterColor: "#FFEB3B",
|
||||
|
||||
Reference in New Issue
Block a user