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 = ({ /> - {fonts.find( @@ -367,14 +371,25 @@ 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);