diff --git a/package-lock.json b/package-lock.json index 36558507..ea6eb00d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "lnreader", "version": "2.0.0", "dependencies": { - "@cd-z/react-native-epub-creator": "^2.0.4", + "@cd-z/react-native-epub-creator": "^3.0.0", "@gorhom/bottom-sheet": "^5.1.4", "@legendapp/list": "^1.0.0-beta.24", "@react-native-community/slider": "4.5.6", @@ -48,6 +48,7 @@ "react-native-drawer-layout": "^4.0.3", "react-native-edge-to-edge": "^1.6.0", "react-native-error-boundary": "^1.1.16", + "react-native-file-access": "^3.1.1", "react-native-gesture-handler": "~2.25.0", "react-native-lottie-splash-screen": "^1.0.1", "react-native-mmkv": "^3.1.0", @@ -2252,22 +2253,22 @@ } }, "node_modules/@cd-z/epub-constructor": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@cd-z/epub-constructor/-/epub-constructor-2.2.5.tgz", - "integrity": "sha512-THLKTB3TppEPBE9I9M6IPEo3JGrD575/Wt2dEUig9hzIKox4vEWqygXFbnJgibjI3SudUeSnhUyLkh4S7pEBzg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@cd-z/epub-constructor/-/epub-constructor-3.0.1.tgz", + "integrity": "sha512-gI8eZ2+YnkflO48p+IjRVjFMJXv/86MYNdL6cxZUNd+GPEv3XBebg/hrHKXvTGTcBA3502CnwBf0hWoQyx3OoQ==", "license": "MIT", "dependencies": { - "node-html-parser": "^4.1.4" + "sanitize-html": "^2.13.0" } }, "node_modules/@cd-z/react-native-epub-creator": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@cd-z/react-native-epub-creator/-/react-native-epub-creator-2.0.4.tgz", - "integrity": "sha512-bVCE0J9lNjT2JaEVqfjU4kI5Fq4cI4KObWnNfo0NBeCjU89s6+3oHme1yKI/Xm2cq7QDWIklRW/WPXGAO4IE1A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@cd-z/react-native-epub-creator/-/react-native-epub-creator-3.0.0.tgz", + "integrity": "sha512-vpI3viNxkSJWuSCuotFKfe1Ulg7Z+VPBTSt4GkGSGwHwpZxkNnh/UOZqyHBTtn3yvjaq2PK3GABDZlxlDhiQvA==", "license": "MIT", "dependencies": { - "@cd-z/epub-constructor": "^2.1.0", - "expo-file-system": "^15.2.2", + "@cd-z/epub-constructor": "^3.0.0", + "react-native-file-access": "^3.1.0", "react-native-saf-x": "^2.2.1", "react-native-zip-archive": "^6.0.3" }, @@ -2276,18 +2277,6 @@ "react-native": "*" } }, - "node_modules/@cd-z/react-native-epub-creator/node_modules/expo-file-system": { - "version": "15.9.0", - "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-15.9.0.tgz", - "integrity": "sha512-ikzqBGpbSzp/57+aruXzRR2s0HEVYxdd1o3RxfswJRujjT/6X128xmUTTFDaQUr+zydyvxvY7Mo0szWpXhPyLA==", - "license": "MIT", - "dependencies": { - "uuid": "^3.4.0" - }, - "peerDependencies": { - "expo": "*" - } - }, "node_modules/@cd-z/react-native-epub-creator/node_modules/react-native-zip-archive": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/react-native-zip-archive/-/react-native-zip-archive-6.1.2.tgz", @@ -2298,16 +2287,6 @@ "react-native": ">=0.60.0" } }, - "node_modules/@cd-z/react-native-epub-creator/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "license": "MIT", - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/@egjs/hammerjs": { "version": "2.0.17", "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", @@ -9794,15 +9773,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -12697,84 +12667,6 @@ "node": ">= 6.13.0" } }, - "node_modules/node-html-parser": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-4.1.5.tgz", - "integrity": "sha512-NLgqUXtftqnBqIjlRjYSaApaqE7TTxfTiH4VqKCjdUJKFOtUzRwney83EHz2qYc0XoxXAkYdmLjENCuZHvsIFg==", - "license": "MIT", - "dependencies": { - "css-select": "^4.1.3", - "he": "1.2.0" - } - }, - "node_modules/node-html-parser/node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/node-html-parser/node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/node-html-parser/node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/node-html-parser/node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/node-html-parser/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -14091,6 +13983,16 @@ } } }, + "node_modules/react-native-file-access": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/react-native-file-access/-/react-native-file-access-3.1.1.tgz", + "integrity": "sha512-4KUpBAsnWJa+AQf1tUbLdHO+1pyiZMTeq3NPf5XOGdz1O5CwIrVkrzl+gkN7ffmUa5JyoYHyXUtwScmA+z0Tlg==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-gesture-handler": { "version": "2.25.0", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.25.0.tgz", diff --git a/package.json b/package.json index 424d9605..d07586cd 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ } }, "dependencies": { - "@cd-z/react-native-epub-creator": "^2.0.4", + "@cd-z/react-native-epub-creator": "^3.0.0", "@gorhom/bottom-sheet": "^5.1.4", "@legendapp/list": "^1.0.0-beta.24", "@react-native-community/slider": "4.5.6", @@ -65,6 +65,7 @@ "react-native-drawer-layout": "^4.0.3", "react-native-edge-to-edge": "^1.6.0", "react-native-error-boundary": "^1.1.16", + "react-native-file-access": "^3.1.1", "react-native-gesture-handler": "~2.25.0", "react-native-lottie-splash-screen": "^1.0.1", "react-native-mmkv": "^3.1.0", diff --git a/src/hooks/persisted/useImport.ts b/src/hooks/persisted/useImport.ts index f9c206c6..7f104d8e 100644 --- a/src/hooks/persisted/useImport.ts +++ b/src/hooks/persisted/useImport.ts @@ -1,9 +1,11 @@ +import { useLibraryContext } from '@components/Context/LibraryContext'; import ServiceManager, { BackgroundTask } from '@services/ServiceManager'; import { DocumentPickerResult } from 'expo-document-picker'; -import { useMemo } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { useMMKVObject } from 'react-native-mmkv'; export default function useImport() { + const { refetchLibrary } = useLibraryContext(); const [queue] = useMMKVObject( ServiceManager.manager.STORE_KEY, ); @@ -12,7 +14,11 @@ export default function useImport() { [queue], ); - const importNovel = (pickedNovel: DocumentPickerResult) => { + useEffect(() => { + refetchLibrary(); + }, [importQueue, refetchLibrary]); + + const importNovel = useCallback((pickedNovel: DocumentPickerResult) => { if (pickedNovel.canceled) return; ServiceManager.manager.addTask( pickedNovel.assets.map(asset => ({ @@ -23,7 +29,7 @@ export default function useImport() { }, })), ); - }; + }, []); const resumeImport = () => ServiceManager.manager.resume(); const pauseImport = () => ServiceManager.manager.pause(); diff --git a/src/screens/Categories/CategoriesScreen.tsx b/src/screens/Categories/CategoriesScreen.tsx index b85e4487..b6d0411e 100644 --- a/src/screens/Categories/CategoriesScreen.tsx +++ b/src/screens/Categories/CategoriesScreen.tsx @@ -1,31 +1,28 @@ import { FlatList, StyleSheet } from 'react-native'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { FAB } from 'react-native-paper'; import { useNavigation } from '@react-navigation/native'; import { Appbar, EmptyView, SafeAreaView } from '@components/index'; import AddCategoryModal from './components/AddCategoryModal'; -import { - getCategoriesFromDb, - updateCategoryOrderInDb, -} from '@database/queries/CategoryQueries'; +import { updateCategoryOrderInDb } from '@database/queries/CategoryQueries'; import { useBoolean } from '@hooks'; import { useTheme } from '@hooks/persisted'; import { getString } from '@strings/translations'; -import { Category } from '@database/types'; import CategoryCard from './components/CategoryCard'; import { orderBy } from 'lodash-es'; import CategorySkeletonLoading from './components/CategorySkeletonLoading'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { useLibraryContext } from '@components/Context/LibraryContext'; const CategoriesScreen = () => { + const { categories, setCategories, refreshCategories, isLoading } = + useLibraryContext(); const theme = useTheme(); const { goBack } = useNavigation(); - const [isLoading, setIsLoading] = useState(true); - const [categories, setCategories] = useState(); const { bottom, right } = useSafeAreaInsets(); const { @@ -34,25 +31,17 @@ const CategoriesScreen = () => { setFalse: closeCategoryModal, } = useBoolean(); - const getCategories = async () => { - try { - const res = getCategoriesFromDb(); - setCategories(res); - } finally { - setIsLoading(false); - } - }; - useEffect(() => { - getCategories(); - }, []); + refreshCategories(); + }, [refreshCategories]); + const updateCategorySort = (currentIndex: number, newIndex: number) => { // Do not set local as default one if ( (newIndex === 0 && - currentIndex == 1 && + currentIndex === 1 && categories?.[currentIndex].id === 2) || - (newIndex === 1 && currentIndex == 0 && categories?.[newIndex].id === 2) + (newIndex === 1 && currentIndex === 0 && categories?.[newIndex].id === 2) ) { return; } @@ -91,7 +80,7 @@ const CategoriesScreen = () => { renderItem={({ item, index }) => ( { ); diff --git a/src/screens/Categories/components/AddCategoryModal.tsx b/src/screens/Categories/components/AddCategoryModal.tsx index aec6579f..05d3f7a9 100644 --- a/src/screens/Categories/components/AddCategoryModal.tsx +++ b/src/screens/Categories/components/AddCategoryModal.tsx @@ -37,6 +37,10 @@ const AddCategoryModal: React.FC = ({ setCategoryName(''); closeModal(); } + function finalize() { + onSuccess(); + close(); + } return ( @@ -69,8 +73,7 @@ const AddCategoryModal: React.FC = ({ } else { createCategory(categoryName); } - onSuccess(); - close(); + finalize(); } }} /> diff --git a/src/screens/library/LibraryScreen.tsx b/src/screens/library/LibraryScreen.tsx index 8391486a..1c753ba3 100644 --- a/src/screens/library/LibraryScreen.tsx +++ b/src/screens/library/LibraryScreen.tsx @@ -289,6 +289,18 @@ const LibraryScreen = ({ navigation }: LibraryScreenProps) => { ], ); + const navigationState = useMemo( + () => ({ + index, + routes: categories.map(category => ({ + key: String(category.id), + title: category.name, + ...category, + })), + }), + [categories, index], + ); + return ( { label: renderLabel, }} lazy - navigationState={{ - index, - routes: categories.map(category => ({ - key: String(category.id), - title: category.name, - ...category, - })), - }} + navigationState={navigationState} renderTabBar={renderTabBar} renderScene={renderScene} onIndexChange={setIndex} diff --git a/src/screens/library/components/LibraryListView.tsx b/src/screens/library/components/LibraryListView.tsx index 93fb8c14..67dd2445 100644 --- a/src/screens/library/components/LibraryListView.tsx +++ b/src/screens/library/components/LibraryListView.tsx @@ -12,7 +12,6 @@ import { getString } from '@strings/translations'; import { useTheme } from '@hooks/persisted'; import { LibraryScreenProps } from '@navigators/types'; import ServiceManager from '@services/ServiceManager'; -import { useLibraryContext } from '@components/Context/LibraryContext'; interface Props { categoryId: number; @@ -31,8 +30,8 @@ export const LibraryView: React.FC = ({ setSelectedNovelIds, pickAndImport, navigation, + novels, }) => { - const { library } = useLibraryContext(); const theme = useTheme(); const renderItem = ({ item }: { item: NovelInfo }) => { return ( @@ -80,7 +79,7 @@ export const LibraryView: React.FC = ({ return ( >; + refreshCategories: () => Promise; setLibrary: React.Dispatch>; novelInLibrary: (pluginId: string, novelPath: string) => boolean; switchNovelToLibrary: (novelPath: string, pluginId: string) => Promise; @@ -35,26 +37,30 @@ export const useLibrary = (): UseLibraryReturnType => { const [isLoading, setIsLoading] = useState(true); const [searchText, setSearchText] = useState(''); - const getLibrary = useCallback(async () => { - if (searchText) { - setIsLoading(true); - } + const refreshCategories = useCallback(async () => { + const dbCategories = getCategoriesFromDb(); - const [categories, novels] = await Promise.all([ - getCategoriesFromDb(), - getLibraryNovelsFromDb(sortOrder, filter, searchText, downloadedOnlyMode), - // getLibraryNovelsFromDb(), - ]); - - const res = categories.map(c => ({ + const res = dbCategories.map(c => ({ ...c, novelIds: (c.novelIds ?? '').split(',').map(Number), })); setCategories(res); + }, []); + + const getLibrary = useCallback(async () => { + if (searchText) { + setIsLoading(true); + } + + const [_, novels] = await Promise.all([ + refreshCategories(), + getLibraryNovelsFromDb(sortOrder, filter, searchText, downloadedOnlyMode), + ]); + setLibrary(novels); setIsLoading(false); - }, [downloadedOnlyMode, filter, searchText, sortOrder]); + }, [downloadedOnlyMode, filter, refreshCategories, searchText, sortOrder]); const novelInLibrary = useCallback( (pluginId: string, novelPath: string) => @@ -91,6 +97,8 @@ export const useLibrary = (): UseLibraryReturnType => { categories, isLoading, setLibrary, + setCategories, + refreshCategories, novelInLibrary, switchNovelToLibrary, refetchLibrary: getLibrary,