diff --git a/src/components/FlashSectionList.tsx b/src/components/FlashSectionList.tsx new file mode 100644 index 0000000000..d68fa33c9e --- /dev/null +++ b/src/components/FlashSectionList.tsx @@ -0,0 +1,62 @@ +import { FlashList, FlashListProps } from '@shopify/flash-list'; +import { ReactElement, RefObject, useMemo } from 'react'; + +type FLashSectionListProps = FlashListProps & { + sectionField: keyof T; + /** + * @param item The first item of section + */ + renderSectionHeader: (item: T) => ReactElement; + listRef?: RefObject>; +}; + +/** + * @param data should be sorted array by `sectionField` + * @param sectionField the field for grouping + * @param renderSectionHeader render the header + * You should use index in keyExtractor because the header item is a copy of section's first item + */ + +const FLashSectionList = (props: FLashSectionListProps) => { + const { + data, + sectionField, + renderItem, + renderSectionHeader, + listRef, + ...otherProps + } = props; + const _data = useMemo(() => { + return data?.reduce((prev, cur) => { + if ( + prev.length === 0 || + cur[props.sectionField] != prev[prev.length - 1][sectionField] + ) { + prev.push({ ...cur, _type: 'header' }); + } + prev.push(cur); + return prev; + }, [] as T[]); + }, [data, props.extraData]); + + return ( + { + // @ts-ignore + switch (renderProps.item._type) { + case 'header': + return renderSectionHeader(renderProps.item); + default: + return renderItem?.(renderProps) || null; + } + }} + // @ts-ignore + getItemType={item => item._type} + /> + ); +}; + +export default FLashSectionList; diff --git a/src/database/queries/ChapterQueries.ts b/src/database/queries/ChapterQueries.ts index 7c10a7b1e8..38160351f6 100644 --- a/src/database/queries/ChapterQueries.ts +++ b/src/database/queries/ChapterQueries.ts @@ -81,7 +81,7 @@ export const getNovelChapters = (novelId: number): Promise => { return new Promise(resolve => db.transaction(tx => { tx.executeSql( - 'SELECT * FROM Chapter WHERE novelId = ?', + 'SELECT * FROM Chapter WHERE novelId = ? ORDER BY CAST (page as INTEGER)', [novelId], (txObj, { rows }) => resolve((rows as any)._array), txnErrorCallback, diff --git a/src/screens/browse/components/BrowseTabs.tsx b/src/screens/browse/components/BrowseTabs.tsx index 681575d3d5..079d476f8f 100644 --- a/src/screens/browse/components/BrowseTabs.tsx +++ b/src/screens/browse/components/BrowseTabs.tsx @@ -28,6 +28,7 @@ import Animated, { useSharedValue, withTiming, } from 'react-native-reanimated'; +import FLashSectionList from '@components/FlashSectionList'; interface AvailableTabProps { searchText: string; @@ -245,7 +246,7 @@ export const InstalledTab = memo( ); interface AvailablePluginCardProps { - plugin: PluginItem & { header: boolean }; + plugin: PluginItem; theme: ThemeColors; installPlugin: (plugin: PluginItem) => Promise; } @@ -267,76 +268,61 @@ const AvailablePluginCard = ({ lineHeight: ratio.value * 20, })); return ( - - {plugin.header ? ( - - {plugin.lang} - - ) : null} - - - + + + + - - - {plugin.name} - - - {`${plugin.lang} - ${plugin.version}`} - - + > + {plugin.name} + + + {`${plugin.lang} - ${plugin.version}`} + - { - ratio.value = withTiming(0, { duration: 500 }); - installPlugin(plugin) - .then(() => { - showToast( - getString('browseScreen.installedPlugin', { - name: plugin.name, - }), - ); - }) - .catch((error: Error) => { - showToast(error.message); - ratio.value = 1; - }); - }} - size={22} - theme={theme} - /> - + { + ratio.value = withTiming(0, { duration: 500 }); + installPlugin(plugin) + .then(() => { + showToast( + getString('browseScreen.installedPlugin', { + name: plugin.name, + }), + ); + }) + .catch((error: Error) => { + showToast(error.message); + ratio.value = 1; + }); + }} + size={22} + theme={theme} + /> + ); }; @@ -357,42 +343,36 @@ export const AvailableTab = memo(({ searchText, theme }: AvailableTabProps) => { plg.id.includes(lowerCaseSearchText), ); } - let previousLang: string | null = null; - return res - .sort((a, b) => a.lang.localeCompare(b.lang)) - .map(plg => { - if (plg.lang !== previousLang) { - previousLang = plg.lang; - return { ...plg, header: true }; - } else { - return { ...plg, header: false }; - } - }); + return res.sort((a, b) => a.lang.localeCompare(b.lang)); }, [searchText, filteredAvailablePlugins]); - const renderItem: FlashListRenderItem = - useCallback( - ({ item }) => { - return ( - - ); - }, - [theme, searchedPlugins], - ); + const renderItem: FlashListRenderItem = useCallback( + ({ item }) => { + return ( + + ); + }, + [theme, searchedPlugins], + ); return ( - ( + + {item.lang} + + )} removeClippedSubviews={true} - showsVerticalScrollIndicator={false} - keyExtractor={item => item.id + '_available'} + keyExtractor={(item, index) => item.id + '_available_' + index} refreshControl={ { setChapter, setLoading, } = useChapterContext(); - const { chapters, novelSettings, pages, setPageIndex } = useNovel( - novelItem.path, - novelItem.pluginId, - ); + const [chapters, setChapters] = useState([]); + // const { chapters, novelSettings, pages, setPageIndex } = useNovel( + // novelItem.path, + // novelItem.pluginId, + // ); const theme = useTheme(); const insets = useSafeAreaInsets(); - const { defaultChapterSort } = useAppSettings(); + // const { defaultChapterSort } = useAppSettings(); const listRef = useRef>(null); const styles = createStylesheet(theme, insets); - const { sort = defaultChapterSort } = novelSettings; - const listAscending = sort === 'ORDER BY position ASC'; - + // const { sort = defaultChapterSort } = novelSettings; + // const listAscending = sort === 'ORDER BY position ASC'; + useEffect(() => { + getNovelChapters(novelItem.id).then(setChapters); + }); + const listAscending = true; const defaultButtonLayout: ButtonsProperties = { up: { text: getString('readerScreen.drawer.scrollToTop'), @@ -60,14 +65,6 @@ const ChapterDrawer = () => { }, }; - useEffect(() => { - let pageIndex = pages.indexOf(chapter.page); - if (pageIndex === -1) { - pageIndex = 0; - } - setPageIndex(pageIndex); - }, [chapter, pages, setPageIndex]); - const scrollToIndex = useMemo(() => { if (chapters.length < 1) { return; @@ -135,11 +132,17 @@ const ChapterDrawer = () => { {scrollToIndex === undefined ? ( ) : ( - ( + + {item.page} + + )} renderItem={val => renderListChapter({ item: val.item,