From 9c05266c3c324ea883a9e8056df7b80f57bd2129 Mon Sep 17 00:00:00 2001 From: nyagami Date: Sun, 25 Feb 2024 13:30:59 +0700 Subject: [PATCH] Feat: Add features from main and fixes (#972) * fix: download chapters filter * fix: deletechapters missing * fix: tweak chapter * optimise: chapters preprocess * fix: bookmark chapter * fix: not showToast when no network * fix: preprocess updates|history|download * fix: ScrollView for morescreen * feat: exclude download chapter filter * fix: show labels in nav by default --- src/hooks/persisted/useHistory.ts | 19 +- src/hooks/persisted/useNovel.ts | 26 +- src/hooks/persisted/usePlugins.ts | 45 ++- src/hooks/persisted/useSettings.ts | 2 +- src/hooks/persisted/useUpdates.ts | 16 +- src/plugins/pluginManager.ts | 5 +- .../browse/components/PluginSection.tsx | 7 +- .../components/HistoryCard/HistoryCard.tsx | 26 +- src/screens/more/DownloadsScreen.tsx | 17 +- src/screens/more/MoreScreen.tsx | 268 +++++++++--------- src/screens/novel/NovelScreen.tsx | 16 +- src/screens/novel/components/ChapterItem.tsx | 46 ++- .../novel/components/NovelBottomSheet.tsx | 22 +- src/screens/reader/ReaderScreen.tsx | 3 +- .../reader/components/ChapterDrawer.tsx | 23 +- .../reader/components/ReaderAppbar.tsx | 15 +- 16 files changed, 323 insertions(+), 233 deletions(-) diff --git a/src/hooks/persisted/useHistory.ts b/src/hooks/persisted/useHistory.ts index e495ef75..1c4c4f21 100644 --- a/src/hooks/persisted/useHistory.ts +++ b/src/hooks/persisted/useHistory.ts @@ -8,6 +8,8 @@ import { deleteChapterHistory, getHistoryFromDb, } from '@database/queries/HistoryQueries'; +import dayjs from 'dayjs'; +import { parseChapterNumber } from '@utils/parseChapterNumber'; const useHistory = () => { const [isLoading, setIsLoading] = useState(true); @@ -16,7 +18,22 @@ const useHistory = () => { const getHistory = () => getHistoryFromDb() - .then(res => setHistory(res)) + .then(res => + setHistory( + res.map(history => { + const parsedTime = dayjs(history.releaseTime); + return { + ...history, + releaseTime: parsedTime.isValid() + ? parsedTime.format('LL') + : history.releaseTime, + chapterNumber: history.chapterNumber + ? history.chapterNumber + : parseChapterNumber(history.novelName, history.name), + }; + }), + ), + ) .catch((err: Error) => setError(err.message)) .finally(() => setIsLoading(false)); diff --git a/src/hooks/persisted/useNovel.ts b/src/hooks/persisted/useNovel.ts index becbe910..cb00f6bc 100644 --- a/src/hooks/persisted/useNovel.ts +++ b/src/hooks/persisted/useNovel.ts @@ -29,6 +29,8 @@ import { NovelDownloadFolder } from '@utils/constants/download'; import * as RNFS from 'react-native-fs'; import { getString } from '@strings/translations'; import { ChapterItem } from '@plugins/types'; +import dayjs from 'dayjs'; +import { parseChapterNumber } from '@utils/parseChapterNumber'; // store key: '__', // store key: '_', @@ -110,9 +112,29 @@ export const useTrackedNovel = (novelId: number) => { export const useNovel = (novelPath: string, pluginId: string) => { const [loading, setLoading] = useState(true); const [novel, setNovel] = useState(); - const [chapters, setChapters] = useState([]); + const [chapters, _setChapters] = useState([]); const [pages, setPages] = useState([]); - + const setChapters = useCallback( + (chapters: ChapterInfo[]) => { + if (novel) { + _setChapters( + chapters.map(chapter => { + const parsedTime = dayjs(chapter.releaseTime); + return { + ...chapter, + releaseTime: parsedTime.isValid() + ? parsedTime.format('LL') + : chapter.releaseTime, + chapterNumber: chapter.chapterNumber + ? chapter.chapterNumber + : parseChapterNumber(novel.name, chapter.name), + }; + }), + ); + } + }, + [novel, _setChapters], + ); const [pageIndex = defaultPageIndex, setPageIndex] = useMMKVNumber(` ${NOVEL_PAGE_INDEX_PREFIX}_${pluginId}_${novelPath} `); diff --git a/src/hooks/persisted/usePlugins.ts b/src/hooks/persisted/usePlugins.ts index 9d34e8b6..efc18c94 100644 --- a/src/hooks/persisted/usePlugins.ts +++ b/src/hooks/persisted/usePlugins.ts @@ -10,7 +10,6 @@ import { } from '@plugins/pluginManager'; import { newer } from '@utils/compareVersion'; import { MMKVStorage, getMMKVObject, setMMKVObject } from '@utils/mmkv/mmkv'; -import { showToast } from '@utils/showToast'; import { useCallback } from 'react'; import { getString } from '@strings/translations'; @@ -60,31 +59,29 @@ export default function usePlugins() { const refreshPlugins = useCallback(() => { const installedPlugins = getMMKVObject(INSTALLED_PLUGINS) || []; - return fetchPlugins() - .then(fetchedPluginsMap => { - for (const key in fetchedPluginsMap) { - const lang = key as Language; - fetchedPluginsMap[lang] = fetchedPluginsMap[lang].filter(plg => { - const finded = installedPlugins.find(v => v.id === plg.id); - if (finded) { - if (newer(plg.version, finded.version)) { - finded.hasUpdate = true; - finded.iconUrl = plg.iconUrl; - finded.url = plg.url; - if (finded.id === lastUsedPlugin?.id) { - setLastUsedPlugin(finded); - } + return fetchPlugins().then(fetchedPluginsMap => { + for (const key in fetchedPluginsMap) { + const lang = key as Language; + fetchedPluginsMap[lang] = fetchedPluginsMap[lang].filter(plg => { + const finded = installedPlugins.find(v => v.id === plg.id); + if (finded) { + if (newer(plg.version, finded.version)) { + finded.hasUpdate = true; + finded.iconUrl = plg.iconUrl; + finded.url = plg.url; + if (finded.id === lastUsedPlugin?.id) { + setLastUsedPlugin(finded); } - return false; } - return true; - }); - setMMKVObject(INSTALLED_PLUGINS, installedPlugins); - setMMKVObject(AVAILABLE_PLUGINS, fetchedPluginsMap); - filterPlugins(languagesFilter); - } - }) - .catch((error: Error) => showToast(error.message)); + return false; + } + return true; + }); + setMMKVObject(INSTALLED_PLUGINS, installedPlugins); + setMMKVObject(AVAILABLE_PLUGINS, fetchedPluginsMap); + filterPlugins(languagesFilter); + } + }); }, [languagesFilter]); const toggleLanguageFilter = (lang: Language) => { diff --git a/src/hooks/persisted/useSettings.ts b/src/hooks/persisted/useSettings.ts index b6f1ce46..2ae2a80d 100644 --- a/src/hooks/persisted/useSettings.ts +++ b/src/hooks/persisted/useSettings.ts @@ -115,7 +115,7 @@ const initialAppSettings: AppSettings = { showHistoryTab: true, showUpdatesTab: true, - showLabelsInNav: false, + showLabelsInNav: true, useFabForContinueReading: false, /** diff --git a/src/hooks/persisted/useUpdates.ts b/src/hooks/persisted/useUpdates.ts index e4864947..5ec1f1c6 100644 --- a/src/hooks/persisted/useUpdates.ts +++ b/src/hooks/persisted/useUpdates.ts @@ -4,6 +4,7 @@ import { getUpdatesFromDb } from '@database/queries/ChapterQueries'; import { Update } from '@database/types'; import { useMMKVBoolean, useMMKVString } from 'react-native-mmkv'; import dayjs from 'dayjs'; +import { parseChapterNumber } from '@utils/parseChapterNumber'; export const SHOW_LAST_UPDATE_TIME = 'SHOW_LAST_UPDATE_TIME'; export const LAST_UPDATE_TIME = 'LAST_UPDATE_TIME'; @@ -33,7 +34,20 @@ export const useUpdates = () => { const getUpdates = () => getUpdatesFromDb() .then(res => { - setUpdates(res); + setUpdates( + res.map(update => { + const parsedTime = dayjs(update.releaseTime); + return { + ...update, + releaseTime: parsedTime.isValid() + ? parsedTime.format('LL') + : update.releaseTime, + chapterNumber: update.chapterNumber + ? update.chapterNumber + : parseChapterNumber(update.novelName, update.name), + }; + }), + ); if (res.length) { if ( !lastUpdateTime || diff --git a/src/plugins/pluginManager.ts b/src/plugins/pluginManager.ts index f414d780..528cea8b 100644 --- a/src/plugins/pluginManager.ts +++ b/src/plugins/pluginManager.ts @@ -123,15 +123,14 @@ const updatePlugin = async (plugin: PluginItem) => { return installPlugin(plugin.url); }; -const fetchPlugins = async () => { +const fetchPlugins = (): Promise>> => { // plugins host const githubUsername = 'LNReader'; const githubRepository = 'lnreader-sources'; - const availablePlugins: Record> = await fetch( + return fetch( `https://raw.githubusercontent.com/${githubUsername}/${githubRepository}/beta-dist/.dist/plugins.min.json`, ).then(res => res.json()); - return availablePlugins; }; const getPlugin = (pluginId: string) => plugins[pluginId]; diff --git a/src/screens/browse/components/PluginSection.tsx b/src/screens/browse/components/PluginSection.tsx index c3760226..27a68358 100644 --- a/src/screens/browse/components/PluginSection.tsx +++ b/src/screens/browse/components/PluginSection.tsx @@ -7,6 +7,7 @@ import PluginCard from './PluginCard'; import { usePlugins } from '@hooks/persisted'; import { BrowseScreenProps } from '@navigators/types'; import TrackerCard from '../discover/TrackerCard'; +import { showToast } from '@utils/showToast'; interface PluginSectionProps { sections: { header: string; data: PluginItem[] }[]; @@ -108,7 +109,11 @@ const PluginSection = ({ refreshing={refreshing} onRefresh={() => { setRefreshing(true); - refreshPlugins().finally(() => setRefreshing(false)); + refreshPlugins() + .finally(() => setRefreshing(false)) + .catch(e => { + showToast(e); + }); }} colors={[theme.onPrimary]} progressBackgroundColor={theme.primary} diff --git a/src/screens/history/components/HistoryCard/HistoryCard.tsx b/src/screens/history/components/HistoryCard/HistoryCard.tsx index 03a7cc07..97d2d719 100644 --- a/src/screens/history/components/HistoryCard/HistoryCard.tsx +++ b/src/screens/history/components/HistoryCard/HistoryCard.tsx @@ -1,11 +1,10 @@ -import React, { useMemo } from 'react'; +import React from 'react'; import { Pressable, StyleSheet, Text, View } from 'react-native'; import dayjs from 'dayjs'; import { Image } from 'react-native'; import { IconButtonV2 } from '@components'; -import { parseChapterNumber } from '@utils/parseChapterNumber'; import { History, NovelInfo } from '@database/types'; import { ThemeColors } from '@theme/types'; @@ -26,20 +25,6 @@ const HistoryCard: React.FC = ({ handleRemoveFromHistory, theme, }) => { - const chapterNoAndTime = useMemo( - () => - `${getString('historyScreen.chapter')} ${parseChapterNumber( - history.novelName, - history.name, - )} • ${dayjs(history.readTime).format('LT').toUpperCase()}` + - `${ - history.progress && history.progress > 0 - ? ' • ' + history.progress + '%' - : '' - }`, - [history, history.readTime], - ); - return ( = ({ {history.novelName} - {chapterNoAndTime} + {`${getString('historyScreen.chapter')} ${ + history.chapterNumber + } • ${dayjs(history.readTime).format('LT').toUpperCase()}` + + `${ + history.progress && history.progress > 0 + ? ' • ' + history.progress + '%' + : '' + }`} diff --git a/src/screens/more/DownloadsScreen.tsx b/src/screens/more/DownloadsScreen.tsx index a0cb8890..eea15f90 100644 --- a/src/screens/more/DownloadsScreen.tsx +++ b/src/screens/more/DownloadsScreen.tsx @@ -21,6 +21,8 @@ import { getString } from '@strings/translations'; import { DownloadsScreenProps } from '@navigators/types'; import { DownloadedChapter } from '@database/types'; import { showToast } from '@utils/showToast'; +import dayjs from 'dayjs'; +import { parseChapterNumber } from '@utils/parseChapterNumber'; type DownloadGroup = Record; @@ -53,7 +55,20 @@ const Downloads = ({ navigation }: DownloadsScreenProps) => { const getChapters = async () => { const res = await getDownloadedChapters(); - setChapters(res); + setChapters( + res.map(download => { + const parsedTime = dayjs(download.releaseTime); + return { + ...download, + releaseTime: parsedTime.isValid() + ? parsedTime.format('LL') + : download.releaseTime, + chapterNumber: download.chapterNumber + ? download.chapterNumber + : parseChapterNumber(download.novelName, download.name), + }; + }), + ); setLoading(false); }; diff --git a/src/screens/more/MoreScreen.tsx b/src/screens/more/MoreScreen.tsx index 8e54bd92..67e31a43 100644 --- a/src/screens/more/MoreScreen.tsx +++ b/src/screens/more/MoreScreen.tsx @@ -30,149 +30,147 @@ const MoreScreen = ({ navigation }: MoreStackScreenProps) => { setLibrarySettings({ incognitoMode: !incognitoMode }); return ( - <> + - - - - - - - - {getString('moreScreen.downloadOnly')} - - - {getString('moreScreen.downloadOnlyDesc')} - - + + + + + + + {getString('moreScreen.downloadOnly')} + + + {getString('moreScreen.downloadOnlyDesc')} + - - - - - - - - {getString('moreScreen.incognitoMode')} - - - {getString('moreScreen.incognitoModeDesc')} - - + + + + + + + + + {getString('moreScreen.incognitoMode')} + + + {getString('moreScreen.incognitoModeDesc')} + - - - - 0 ? queue.length + ' remaining' : ''} - icon="progress-download" - onPress={() => - navigation.navigate('MoreStack', { - screen: 'DownloadQueue', - }) - } - theme={theme} + + - - navigation.navigate('MoreStack', { - screen: 'Downloads', - }) - } - theme={theme} - /> - - navigation.navigate('MoreStack', { - screen: 'Categories', - }) - } - theme={theme} - /> - - navigation.navigate('MoreStack', { - screen: 'Statistics', - }) - } - theme={theme} - /> - - - navigation.navigate('MoreStack', { - screen: 'SettingsStack', - params: { - screen: 'Settings', - }, - }) - } - theme={theme} - /> - - navigation.navigate('MoreStack', { - screen: 'About', - }) - } - theme={theme} - /> - - - + + + 0 ? queue.length + ' remaining' : ''} + icon="progress-download" + onPress={() => + navigation.navigate('MoreStack', { + screen: 'DownloadQueue', + }) + } + theme={theme} + /> + + navigation.navigate('MoreStack', { + screen: 'Downloads', + }) + } + theme={theme} + /> + + navigation.navigate('MoreStack', { + screen: 'Categories', + }) + } + theme={theme} + /> + + navigation.navigate('MoreStack', { + screen: 'Statistics', + }) + } + theme={theme} + /> + + + navigation.navigate('MoreStack', { + screen: 'SettingsStack', + params: { + screen: 'Settings', + }, + }) + } + theme={theme} + /> + + navigation.navigate('MoreStack', { + screen: 'About', + }) + } + theme={theme} + /> + + ); }; diff --git a/src/screens/novel/NovelScreen.tsx b/src/screens/novel/NovelScreen.tsx index eaa3539d..d5144626 100644 --- a/src/screens/novel/NovelScreen.tsx +++ b/src/screens/novel/NovelScreen.tsx @@ -158,7 +158,10 @@ const Novel = ({ route, navigation }: NovelScreenProps) => { icon: 'download-outline', onPress: () => { if (novel) { - downloadChapters(novel, selected); + downloadChapters( + novel, + selected.filter(chapter => !chapter.isDownloaded), + ); } setSelected([]); }, @@ -168,6 +171,7 @@ const Novel = ({ route, navigation }: NovelScreenProps) => { list.push({ icon: 'trash-can-outline', onPress: () => { + deleteChapters(selected.filter(chapter => chapter.isDownloaded)); setSelected([]); }, }); @@ -454,7 +458,10 @@ const Novel = ({ route, navigation }: NovelScreenProps) => { if (novel) { downloadChapters( novel, - chapters.filter(chapter => chapter.unread), + chapters.filter( + chapter => + chapter.unread && !chapter.isDownloaded, + ), ); } }} @@ -467,7 +474,10 @@ const Novel = ({ route, navigation }: NovelScreenProps) => { }} onPress={() => { if (novel) { - downloadChapters(novel, chapters); + downloadChapters( + novel, + chapters.filter(chapter => !chapter.isDownloaded), + ); } showDownloadMenu(false); }} diff --git a/src/screens/novel/components/ChapterItem.tsx b/src/screens/novel/components/ChapterItem.tsx index f5499f0a..6f00e4ee 100644 --- a/src/screens/novel/components/ChapterItem.tsx +++ b/src/screens/novel/components/ChapterItem.tsx @@ -1,20 +1,15 @@ -import React, { memo, ReactNode, useState } from 'react'; +import React, { memo, ReactNode } from 'react'; import { View, Text, StyleSheet, Pressable } from 'react-native'; import color from 'color'; - -import { Row } from '@components/Common'; - import { ChapterBookmarkButton, DownloadButton, } from './Chapter/ChapterDownloadButtons'; -import { parseChapterNumber } from '@utils/parseChapterNumber'; - import { ThemeColors } from '@theme/types'; import { ChapterInfo } from '@database/types'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; -import dayjs from 'dayjs'; import { getString } from '@strings/translations'; +import { useBoolean } from '@hooks'; interface ChapterItemProps { isDownloading?: boolean; @@ -51,14 +46,11 @@ const ChapterItem: React.FC = ({ }) => { const { id, name, unread, releaseTime, bookmark, chapterNumber, progress } = chapter; - const [deleteChapterMenuVisible, setDeleteChapterMenuVisible] = - useState(false); - const showDeleteChapterMenu = () => setDeleteChapterMenuVisible(true); - const hideDeleteChapterMenu = () => setDeleteChapterMenuVisible(false); - const chapNum = chapterNumber - ? chapterNumber - : parseChapterNumber(novelName, name); - const parsedTime = dayjs(releaseTime); + const { + value: isMenuVisible, + setTrue: showMenu, + setFalse: hideMenu, + } = useBoolean(); return ( = ({ onLongPress={() => onSelectLongPress?.(chapter)} android_ripple={{ color: theme.rippleColor }} > - + {left} - {!!bookmark && } + {bookmark ? : null} {isUpdateCard && ( = ({ {novelName} )} - + {unread ? ( = ({ > {showChapterTitles ? name - : getString('novelScreen.chapterChapnum', { num: chapNum })} + : getString('novelScreen.chapterChapnum', { + num: chapterNumber, + })} - + {releaseTime && !isUpdateCard ? ( = ({ ]} numberOfLines={1} > - {parsedTime.isValid() ? parsedTime.format('LL') : releaseTime} + {releaseTime} ) : null} {!isUpdateCard && progress && progress > 0 && chapter.unread ? ( @@ -152,7 +146,7 @@ const ChapterItem: React.FC = ({ ) : null} - + {!isLocal && ( = ({ theme={theme} deleteChapter={deleteChapter} downloadChapter={downloadChapter} - hideDeleteChapterMenu={hideDeleteChapterMenu} - showDeleteChapterMenu={showDeleteChapterMenu} - deleteChapterMenuVisible={deleteChapterMenuVisible} + hideDeleteChapterMenu={hideMenu} + showDeleteChapterMenu={showMenu} + deleteChapterMenuVisible={isMenuVisible} /> )} @@ -187,7 +181,7 @@ const styles = StyleSheet.create({ flexDirection: 'row', marginTop: 5, }, - row: { flex: 1, overflow: 'hidden' }, + row: { flex: 1, flexDirection: 'row', alignItems: 'center' }, unreadIcon: { marginRight: 4, }, diff --git a/src/screens/novel/components/NovelBottomSheet.tsx b/src/screens/novel/components/NovelBottomSheet.tsx index 0c184d50..9a235794 100644 --- a/src/screens/novel/components/NovelBottomSheet.tsx +++ b/src/screens/novel/components/NovelBottomSheet.tsx @@ -41,12 +41,24 @@ const ChaptersSettingsSheet = ({ - filter.match('AND isDownloaded = 1') - ? filterChapters(filter.replace(' AND isDownloaded = 1', '')) - : filterChapters(filter + ' AND isDownloaded = 1') + status={ + filter.match('AND isDownloaded=1') + ? true + : filter.match('AND isDownloaded=0') + ? 'indeterminate' + : false } + onPress={() => { + if (filter.match('AND isDownloaded=1')) { + filterChapters( + filter.replace(' AND isDownloaded=1', ' AND isDownloaded=0'), + ); + } else if (filter.match('AND isDownloaded=0')) { + filterChapters(filter.replace(' AND isDownloaded=0', '')); + } else { + filterChapters(filter + ' AND isDownloaded=1'); + } + }} /> diff --git a/src/screens/reader/components/ChapterDrawer.tsx b/src/screens/reader/components/ChapterDrawer.tsx index 72c5238c..4f64825e 100644 --- a/src/screens/reader/components/ChapterDrawer.tsx +++ b/src/screens/reader/components/ChapterDrawer.tsx @@ -10,7 +10,6 @@ import { getString } from '@strings/translations'; import { ChapterScreenProps } from '@navigators/types'; import { ChapterInfo } from '@database/types'; import { ThemeColors } from '@theme/types'; -import dayjs from 'dayjs'; import { NovelSettings } from '@hooks/persisted/useNovel'; type ChapterDrawerProps = ChapterScreenProps & { @@ -73,7 +72,6 @@ const ChapterDrawer = ({ }); }; const renderItem: FlashListProps['renderItem'] = ({ item }) => { - const parsedTime = dayjs(item.releaseTime); return ( changeChapter(item)} style={styles.chapterCtn} > - + {item.name} - - {parsedTime.isValid() ? parsedTime.format('LL') : item.readTime} - + {item.releaseTime ? ( + + {item.releaseTime} + + ) : null} ); diff --git a/src/screens/reader/components/ReaderAppbar.tsx b/src/screens/reader/components/ReaderAppbar.tsx index 410982f9..a36086b5 100644 --- a/src/screens/reader/components/ReaderAppbar.tsx +++ b/src/screens/reader/components/ReaderAppbar.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import { StyleSheet, View } from 'react-native'; import color from 'color'; @@ -11,9 +11,10 @@ import { bookmarkChapter } from '@database/queries/ChapterQueries'; interface ReaderAppbarProps { novelName: string; - chapter: ChapterInfo; + chapter: ChapterInfo & { chapterText: string }; theme: ThemeColors; goBack: () => void; + setChapter: (chapter: ChapterInfo & { chapterText: string }) => void; } const ReaderAppbar = ({ @@ -21,9 +22,8 @@ const ReaderAppbar = ({ chapter, goBack, theme, + setChapter, }: ReaderAppbarProps) => { - const [bookmarked, setBookmarked] = useState(chapter.bookmark); - return ( { bookmarkChapter(chapter.id).then(() => - setBookmarked(!bookmarked), + setChapter({ + ...chapter, + bookmark: !chapter.bookmark, + }), ); }} color={theme.onSurface}