Fix: Chapter loading for jump to chapter modal and chapter drawer
* updated debs * Jump to chapter now loads not preloaded chapters * chapter drawer scrolls to current chapter * Load new chapters when reaching end of chapter drawer * fix JumpToChapterModal rebase * fix Chapter drawer current chapter position detection --------- Co-authored-by: CD-Z <github.verify097@passmail.com>
This commit is contained in:
Generated
+5135
-1811
File diff suppressed because it is too large
Load Diff
@@ -293,6 +293,25 @@ export const getPageChaptersBatched = (
|
||||
);
|
||||
};
|
||||
|
||||
export const getNovelChaptersByNumber = (
|
||||
novelId: number,
|
||||
chapterNumber: number,
|
||||
) => {
|
||||
return db.getAllAsync<ChapterInfo>(
|
||||
'SELECT * FROM Chapter WHERE novelId = ? AND position = ?',
|
||||
novelId,
|
||||
chapterNumber - 1,
|
||||
);
|
||||
};
|
||||
|
||||
export const getNovelChaptersByName = (novelId: number, searchText: string) => {
|
||||
return db.getAllAsync<ChapterInfo>(
|
||||
'SELECT * FROM Chapter WHERE novelId = ? AND name LIKE ?',
|
||||
novelId,
|
||||
`%${searchText}%`,
|
||||
);
|
||||
};
|
||||
|
||||
export const getPrevChapter = (
|
||||
novelId: number,
|
||||
chapterPosition: number,
|
||||
|
||||
@@ -322,6 +322,46 @@ export const useNovel = (novelOrPath: string | NovelInfo, pluginId: string) => {
|
||||
settingsSort,
|
||||
]);
|
||||
|
||||
const loadUpToBatch = useCallback(
|
||||
async (targetBatch: number) => {
|
||||
const page = pages[pageIndex];
|
||||
if (!novel || !page || targetBatch <= batchInformation.batch) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load all batches from current + 1 up to targetBatch
|
||||
for (let batch = batchInformation.batch + 1; batch <= targetBatch; batch++) {
|
||||
if (batch > batchInformation.total) break;
|
||||
|
||||
let newChapters: ChapterInfo[] = [];
|
||||
try {
|
||||
newChapters =
|
||||
getPageChaptersBatched(
|
||||
novel.id,
|
||||
settingsSort,
|
||||
novelSettings.filter,
|
||||
page,
|
||||
batch,
|
||||
) || [];
|
||||
} catch (error) {
|
||||
console.error('Error loading batch', batch, error);
|
||||
}
|
||||
|
||||
setBatchInformation(prev => ({ ...prev, batch }));
|
||||
extendChapters(newChapters);
|
||||
}
|
||||
},
|
||||
[
|
||||
batchInformation,
|
||||
extendChapters,
|
||||
novel,
|
||||
novelSettings.filter,
|
||||
pageIndex,
|
||||
pages,
|
||||
settingsSort,
|
||||
],
|
||||
);
|
||||
|
||||
// #endregion
|
||||
// #region Mark chapters
|
||||
|
||||
@@ -566,6 +606,7 @@ export const useNovel = (novelOrPath: string | NovelInfo, pluginId: string) => {
|
||||
novelSettings,
|
||||
batchInformation,
|
||||
getNextChapterBatch,
|
||||
loadUpToBatch,
|
||||
getNovel,
|
||||
setPageIndex,
|
||||
openPage,
|
||||
@@ -597,6 +638,7 @@ export const useNovel = (novelOrPath: string | NovelInfo, pluginId: string) => {
|
||||
novelSettings,
|
||||
batchInformation,
|
||||
getNextChapterBatch,
|
||||
loadUpToBatch,
|
||||
getNovel,
|
||||
setPageIndex,
|
||||
openPage,
|
||||
|
||||
@@ -40,6 +40,7 @@ const Novel = ({ route, navigation }: NovelScreenProps) => {
|
||||
fetching,
|
||||
batchInformation,
|
||||
getNextChapterBatch,
|
||||
loadUpToBatch,
|
||||
setNovel,
|
||||
bookmarkChapters,
|
||||
markChaptersRead,
|
||||
@@ -321,6 +322,9 @@ const Novel = ({ route, navigation }: NovelScreenProps) => {
|
||||
novel={novel}
|
||||
chapterListRef={chapterListRef}
|
||||
navigation={navigation}
|
||||
loadUpToBatch={loadUpToBatch}
|
||||
totalChapters={batchInformation.totalChapters}
|
||||
chapters={chapters}
|
||||
/>
|
||||
<EditInfoModal
|
||||
modalVisible={editInfoModal}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
@@ -12,32 +12,42 @@ import { Portal, Text } from 'react-native-paper';
|
||||
import { useTheme } from '@hooks/persisted';
|
||||
import { ChapterInfo, NovelInfo } from '@database/types';
|
||||
import { NovelScreenProps } from '@navigators/types';
|
||||
import {
|
||||
getNovelChaptersByNumber,
|
||||
getNovelChaptersByName,
|
||||
} from '@database/queries/ChapterQueries';
|
||||
import {
|
||||
LegendList,
|
||||
LegendListRef,
|
||||
LegendListRenderItemProps,
|
||||
} from '@legendapp/list';
|
||||
import { getNovelChapters } from '@database/queries/ChapterQueries';
|
||||
|
||||
interface JumpToChapterModalProps {
|
||||
hideModal: () => void;
|
||||
modalVisible: boolean;
|
||||
navigation: NovelScreenProps['navigation'];
|
||||
novel: NovelInfo;
|
||||
chapters: ChapterInfo[];
|
||||
chapterListRef: React.RefObject<LegendListRef | null>;
|
||||
loadUpToBatch: (batch: number) => Promise<void>;
|
||||
totalChapters?: number;
|
||||
}
|
||||
|
||||
const JumpToChapterModal = ({
|
||||
hideModal,
|
||||
modalVisible,
|
||||
chapters: loadedChapters,
|
||||
navigation,
|
||||
novel,
|
||||
chapterListRef,
|
||||
loadUpToBatch,
|
||||
totalChapters,
|
||||
}: JumpToChapterModalProps) => {
|
||||
const minNumber = 1;
|
||||
const maxNumber = totalChapters ?? -1;
|
||||
const theme = useTheme();
|
||||
const [mode, setMode] = useState(false);
|
||||
const [openChapter, setOpenChapter] = useState(false);
|
||||
const [allChapters, setAllChapters] = useState<ChapterInfo[]>([]);
|
||||
|
||||
const [text, setText] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
@@ -46,30 +56,6 @@ const JumpToChapterModal = ({
|
||||
const inputRef = useRef<RNTextInput>(null);
|
||||
const [inputFocused, setInputFocused] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (modalVisible && novel?.id) {
|
||||
getNovelChapters(novel.id).then(chapters => {
|
||||
setAllChapters(chapters);
|
||||
});
|
||||
}
|
||||
}, [modalVisible, novel?.id]);
|
||||
|
||||
const minNumber = useMemo(() => {
|
||||
if (allChapters.length === 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return Math.min(...allChapters.map(c => c.chapterNumber || -1));
|
||||
}, [allChapters]);
|
||||
|
||||
const maxNumber = useMemo(() => {
|
||||
if (allChapters.length === 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return Math.max(...allChapters.map(c => c.chapterNumber || -1));
|
||||
}, [allChapters]);
|
||||
|
||||
const onDismiss = () => {
|
||||
hideModal();
|
||||
setText('');
|
||||
@@ -87,22 +73,28 @@ const JumpToChapterModal = ({
|
||||
});
|
||||
};
|
||||
|
||||
const scrollToChapter = (chap: ChapterInfo) => {
|
||||
const scrollToChapter = async (chap: ChapterInfo) => {
|
||||
onDismiss();
|
||||
const index = allChapters.findIndex(c => c.id === chap.id);
|
||||
|
||||
if (index !== -1) {
|
||||
const loadedIndex = loadedChapters.findIndex(c => c.id === chap.id);
|
||||
if (loadedIndex >= 0) {
|
||||
chapterListRef.current?.scrollToIndex({
|
||||
animated: true,
|
||||
index: index,
|
||||
viewPosition: 0.5,
|
||||
});
|
||||
} else {
|
||||
chapterListRef.current?.scrollToItem({
|
||||
animated: true,
|
||||
item: chap,
|
||||
index: loadedIndex,
|
||||
viewPosition: 0.5,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if ((chap.position ?? -1) >= 0) {
|
||||
const targetBatch = Math.floor(chap.position! / 300);
|
||||
await loadUpToBatch(targetBatch);
|
||||
setTimeout(() => {
|
||||
chapterListRef.current?.scrollToIndex({
|
||||
animated: true,
|
||||
index: chap.position!,
|
||||
viewPosition: 0.5,
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -136,46 +128,48 @@ const JumpToChapterModal = ({
|
||||
);
|
||||
};
|
||||
|
||||
const onSubmit = () => {
|
||||
const onSubmit = async () => {
|
||||
if (!mode) {
|
||||
// Number search
|
||||
const num = Number(text);
|
||||
if (num && num >= minNumber && num <= maxNumber) {
|
||||
if (openChapter) {
|
||||
const chapter = allChapters.find(c => c.chapterNumber === num);
|
||||
if (chapter) {
|
||||
const chapters = await getNovelChaptersByNumber(novel!.id, num);
|
||||
if (chapters.length > 0) {
|
||||
const chapter = chapters[0];
|
||||
if (openChapter) {
|
||||
return navigateToChapter(chapter);
|
||||
}
|
||||
} else {
|
||||
const chapter = allChapters.find(c => c.chapterNumber === num);
|
||||
if (chapter) {
|
||||
} else {
|
||||
return scrollToChapter(chapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return setError(
|
||||
getString('novelScreen.jumpToChapterModal.error.validChapterNumber') +
|
||||
` (${num < minNumber ? '≥ ' + minNumber : '≤ ' + maxNumber})`,
|
||||
);
|
||||
} else {
|
||||
const searchedChapters = allChapters.filter(chap =>
|
||||
chap.name.toLowerCase().includes(text?.toLowerCase()),
|
||||
// Text search
|
||||
const chapters = await getNovelChaptersByName(
|
||||
novel!.id,
|
||||
text.toLowerCase(),
|
||||
);
|
||||
|
||||
if (!searchedChapters.length) {
|
||||
if (!chapters.length) {
|
||||
setError(
|
||||
getString('novelScreen.jumpToChapterModal.error.validChapterName'),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchedChapters.length === 1) {
|
||||
if (chapters.length === 1) {
|
||||
if (openChapter) {
|
||||
return navigateToChapter(searchedChapters[0]);
|
||||
return navigateToChapter(chapters[0]);
|
||||
} else {
|
||||
return scrollToChapter(chapters[0]);
|
||||
}
|
||||
return scrollToChapter(searchedChapters[0]);
|
||||
}
|
||||
|
||||
return setResult(searchedChapters);
|
||||
return setResult(chapters);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import renderListChapter from './RenderListChapter';
|
||||
import { useChapterContext } from '@screens/reader/ChapterContext';
|
||||
import { useNovelContext } from '@screens/novel/NovelContext';
|
||||
import { LegendList, LegendListRef, ViewToken } from '@legendapp/list';
|
||||
import { noop } from 'lodash-es';
|
||||
|
||||
type ButtonProperties = {
|
||||
text: string;
|
||||
@@ -29,7 +30,15 @@ type ButtonsProperties = {
|
||||
|
||||
const ChapterDrawer = () => {
|
||||
const { chapter, getChapter, setLoading } = useChapterContext();
|
||||
const { chapters, novelSettings, pages, setPageIndex } = useNovelContext();
|
||||
const {
|
||||
chapters,
|
||||
novelSettings,
|
||||
pages,
|
||||
fetching,
|
||||
batchInformation,
|
||||
getNextChapterBatch,
|
||||
setPageIndex,
|
||||
} = useNovelContext();
|
||||
const theme = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { defaultChapterSort } = useAppSettings();
|
||||
@@ -89,9 +98,9 @@ const ChapterDrawer = () => {
|
||||
const newBtnLayout = Object.create(defaultButtonLayout);
|
||||
|
||||
if (viewableItems.length === 0) return;
|
||||
const cKey = (scrollToIndex.current ?? 0) + 2;
|
||||
const vKey = parseInt(viewableItems[0].key, 10);
|
||||
const visible = vKey <= cKey && cKey <= vKey + viewableItems.length - 1;
|
||||
const visible = viewableItems
|
||||
.map(v => v.index)
|
||||
.includes((scrollToIndex.current ?? 0) + 2);
|
||||
|
||||
if (!visible && scrollToIndex.current !== undefined) {
|
||||
if (
|
||||
@@ -110,12 +119,7 @@ const ChapterDrawer = () => {
|
||||
};
|
||||
}
|
||||
}
|
||||
if (cKey <= 2 && vKey <= 4) {
|
||||
newBtnLayout.up = {
|
||||
text: curChapter,
|
||||
index: scrollToIndex.current,
|
||||
};
|
||||
}
|
||||
|
||||
setButtonProperties(newBtnLayout);
|
||||
},
|
||||
[defaultButtonLayout, listAscending],
|
||||
@@ -136,7 +140,10 @@ const ChapterDrawer = () => {
|
||||
useEffect(() => {
|
||||
const next = calculateScrollToIndex();
|
||||
if (next !== undefined) {
|
||||
if (scrollToIndex.current === undefined) {
|
||||
if (
|
||||
scrollToIndex.current === undefined ||
|
||||
next !== scrollToIndex.current
|
||||
) {
|
||||
scroll(next);
|
||||
}
|
||||
scrollToIndex.current = next;
|
||||
@@ -154,7 +161,7 @@ const ChapterDrawer = () => {
|
||||
recycleItems
|
||||
viewabilityConfig={{
|
||||
minimumViewTime: 100,
|
||||
viewAreaCoveragePercentThreshold: 95,
|
||||
itemVisiblePercentThreshold: 90,
|
||||
}}
|
||||
onViewableItemsChanged={checkViewableItems}
|
||||
data={chapters}
|
||||
@@ -176,6 +183,12 @@ const ChapterDrawer = () => {
|
||||
}
|
||||
estimatedItemSize={60}
|
||||
initialScrollIndex={scrollToIndex.current}
|
||||
onEndReached={
|
||||
batchInformation.batch < batchInformation.total && !fetching
|
||||
? getNextChapterBatch
|
||||
: noop
|
||||
}
|
||||
onEndReachedThreshold={6}
|
||||
/>
|
||||
)}
|
||||
<View style={styles.footer}>
|
||||
|
||||
Reference in New Issue
Block a user