From 5e0f375f7fb4a3ed4cdebce0a43ffccfa13432ba Mon Sep 17 00:00:00 2001 From: Patrick Loser <69157453+CD-Z@users.noreply.github.com> Date: Tue, 20 May 2025 00:02:24 +0200 Subject: [PATCH] chore: fix all lint warnings (#1510) * fix all lint warnings * fix types --- src/components/Checkbox/Checkbox.tsx | 3 +- .../ColorPickerModal/ColorPickerModal.tsx | 27 +- src/components/Common/ToggleButton.tsx | 86 ++++-- src/components/ErrorView/ErrorView.tsx | 73 +++-- src/components/List/List.tsx | 124 ++++---- src/components/ListView.tsx | 3 +- src/components/Skeleton/Skeleton.tsx | 4 +- src/components/ThemePicker/ThemePicker.tsx | 292 ++++++++++-------- src/hooks/common/useSearch.ts | 2 +- src/hooks/persisted/useHistory.ts | 14 +- src/hooks/persisted/usePlugins.ts | 49 +-- src/hooks/persisted/useTheme.ts | 2 +- src/navigators/Main.tsx | 2 +- src/plugins/helpers/fetch.ts | 1 + .../BrowseSourceScreen/useBrowseSource.ts | 12 +- .../Categories/components/CategoryCard.tsx | 4 +- .../hooks/useGlobalSearch.ts | 196 ++++++------ src/screens/browse/SourceNovels.tsx | 12 +- .../browse/discover/AniListTopNovels.tsx | 127 ++++---- src/screens/browse/discover/MalTopNovels.tsx | 3 +- .../globalsearch/GlobalSearchNovelCover.tsx | 4 +- src/screens/browse/migration/Migration.tsx | 3 +- .../browse/migration/MigrationNovelList.tsx | 38 ++- .../browse/migration/MigrationNovels.tsx | 9 +- .../browse/migration/MigrationSourceItem.tsx | 22 +- .../library/components/LibraryListView.tsx | 8 +- src/screens/library/hooks/useLibrary.ts | 2 +- src/screens/more/About.tsx | 8 +- src/screens/more/DownloadsScreen.tsx | 23 +- src/screens/more/TaskQueueScreen.tsx | 9 +- src/screens/more/components/MoreHeader.tsx | 6 +- .../more/components/RemoveDownloadsDialog.tsx | 30 +- .../Chapter/ChapterDownloadButtons.tsx | 2 +- .../components/DownloadCustomChapterModal.tsx | 6 +- .../novel/components/EditInfoModal.tsx | 143 +++++---- .../components/Info/NovelInfoComponents.tsx | 123 ++++---- .../novel/components/Info/ReadButton.tsx | 7 +- .../LoadingAnimation/NovelScreenLoading.tsx | 10 +- .../novel/components/NovelBottomSheet.tsx | 31 +- .../components/NovelSummary/NovelSummary.tsx | 3 +- .../novel/components/SetCategoriesModal.tsx | 21 +- .../novel/components/Tracker/TrackerCards.js | 56 ++-- src/screens/onboarding/OnboardingScreen.tsx | 36 +-- src/screens/onboarding/PickThemeStep.tsx | 14 +- .../ReaderBottomSheet/ReaderBottomSheet.tsx | 3 +- src/screens/reader/hooks/useChapter.ts | 4 + .../settings/SettingsAppearanceScreen.tsx | 52 ++-- .../Components/GoogleDriveModal.tsx | 27 +- .../Components/SelfHostModal.tsx | 24 +- .../settings/SettingsBackupScreen/index.tsx | 7 +- .../SettingsGeneralScreen.tsx | 8 +- .../Modals/VoicePickerModal.tsx | 6 +- .../Settings/TextToSpeechSettings.tsx | 3 +- .../SettingsRepositoryScreen.tsx | 43 +-- src/screens/settings/SettingsScreen.tsx | 8 +- .../settings/SettingsTrackerScreen.tsx | 71 +++-- .../settings/components/SettingSwitch.tsx | 7 +- src/screens/updates/UpdatesScreen.tsx | 2 +- .../updates/components/UpdateNovelCard.tsx | 6 +- 59 files changed, 1090 insertions(+), 831 deletions(-) diff --git a/src/components/Checkbox/Checkbox.tsx b/src/components/Checkbox/Checkbox.tsx index 510a387c..800c3143 100644 --- a/src/components/Checkbox/Checkbox.tsx +++ b/src/components/Checkbox/Checkbox.tsx @@ -68,7 +68,7 @@ interface SortItemProps { export const SortItem = ({ label, status, onPress, theme }: SortItemProps) => ( {status ? ( @@ -98,4 +98,5 @@ const styles = StyleSheet.create({ paddingHorizontal: 16, paddingVertical: 6, }, + sortItem: { paddingVertical: 16, paddingLeft: 64 }, }); diff --git a/src/components/ColorPickerModal/ColorPickerModal.tsx b/src/components/ColorPickerModal/ColorPickerModal.tsx index 5d6e5d3f..a59c7d1c 100644 --- a/src/components/ColorPickerModal/ColorPickerModal.tsx +++ b/src/components/ColorPickerModal/ColorPickerModal.tsx @@ -79,24 +79,14 @@ const ColorPickerModal: React.FC = ({ {showAccentColors ? ( item} renderItem={({ item }) => ( - + ({ + backgroundColor: selected + ? Color(theme.primary).alpha(0.12).string() + : 'transparent', +}); + +const getToggleColorButtonPressableStyle = (backgroundColor: string) => ({ + backgroundColor, +}); + +// --- Components --- + interface ToggleButtonProps { icon: MaterialDesignIconName; selected: boolean; @@ -20,23 +37,13 @@ export const ToggleButton: React.FC = ({ color, onPress, }) => ( - + = ({ textColor, onPress, }) => ( - + = ({ ); + +const styles = StyleSheet.create({ + toggleButtonContainer: { + borderRadius: 6, + overflow: 'hidden', + marginHorizontal: 6, + }, + toggleButtonPressable: { + padding: 8, + alignItems: 'center', + justifyContent: 'center', + }, + toggleColorButtonContainer: { + borderRadius: 50, + overflow: 'hidden', + marginHorizontal: 6, + height: 44, + width: 44, + }, + toggleColorButtonPressable: { + flex: 1, + padding: 10, + alignItems: 'center', + justifyContent: 'center', + }, +}); diff --git a/src/components/ErrorView/ErrorView.tsx b/src/components/ErrorView/ErrorView.tsx index 9e23716a..84bfacfe 100644 --- a/src/components/ErrorView/ErrorView.tsx +++ b/src/components/ErrorView/ErrorView.tsx @@ -3,8 +3,6 @@ import React from 'react'; import { Pressable, StyleSheet, Text, View } from 'react-native'; import { IconButton } from 'react-native-paper'; -// const icons = ['(・o・;)', 'Σ(ಠ_ಠ)', 'ಥ_ಥ', '(˘・_・˘)', '(; ̄Д ̄)', '(・Д・。']; - interface ErrorAction { name: string; icon: string; @@ -17,45 +15,37 @@ interface ErrorViewProps { theme: ThemeColors; } +// Dynamic style helpers +const getOutlineColor = (theme: ThemeColors) => ({ color: theme.outline }); +const getRipple = (theme: ThemeColors) => ({ + color: theme.rippleColor, + borderless: false, +}); +const getActionTextColor = (theme: ThemeColors) => ({ color: theme.outline }); + export const ErrorView = ({ errorName, actions, theme }: ErrorViewProps) => ( - - + + {/* {icons[Math.floor(Math.random() * 5)]} */} ಥ_ಥ - + {errorName} - + {actions.map(action => ( - + - - + + {action.name} @@ -80,4 +70,25 @@ const styles = StyleSheet.create({ paddingHorizontal: 30, textAlign: 'center', }, + actionsRow: { + flexDirection: 'row', + }, + actionContainer: { + borderRadius: 4, + overflow: 'hidden', + margin: 16, + }, + actionPressable: { + justifyContent: 'center', + alignItems: 'center', + paddingBottom: 8, + paddingHorizontal: 20, + minWidth: 100, + }, + iconButton: { + margin: 0, + }, + actionText: { + fontSize: 12, + }, }); diff --git a/src/components/List/List.tsx b/src/components/List/List.tsx index 42804124..119e8a30 100644 --- a/src/components/List/List.tsx +++ b/src/components/List/List.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import React, { ReactNode, useCallback } from 'react'; import { Pressable, StyleProp, @@ -46,45 +46,51 @@ const Item: React.FC = ({ theme, disabled, right, -}) => ( - ( - - ) - : undefined +}) => { + const left = useCallback(() => { + if (icon) { + return ( + + ); } - right={ - right - ? () => ( - - ) - : undefined + }, [icon, theme.primary]); + const rightIcon = useCallback(() => { + if (right) { + return ( + + ); } - disabled={disabled} - onPress={onPress} - rippleColor={theme.rippleColor} - style={styles.listItemCtn} - /> -); + }, [right, theme.primary]); + return ( + + ); +}; const Divider = ({ theme }: { theme: ThemeColors }) => ( @@ -113,7 +119,7 @@ const InfoItem = ({ ); const Icon = ({ icon, theme }: { icon: string; theme: ThemeColors }) => ( - + ); interface ColorItemProps { @@ -125,27 +131,23 @@ interface ColorItemProps { const ColorItem = ({ title, description, theme, onPress }: ColorItemProps) => ( - {title} + + {title} + {description} ); @@ -161,6 +163,16 @@ export default { }; const styles = StyleSheet.create({ + margin0: { margin: 0 }, + fontSize16: { + fontSize: 16, + }, + descriptionView: { + height: 24, + width: 24, + borderRadius: 50, + marginRight: 16, + }, description: { fontSize: 12, lineHeight: 20, @@ -187,4 +199,10 @@ const styles = StyleSheet.create({ flex: 1, marginVertical: 0, }, + pressable: { + padding: 16, + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, }); diff --git a/src/components/ListView.tsx b/src/components/ListView.tsx index 3799f8fd..f60b6727 100644 --- a/src/components/ListView.tsx +++ b/src/components/ListView.tsx @@ -29,6 +29,7 @@ const ListView = ({ isSelected, onLongPress, }: ListViewProps) => { + const fadedImage = { opacity: inLibraryBadge ? 0.5 : 1 }; return ( { React.useEffect(() => { if (disableLoadingAnimations) return; sv.value = withRepeat(withSequence(0, withTiming(160, { duration })), -1); - }, [disableLoadingAnimations]); + }, [disableLoadingAnimations, sv]); const skeletonItems = React.useMemo(() => Array.from({ length: 7 }), []); @@ -178,7 +178,7 @@ const ChapterListSkeleton = ({ img }: { img?: boolean }) => { const LGC = React.useMemo( () => createLGC(highlightColor, animatedProps, disableLoadingAnimations), - [disableLoadingAnimations, highlightColor], + [animatedProps, disableLoadingAnimations, highlightColor], ); const backgroundStyle = React.useMemo( () => ({ backgroundColor }), diff --git a/src/components/ThemePicker/ThemePicker.tsx b/src/components/ThemePicker/ThemePicker.tsx index 2b8abb09..35d21d8c 100644 --- a/src/components/ThemePicker/ThemePicker.tsx +++ b/src/components/ThemePicker/ThemePicker.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { View, Text, /* StyleSheet, */ Pressable } from 'react-native'; +import { View, Text, StyleSheet, Pressable } from 'react-native'; import { overlay } from 'react-native-paper'; import color from 'color'; import MaterialCommunityIcons from '@react-native-vector-icons/material-design-icons'; @@ -18,172 +18,208 @@ export const ThemePicker = ({ onPress, horizontal = false, }: ThemePickerProps) => ( - + - + {currentTheme.id === theme.id ? ( ) : null} - + - + - + - + + - - + {theme.name} ); -// const styles = StyleSheet.create({}); +const styles = StyleSheet.create({ + container: { + justifyContent: 'center', + alignItems: 'center', + paddingBottom: 8, + width: '33%', + }, + horizontalContainer: { + width: undefined, + marginHorizontal: 4, + }, + card: { + borderWidth: 3.6, + width: 95, + height: 140, + borderRadius: 16, + overflow: 'hidden', + // Shadow for iOS + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 4, + // Elevation for Android + elevation: 2, + }, + flex1: { + flex: 1, + }, + checkIcon: { + position: 'absolute', + top: 5, + right: 5, + borderRadius: 50, + padding: 1.6, + zIndex: 1, + }, + topBar: { + height: 20, + justifyContent: 'center', + boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)', + }, + topBarAccent: { + width: 44, + height: 10, + marginLeft: 8, + borderRadius: 50, + }, + content: { + padding: 8, + }, + titleBar: { + height: 18, + borderRadius: 4, + }, + row: { + paddingVertical: 4, + flexDirection: 'row', + }, + rowAccent: { + height: 10, + width: 44, + borderRadius: 50, + }, + rowAccentSmall: { + height: 10, + width: 16, + marginLeft: 4, + borderRadius: 50, + }, + rowAccentShort: { + height: 10, + width: 24, + borderRadius: 50, + }, + bottomBar: { + position: 'absolute', + bottom: 0, + left: 0, + right: 0, + height: 24, + justifyContent: 'center', + }, + bottomBarContent: { + flex: 1, + justifyContent: 'space-between', + alignItems: 'center', + flexDirection: 'row', + paddingHorizontal: 16, + }, + dot: { + height: 12, + width: 12, + borderRadius: 50, + }, + opacityDot: { + opacity: 0.54, + }, + themeName: { + fontSize: 12, + paddingVertical: 4, + }, + marginLeft: { marginLeft: 4 }, +}); diff --git a/src/hooks/common/useSearch.ts b/src/hooks/common/useSearch.ts index 1b8278b6..a5ca3888 100644 --- a/src/hooks/common/useSearch.ts +++ b/src/hooks/common/useSearch.ts @@ -14,7 +14,7 @@ const useSearch = (defaultSearchText?: string, clearSearchOnUnfocus = true) => { clearSearchbar(); } } - }, [isFocused, clearSearchbar]); + }, [isFocused, clearSearchbar, clearSearchOnUnfocus]); return { searchText, setSearchText, clearSearchbar }; }; diff --git a/src/hooks/persisted/useHistory.ts b/src/hooks/persisted/useHistory.ts index 1c4c4f21..5e73639d 100644 --- a/src/hooks/persisted/useHistory.ts +++ b/src/hooks/persisted/useHistory.ts @@ -20,16 +20,16 @@ const useHistory = () => { getHistoryFromDb() .then(res => setHistory( - res.map(history => { - const parsedTime = dayjs(history.releaseTime); + res.map(localHistory => { + const parsedTime = dayjs(localHistory.releaseTime); return { - ...history, + ...localHistory, releaseTime: parsedTime.isValid() ? parsedTime.format('LL') - : history.releaseTime, - chapterNumber: history.chapterNumber - ? history.chapterNumber - : parseChapterNumber(history.novelName, history.name), + : localHistory.releaseTime, + chapterNumber: localHistory.chapterNumber + ? localHistory.chapterNumber + : parseChapterNumber(localHistory.novelName, localHistory.name), }; }), ), diff --git a/src/hooks/persisted/usePlugins.ts b/src/hooks/persisted/usePlugins.ts index c135ae04..f03ee162 100644 --- a/src/hooks/persisted/usePlugins.ts +++ b/src/hooks/persisted/usePlugins.ts @@ -37,28 +37,31 @@ export default function usePlugins() { * We cant use the languagesFilter directly because it is updated only after component's lifecycle end. * And toggleLanguagFilter triggers filterPlugins before lifecycle end. */ - const filterPlugins = useCallback((filter: string[]) => { - const installedPlugins = - getMMKVObject(INSTALLED_PLUGINS) || []; - const availablePlugins = - getMMKVObject(AVAILABLE_PLUGINS) || []; - setFilteredInstalledPlugins( - installedPlugins.filter(plg => filter.includes(plg.lang)), - ); - setFilteredAvailablePlugins( - orderBy( - availablePlugins - .filter( - avalilablePlugin => - !installedPlugins.some( - installedPlugin => installedPlugin.id === avalilablePlugin.id, - ), - ) - .filter(plg => filter.includes(plg.lang)), - 'name', - ), - ); - }, []); + const filterPlugins = useCallback( + (filter: string[]) => { + const installedPlugins = + getMMKVObject(INSTALLED_PLUGINS) || []; + const availablePlugins = + getMMKVObject(AVAILABLE_PLUGINS) || []; + setFilteredInstalledPlugins( + installedPlugins.filter(plg => filter.includes(plg.lang)), + ); + setFilteredAvailablePlugins( + orderBy( + availablePlugins + .filter( + avalilablePlugin => + !installedPlugins.some( + installedPlugin => installedPlugin.id === avalilablePlugin.id, + ), + ) + .filter(plg => filter.includes(plg.lang)), + 'name', + ), + ); + }, + [setFilteredAvailablePlugins, setFilteredInstalledPlugins], + ); const refreshPlugins = useCallback(() => { const installedPlugins = @@ -83,7 +86,7 @@ export default function usePlugins() { setMMKVObject(AVAILABLE_PLUGINS, fetchedPlugins); filterPlugins(languagesFilter); }); - }, [languagesFilter]); + }, [filterPlugins, languagesFilter, lastUsedPlugin?.id, setLastUsedPlugin]); const toggleLanguageFilter = (lang: string) => { const newFilter = languagesFilter.includes(lang) diff --git a/src/hooks/persisted/useTheme.ts b/src/hooks/persisted/useTheme.ts index 379c80da..684de339 100644 --- a/src/hooks/persisted/useTheme.ts +++ b/src/hooks/persisted/useTheme.ts @@ -55,7 +55,7 @@ export const useTheme = (): ThemeColors => { }; return colors; - }, [appTheme?.id, isAmoledBlack, customAccent]); + }, [appTheme, isAmoledBlack, customAccent]); return theme; }; diff --git a/src/navigators/Main.tsx b/src/navigators/Main.tsx index d6063e47..40010967 100644 --- a/src/navigators/Main.tsx +++ b/src/navigators/Main.tsx @@ -65,7 +65,7 @@ const MainNavigator = () => { // hack this helps app has enough time to initialize database; refreshPlugins(); } - }, [isOnboarded]); + }, [isOnboarded, refreshPlugins, updateLibraryOnLaunch]); const { isNewVersion, latestRelease } = useGithubUpdateChecker(); diff --git a/src/plugins/helpers/fetch.ts b/src/plugins/helpers/fetch.ts index c6696c08..3c8c265c 100644 --- a/src/plugins/helpers/fetch.ts +++ b/src/plugins/helpers/fetch.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-bitwise */ import { getUserAgent } from '@hooks/persisted/useUserAgent'; import NativeFile from '@specs/NativeFile'; import { parse as parseProto } from 'protobufjs'; diff --git a/src/screens/BrowseSourceScreen/useBrowseSource.ts b/src/screens/BrowseSourceScreen/useBrowseSource.ts index 099cc359..8e1d934d 100644 --- a/src/screens/BrowseSourceScreen/useBrowseSource.ts +++ b/src/screens/BrowseSourceScreen/useBrowseSource.ts @@ -44,8 +44,8 @@ export const useBrowseSource = ( setHasNextPage(false); } }) - .catch(error => { - setError(error.message); + .catch(e => { + setError(e.message); setHasNextPage(false); }); setFilterValues(plugin.filters); @@ -56,7 +56,7 @@ export const useBrowseSource = ( } } }, - [pluginId], + [pluginId, showLatestNovels], ); const fetchNextPage = () => { @@ -74,7 +74,7 @@ export const useBrowseSource = ( useEffect(() => { fetchNovels(currentPage, selectedFilters); - }, [fetchNovels, currentPage]); + }, [fetchNovels, currentPage, selectedFilters]); const refetchNovels = () => { setError(''); @@ -128,14 +128,14 @@ export const useSearchSource = (pluginId: string) => { const isScreenMounted = useRef(true); const fetchNovels = useCallback( - async (searchText: string, page: number) => { + async (localSearchText: string, page: number) => { if (isScreenMounted.current === true) { try { const plugin = getPlugin(pluginId); if (!plugin) { throw new Error(`Unknown plugin: ${pluginId}`); } - const res = await plugin.searchNovels(searchText, page); + const res = await plugin.searchNovels(localSearchText, page); setSearchResults(prevState => page === 1 ? res : [...prevState, ...res], ); diff --git a/src/screens/Categories/components/CategoryCard.tsx b/src/screens/Categories/components/CategoryCard.tsx index 46d0b3b5..bf8bb017 100644 --- a/src/screens/Categories/components/CategoryCard.tsx +++ b/src/screens/Categories/components/CategoryCard.tsx @@ -38,6 +38,8 @@ const CategoryCard: React.FC = ({ setFalse: closeDeleteCategoryModal, } = useBoolean(); + const fontWeight = category.sort === 1 ? '700' : '300'; + return ( <> = ({ styles.name, { color: theme.onSurface, - fontWeight: category.sort === 1 ? '700' : '300', + fontWeight, }, ]} onPress={showCategoryModal} diff --git a/src/screens/GlobalSearchScreen/hooks/useGlobalSearch.ts b/src/screens/GlobalSearchScreen/hooks/useGlobalSearch.ts index 9cc61d3e..96ab71cd 100644 --- a/src/screens/GlobalSearchScreen/hooks/useGlobalSearch.ts +++ b/src/screens/GlobalSearchScreen/hooks/useGlobalSearch.ts @@ -41,113 +41,117 @@ export const useGlobalSearch = ({ defaultSearchText }: Props) => { const { globalSearchConcurrency = 1 } = useBrowseSettings(); - const globalSearch = (searchText: string) => { - if (lastSearch.current === searchText) { - return; - } - lastSearch.current = searchText; - const defaultResult: GlobalSearchResult[] = filteredInstalledPlugins.map( - plugin => ({ - isLoading: true, - plugin, - novels: [], - error: null, - }), - ); - - setSearchResults(defaultResult.sort(novelResultSorter)); - setProgress(0); - - let running = 0; - - async function searchInPlugin(_plugin: PluginItem) { - try { - const plugin = getPlugin(_plugin.id); - if (!plugin) { - throw new Error(`Unknown plugin: ${_plugin.id}`); - } - const res = await plugin.searchNovels(searchText, 1); - - setSearchResults(prevState => - prevState - .map(prevResult => - prevResult.plugin.id === plugin.id - ? { ...prevResult, novels: res, isLoading: false } - : { ...prevResult }, - ) - .sort(novelResultSorter), - ); - } catch (error: any) { - const errorMessage = error?.message || String(error); - setSearchResults(prevState => - prevState - .map(prevResult => - prevResult.plugin.id === _plugin.id - ? { - ...prevResult, - novels: [], - isLoading: false, - error: errorMessage, - } - : { ...prevResult }, - ) - .sort(novelResultSorter), - ); + const globalSearch = useCallback( + (searchText: string) => { + if (lastSearch.current === searchText) { + return; } - } + lastSearch.current = searchText; + const defaultResult: GlobalSearchResult[] = filteredInstalledPlugins.map( + plugin => ({ + isLoading: true, + plugin, + novels: [], + error: null, + }), + ); - //Sort so we load the plugins results in the same order as they show on the list - const filteredSortedInstalledPlugins = [...filteredInstalledPlugins].sort( - (a, b) => a.name.localeCompare(b.name), - ); + setSearchResults(defaultResult.sort(novelResultSorter)); + setProgress(0); - (async () => { - if (globalSearchConcurrency > 1) { - for (const _plugin of filteredSortedInstalledPlugins) { - while (running >= globalSearchConcurrency || !isFocused.current) { - await new Promise(resolve => setTimeout(resolve, 100)); - } - if (!isMounted.current || lastSearch.current !== searchText) { - break; - } - running++; - searchInPlugin(_plugin) - .then(() => { - running--; - if (lastSearch.current === searchText) { - setProgress( - prevState => prevState + 1 / filteredInstalledPlugins.length, - ); - } - }) - .catch(() => { - running--; - }); - } - } else { - for (const _plugin of filteredSortedInstalledPlugins) { - if (!isMounted.current || lastSearch.current !== searchText) { - break; - } - while (!isFocused.current) { - await new Promise(resolve => setTimeout(resolve, 100)); - } - await searchInPlugin(_plugin); - if (lastSearch.current === searchText) { - setProgress( - prevState => prevState + 1 / filteredInstalledPlugins.length, - ); + let running = 0; + + async function searchInPlugin(_plugin: PluginItem) { + try { + const plugin = getPlugin(_plugin.id); + if (!plugin) { + throw new Error(`Unknown plugin: ${_plugin.id}`); } + const res = await plugin.searchNovels(searchText, 1); + + setSearchResults(prevState => + prevState + .map(prevResult => + prevResult.plugin.id === plugin.id + ? { ...prevResult, novels: res, isLoading: false } + : { ...prevResult }, + ) + .sort(novelResultSorter), + ); + } catch (error: any) { + const errorMessage = error?.message || String(error); + setSearchResults(prevState => + prevState + .map(prevResult => + prevResult.plugin.id === _plugin.id + ? { + ...prevResult, + novels: [], + isLoading: false, + error: errorMessage, + } + : { ...prevResult }, + ) + .sort(novelResultSorter), + ); } } - })(); - }; + + //Sort so we load the plugins results in the same order as they show on the list + const filteredSortedInstalledPlugins = [...filteredInstalledPlugins].sort( + (a, b) => a.name.localeCompare(b.name), + ); + + (async () => { + if (globalSearchConcurrency > 1) { + for (const _plugin of filteredSortedInstalledPlugins) { + while (running >= globalSearchConcurrency || !isFocused.current) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + if (!isMounted.current || lastSearch.current !== searchText) { + break; + } + running++; + searchInPlugin(_plugin) + .then(() => { + running--; + if (lastSearch.current === searchText) { + setProgress( + prevState => + prevState + 1 / filteredInstalledPlugins.length, + ); + } + }) + .catch(() => { + running--; + }); + } + } else { + for (const _plugin of filteredSortedInstalledPlugins) { + if (!isMounted.current || lastSearch.current !== searchText) { + break; + } + while (!isFocused.current) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + await searchInPlugin(_plugin); + if (lastSearch.current === searchText) { + setProgress( + prevState => prevState + 1 / filteredInstalledPlugins.length, + ); + } + } + } + })(); + }, + [filteredInstalledPlugins, globalSearchConcurrency], + ); useEffect(() => { if (defaultSearchText) { globalSearch(defaultSearchText); } - }, []); + }, [defaultSearchText, globalSearch]); return { searchResults, globalSearch, progress }; }; diff --git a/src/screens/browse/SourceNovels.tsx b/src/screens/browse/SourceNovels.tsx index 4702af34..eb3036ed 100644 --- a/src/screens/browse/SourceNovels.tsx +++ b/src/screens/browse/SourceNovels.tsx @@ -41,11 +41,12 @@ const SourceNovels = ({ navigation, route }: SourceNovelsScreenProps) => { renderItem={renderItem} ListEmptyComponent={ {getString('browseScreen.noSource')} @@ -59,4 +60,5 @@ export default SourceNovels; const styles = StyleSheet.create({ container: { flex: 1 }, + text: { padding: 20, textAlign: 'center' }, }); diff --git a/src/screens/browse/discover/AniListTopNovels.tsx b/src/screens/browse/discover/AniListTopNovels.tsx index 2ee9faaa..71d629f3 100644 --- a/src/screens/browse/discover/AniListTopNovels.tsx +++ b/src/screens/browse/discover/AniListTopNovels.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { StyleSheet, View, @@ -90,50 +90,53 @@ const BrowseALScreen = ({ navigation }: BrowseALScreenProps) => { const anilistUrl = 'https://anilist.co/search/manga?format=NOVEL&sort=POPULARITY_DESC'; - const searchAniList = async (onlyTop: boolean, page = 1) => { - try { - if (!tracker) { + const searchAniList = useCallback( + async (onlyTop: boolean, page = 1) => { + try { + if (!tracker) { + setLoading(false); + setError('Please login!'); + return; + } + const { data } = await queryAniList( + anilistSearchQuery, + { + search: onlyTop ? undefined : searchText, + page, + }, + tracker.auth, + ); + + const results = data.Page.media.map((m: any) => { + return { + id: m.id, + novelName: m.title.userPreferred, + novelCover: m.coverImage.extraLarge, + score: `${m.averageScore}%`, + info: [ + '', // MAL returns an item we don't care about first, so the component ignores the first element + `Light Novel (${m.volumes || '?'} Vols)`, + `${formatDate(m.startDate)}${ + datesEqual(m.startDate, m.endDate) + ? '' + : `- ${formatDate(m.endDate)}` + }`, + ], + }; + }); + + setHasNextPage(data.Page.pageInfo.hasNextPage); + setNovels(onlyTop ? before => before.concat(results) : results); setLoading(false); - setError('Please login!'); - return; + } catch (err: any) { + setError(err.message); + setNovels([]); + setLoading(false); + showToast(err.message); } - const { data } = await queryAniList( - anilistSearchQuery, - { - search: onlyTop ? undefined : searchText, - page, - }, - tracker.auth, - ); - - const results = data.Page.media.map((m: any) => { - return { - id: m.id, - novelName: m.title.userPreferred, - novelCover: m.coverImage.extraLarge, - score: `${m.averageScore}%`, - info: [ - '', // MAL returns an item we don't care about first, so the component ignores the first element - `Light Novel (${m.volumes || '?'} Vols)`, - `${formatDate(m.startDate)}${ - datesEqual(m.startDate, m.endDate) - ? '' - : `- ${formatDate(m.endDate)}` - }`, - ], - }; - }); - - setHasNextPage(data.Page.pageInfo.hasNextPage); - setNovels(onlyTop ? before => before.concat(results) : results); - setLoading(false); - } catch (err: any) { - setError(err.message); - setNovels([]); - setLoading(false); - showToast(err.message); - } - }; + }, + [anilistSearchQuery, searchText, tracker], + ); const clearSearchbar = () => { setNovels([]); @@ -145,7 +148,7 @@ const BrowseALScreen = ({ navigation }: BrowseALScreenProps) => { useEffect(() => { searchAniList(true); - }, []); + }, [searchAniList]); const renderItem: ListRenderItem = ({ item }) => ( { /> ); - const ListEmptyComponent = () => ( - { - setLoading(true); - setError(undefined); - searchAniList(true); + const ListEmptyComponent = useCallback( + () => ( + { + setLoading(true); + setError(undefined); + searchAniList(true); + }, + icon: 'reload', }, - icon: 'reload', - }, - ]} - theme={theme} - /> + ]} + theme={theme} + /> + ), + [error, searchAniList, theme], ); return ( @@ -213,7 +219,7 @@ const BrowseALScreen = ({ navigation }: BrowseALScreenProps) => { }} ListFooterComponent={ !searchText ? ( - + ) : null @@ -235,4 +241,5 @@ const styles = StyleSheet.create({ paddingBottom: 8, paddingHorizontal: 4, }, + paddingVertical: { paddingVertical: 16 }, }); diff --git a/src/screens/browse/discover/MalTopNovels.tsx b/src/screens/browse/discover/MalTopNovels.tsx index e78c90ff..6fdc19aa 100644 --- a/src/screens/browse/discover/MalTopNovels.tsx +++ b/src/screens/browse/discover/MalTopNovels.tsx @@ -153,7 +153,7 @@ const BrowseMalScreen = ({ navigation }: BrowseMalScreenProps) => { }} ListFooterComponent={ !searchText ? ( - + ) : null @@ -175,4 +175,5 @@ const styles = StyleSheet.create({ paddingBottom: 8, paddingHorizontal: 4, }, + paddingVertical: { paddingVertical: 16 }, }); diff --git a/src/screens/browse/globalsearch/GlobalSearchNovelCover.tsx b/src/screens/browse/globalsearch/GlobalSearchNovelCover.tsx index 4ca995b3..8906e9cc 100644 --- a/src/screens/browse/globalsearch/GlobalSearchNovelCover.tsx +++ b/src/screens/browse/globalsearch/GlobalSearchNovelCover.tsx @@ -23,6 +23,8 @@ const GlobalSearchNovelCover = ({ const uri = cover; + const opacity = inLibrary ? 0.5 : 1; + return ( { /> item.id} renderItem={renderItem} ListHeaderComponent={ListHeaderComponent} @@ -62,6 +62,7 @@ const styles = StyleSheet.create({ container: { flex: 1, }, + paddingBottom: { paddingBottom: 48 }, listHeader: { padding: 20, diff --git a/src/screens/browse/migration/MigrationNovelList.tsx b/src/screens/browse/migration/MigrationNovelList.tsx index ea1cd910..8c133910 100644 --- a/src/screens/browse/migration/MigrationNovelList.tsx +++ b/src/screens/browse/migration/MigrationNovelList.tsx @@ -78,11 +78,12 @@ const MigrationNovelList = ({ renderItem={renderItem} ListEmptyComponent={ {getString('sourceScreen.noResultsFound')} @@ -91,22 +92,18 @@ const MigrationNovelList = ({ {getString('browseScreen.migration.dialogMessage', { url: selectedNovel.name, })} - + - + )} - ListEmptyComponent={() => ( - - )} + ListEmptyComponent={emptyComponent} /> )} - ListEmptyComponent={() => ( - - )} + ListEmptyComponent={emptyComponent} />