diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..8856093b
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,24 @@
+name: react-native-android-build-apk
+on:
+ push:
+ branches:
+ - main
+jobs:
+ install-and-test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install npm dependencies
+ run: |
+ npm install
+ build-android:
+ needs: install-and-test
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install npm dependencies
+ run: |
+ npm install
+ - name: Build Android Release
+ run: |
+ cd android && ./gradlew assembleRelease
diff --git a/src/database/queries/NovelQueries.js b/src/database/queries/NovelQueries.js
index 15851cff..ea92a17f 100644
--- a/src/database/queries/NovelQueries.js
+++ b/src/database/queries/NovelQueries.js
@@ -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);
+ }
};
diff --git a/src/screens/Browse/Browse.js b/src/screens/Browse/Browse.js
index f5df9454..d43e0781 100644
--- a/src/screens/Browse/Browse.js
+++ b/src/screens/Browse/Browse.js
@@ -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 }) => (
);
@@ -124,9 +122,27 @@ const Browse = ({ navigation }) => {
Discover
{showNovelUpdates && (
-
+
+ navigation.navigate(
+ "BrowseNovelUpdates"
+ )
+ }
+ icon={require("../../../assets/novelupdates.png")}
+ theme={theme}
+ />
+ )}
+ {showMyAnimeList && (
+
+ navigation.navigate("BrowseMal")
+ }
+ icon={require("../../../assets/mal.png")}
+ theme={theme}
+ />
)}
- {showMyAnimeList && }
>
)}
{pinned.length > 0 && (
diff --git a/src/screens/Browse/SourceNovels.js b/src/screens/Browse/SourceNovels.js
index d4837c5c..1deffce0 100644
--- a/src/screens/Browse/SourceNovels.js
+++ b/src/screens/Browse/SourceNovels.js
@@ -53,25 +53,6 @@ const SourceNovels = ({ navigation, route }) => {
}
/>
- {/*
-
-
- */}
);
};
diff --git a/src/screens/Browse/components/MalCard.js b/src/screens/Browse/components/MalCard.js
deleted file mode 100644
index 37633ab6..00000000
--- a/src/screens/Browse/components/MalCard.js
+++ /dev/null
@@ -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 (
-
- <>
-
-
-
-
- MyAnimeList
-
-
-
-
-
-
- >
-
- );
-};
-
-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,
- },
-});
diff --git a/src/screens/Browse/components/NovelUpdatesCard.js b/src/screens/Browse/components/NovelUpdatesCard.js
deleted file mode 100644
index bab90670..00000000
--- a/src/screens/Browse/components/NovelUpdatesCard.js
+++ /dev/null
@@ -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 (
-
- <>
-
-
-
-
- Novel Updates
-
-
-
-
-
-
- >
-
- );
-};
-
-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,
- },
-});
diff --git a/src/screens/Browse/components/SourceCard.js b/src/screens/Browse/components/SourceCard.js
index abb94748..e984b939 100644
--- a/src/screens/Browse/components/SourceCard.js
+++ b/src/screens/Browse/components/SourceCard.js
@@ -1,17 +1,13 @@
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,
@@ -20,63 +16,59 @@ const ExtensionCard = ({ item, theme, isPinned }) => {
});
return (
-
- <>
-
-
-
-
- {sourceName}
-
-
- {lang}
-
-
-
+
+
+
-
- dispatch(pinSourceAction(sourceId))}
- color={
- isPinned
- ? theme.colorAccent
- : theme.textColorSecondary
- }
- style={{ margin: 2 }}
- />
-
+ {sourceName}
+
+
+ {lang}
+
- >
-
+
+
+ dispatch(pinSourceAction(sourceId))}
+ color={
+ isPinned
+ ? theme.colorAccent
+ : theme.textColorSecondary
+ }
+ style={{ margin: 2 }}
+ />
+
+
+
);
};
@@ -93,7 +85,7 @@ const styles = StyleSheet.create({
extensionIcon: {
width: 40,
height: 40,
- borderRadius: 8,
+ borderRadius: 4,
},
extensionDetails: {
flex: 1,
diff --git a/src/screens/Browse/discover/BrowseMalCard.js b/src/screens/Browse/discover/BrowseMalCard.js
index 7d5bee93..60e7eee5 100644
--- a/src/screens/Browse/discover/BrowseMalCard.js
+++ b/src/screens/Browse/discover/BrowseMalCard.js
@@ -67,49 +67,57 @@ const BrowseMalCard = ({ novel, theme, onPress }) => {
{novel.novelName}
{novel.score}
-
-
-
-
+
+
+
+ {`${"Type "}`}
+
+ {novel.info[1]}
+
+
+
- {`${"Type "}`}
-
- {novel.info[1]}
-
-
-
-
-
- {`${"Published "}`}
-
- {novel.info[2]}
-
-
+
+
+ {`${"Published "}`}
+
+ {novel.info[2]}
+
+
+ >
+ )}
diff --git a/src/screens/Browse/discover/DiscoverCard.js b/src/screens/Browse/discover/DiscoverCard.js
new file mode 100644
index 00000000..55959a7d
--- /dev/null
+++ b/src/screens/Browse/discover/DiscoverCard.js
@@ -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 (
+
+
+
+
+ {label}
+
+
+
+
+ );
+};
+
+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,
+ },
+});
diff --git a/src/screens/Browse/discover/MalTopNovels.js b/src/screens/Browse/discover/MalTopNovels.js
index e87cf604..f83bb341 100644
--- a/src/screens/Browse/discover/MalTopNovels.js
+++ b/src/screens/Browse/discover/MalTopNovels.js
@@ -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";
@@ -29,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) => {
@@ -155,8 +147,6 @@ const BrowseMalScreen = ({ navigation, route }) => {
) : (
item.novelName}
renderItem={renderItem}
diff --git a/src/screens/Browse/discover/MyAnimeListScraper.js b/src/screens/Browse/discover/MyAnimeListScraper.js
index 00f92eff..28af6770 100644
--- a/src/screens/Browse/discover/MyAnimeListScraper.js
+++ b/src/screens/Browse/discover/MyAnimeListScraper.js
@@ -63,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);
}
diff --git a/src/screens/Browse/discover/NovelUpdatesScraper.js b/src/screens/Browse/discover/NovelUpdatesScraper.js
index f23e6b9f..9afe6708 100644
--- a/src/screens/Browse/discover/NovelUpdatesScraper.js
+++ b/src/screens/Browse/discover/NovelUpdatesScraper.js
@@ -101,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);
});
diff --git a/src/screens/Browse/migration/MigrationNovelList.js b/src/screens/Browse/migration/MigrationNovelList.js
index 38377d00..f0ed62bd 100644
--- a/src/screens/Browse/migration/MigrationNovelList.js
+++ b/src/screens/Browse/migration/MigrationNovelList.js
@@ -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.`
);
diff --git a/src/screens/Chapter/components/ReaderSheet.js b/src/screens/Chapter/components/ReaderSheet.js
index ca9a857e..09ebe3d8 100644
--- a/src/screens/Chapter/components/ReaderSheet.js
+++ b/src/screens/Chapter/components/ReaderSheet.js
@@ -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";
@@ -68,6 +68,7 @@ const ReaderSheet = ({
justifyContent: "space-between",
width: "100%",
paddingHorizontal: 16,
+ paddingVertical: 8,
}}
>
@@ -145,7 +146,7 @@ const ReaderSheet = ({
justifyContent: "space-between",
width: "100%",
paddingHorizontal: 16,
- marginTop: 16,
+ paddingVertical: 8,
}}
>
@@ -216,7 +217,7 @@ const ReaderSheet = ({
justifyContent: "space-between",
width: "100%",
paddingHorizontal: 16,
- marginTop: 16,
+ paddingVertical: 8,
}}
>
@@ -266,7 +267,7 @@ const ReaderSheet = ({
justifyContent: "space-between",
width: "100%",
paddingHorizontal: 16,
- marginTop: 16,
+ paddingVertical: 8,
}}
>
@@ -313,13 +314,17 @@ const ReaderSheet = ({
/>
-
-
-
+
+ dispatch(
+ setAppSettings(
+ "showScrollPercentage",
+ !showScrollPercentage
+ )
+ )
+ }
>
-
-
+
+ dispatch(
+ setAppSettings("textSelectable", !selectText)
+ )
+ }
>
@@ -421,7 +444,7 @@ const ReaderSheet = ({
}
color={theme.colorAccent}
/>
-
+
diff --git a/src/screens/Novel/components/JumpToChapterModal.js b/src/screens/Novel/components/JumpToChapterModal.js
index 1a5c0cdd..b9f57618 100644
--- a/src/screens/Novel/components/JumpToChapterModal.js
+++ b/src/screens/Novel/components/JumpToChapterModal.js
@@ -24,7 +24,7 @@ const JumpToChapterModal = ({
const onSubmit = () => {
if (!mode) {
- if (text <= chapters.length) {
+ if (text > 0 && text <= chapters.length) {
navigation.navigate("Chapter", {
sourceId: novel.sourceId,
novelUrl: novel.novelUrl,
@@ -113,6 +113,7 @@ const JumpToChapterModal = ({
style={{
flexDirection: "row",
justifyContent: "space-between",
+ marginTop: 4,
}}
>
diff --git a/src/sources/en/lightnovelpub.js b/src/sources/en/lightnovelpub.js
index 1ee592fb..4a6f19b2 100644
--- a/src/sources/en/lightnovelpub.js
+++ b/src/sources/en/lightnovelpub.js
@@ -119,7 +119,7 @@ const parseNovelAndChapters = async (novelUrl) => {
const chapterUrl = $(this)
.find("a")
.attr("href")
- .replace(`/novel/${novelUrl}/`, "");
+ .replace(`/novel/${novelUrl}`, "");
const chapter = { chapterName, releaseDate, chapterUrl };
diff --git a/src/sources/helpers/htmlToText.js b/src/sources/helpers/htmlToText.js
index 760ae8d7..3c9f5353 100644
--- a/src/sources/helpers/htmlToText.js
+++ b/src/sources/helpers/htmlToText.js
@@ -16,9 +16,6 @@ const htmlToText = (html) => {
// Remove content in comments.
.replace(//gim, "")
// Format anchor tags properly.
- // e.g.
- // input - ]*>([\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);