Skip to content

Commit 9da47cf

Browse files
Merge pull request #63 from p2devs/ps/feat/track-offline-read
Offline Sync Reliability, Vertical Reader Optimization, and Enhanced State Persistence Update
2 parents a9d6de6 + 8e683d7 commit 9da47cf

File tree

9 files changed

+526
-113
lines changed

9 files changed

+526
-113
lines changed

src/InkNest-Externals

src/Redux/Actions/GlobalActions.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export const fetchComicDetails =
128128
comicDetails = config.customParser($, config, link);
129129
} else {
130130
const detailsContainer = $(config.detailsContainer);
131-
const title = $(config.title).text().trim();
131+
const title = $(config.title).first().text().trim();
132132
let imgSrc = detailsContainer
133133
.find(config.imgSrc)
134134
.attr(config.getImageAttr);

src/Redux/Reducers/index.js

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,49 @@ const Reducers = createSlice({
125125
link,
126126
comicBooks: {
127127
...state.DownloadComic[link]?.comicBooks,
128-
[data?.link]: data,
128+
[data?.link]: {
129+
...state.DownloadComic[link]?.comicBooks?.[data?.link],
130+
...data,
131+
comicBook: state.DownloadComic[link]?.comicBooks?.[data?.link]?.comicBook
132+
? {
133+
...state.DownloadComic[link]?.comicBooks?.[data?.link]?.comicBook,
134+
...data?.comicBook,
135+
...(data?.lastReadPage !== undefined
136+
? {lastReadPage: data.lastReadPage}
137+
: {}),
138+
}
139+
: data?.comicBook,
140+
},
129141
},
130142
};
131-
console.log('action.payload', link, data, title);
143+
},
144+
updateDownloadedComicBook: (state, action) => {
145+
const {comicDetailsLink, chapterLink, data} = action.payload;
132146

133-
console.log('state.DownloadComic', state.DownloadComic);
147+
if (
148+
!comicDetailsLink ||
149+
!chapterLink ||
150+
!state.DownloadComic[comicDetailsLink]?.comicBooks?.[chapterLink]
151+
) {
152+
return;
153+
}
154+
155+
const existingEntry =
156+
state.DownloadComic[comicDetailsLink].comicBooks[chapterLink];
157+
158+
state.DownloadComic[comicDetailsLink].comicBooks[chapterLink] = {
159+
...existingEntry,
160+
...data,
161+
comicBook: existingEntry?.comicBook
162+
? {
163+
...existingEntry.comicBook,
164+
...(data?.comicBook ?? {}),
165+
...(data?.lastReadPage !== undefined
166+
? {lastReadPage: data.lastReadPage}
167+
: {}),
168+
}
169+
: existingEntry?.comicBook,
170+
};
134171
},
135172
DeleteDownloadedComicBook: (state, action) => {
136173
const {comicBooksLink, ChapterLink} = action.payload;
@@ -147,7 +184,10 @@ const Reducers = createSlice({
147184
//trim the query from the url
148185
const link = action.payload.link.split('?')[0];
149186

150-
state.history[link] = action.payload;
187+
state.history[link] = {
188+
...state.history[link],
189+
...action.payload,
190+
};
151191
},
152192
UpdateSearch: (state, action) => {
153193
//push data to search array on top
@@ -221,6 +261,7 @@ export const {
221261
RemoveAnimeBookMark,
222262
DownloadComicBook,
223263
DeleteDownloadedComicBook,
264+
updateDownloadedComicBook,
224265
clearHistory,
225266
setScrollPreference,
226267
rewardAdsShown,

src/Screens/Comic/APIs/constance.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export const HomePageCardClasses = {
110110
export const ComicDetailPageClasses = {
111111
readcomicsonline: {
112112
detailsContainer: '.list-container',
113-
title: 'img.img-responsive',
113+
title: '.listmanga-header',
114114
imgSrc: '.boxed img.img-responsive',
115115
getImageAttr: 'src',
116116
summary: 'div.manga.well p',

src/Screens/Comic/Book/Download/VerticalView.js

Lines changed: 92 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useEffect, useMemo, useRef, useState} from 'react';
1+
import React, {useEffect, useMemo, useRef, useState, useCallback} from 'react';
22
import {
33
View,
44
Text,
@@ -8,15 +8,15 @@ import {
88
FlatList,
99
useWindowDimensions,
1010
Image,
11-
Modal,
1211
} from 'react-native';
13-
import {SafeAreaView} from 'react-native-safe-area-context';
1412
import {
1513
fitContainer,
1614
ResumableZoom,
1715
useImageResolution,
1816
} from 'react-native-zoom-toolkit';
1917

18+
const ITEM_SPACING = 20;
19+
2020
export default function VerticalView({
2121
data,
2222
loading,
@@ -30,12 +30,28 @@ export default function VerticalView({
3030
const ref = useRef(null);
3131
const [zoomMode, setZoomMode] = useState(false);
3232
const [imageSizeAcuired, setImageSizeAcquired] = useState(false);
33+
const [initialSyncDone, setInitialSyncDone] = useState(false);
34+
const currentIndexRef = useRef(0);
3335

34-
// Update imagesLinks when comicBook changes
3536
useEffect(() => {
36-
if (data && data?.length > 0) {
37-
setImagesLinks(data[0]);
37+
if (!data || data.length === 0) {
38+
setImagesLinks('');
39+
currentIndexRef.current = 0;
40+
return;
3841
}
42+
43+
const maxIndex = data.length - 1;
44+
const safeIndex = Math.min(
45+
Math.max(activeIndex ?? 0, 0),
46+
maxIndex,
47+
);
48+
49+
setImagesLinks(data[safeIndex]);
50+
currentIndexRef.current = safeIndex;
51+
}, [activeIndex, data]);
52+
53+
useEffect(() => {
54+
setInitialSyncDone(false);
3955
}, [data]);
4056

4157
// Properly prepare the image source object with headers
@@ -79,33 +95,76 @@ export default function VerticalView({
7995
}, [resolution, isFetching, imagesLinks, imageSource]);
8096

8197
useEffect(() => {
82-
if (activeIndex > 0 && data?.length > 0) {
83-
// Ensure size is acquired before attempting to scroll
84-
if (imageSizeAcuired && ref.current) {
85-
ref.current.scrollToIndex({index: activeIndex, animated: true});
86-
}
98+
if (!data || data.length === 0) {
99+
return;
87100
}
88-
// Depend on imageSizeAcuired to ensure scroll happens after layout is known
89-
}, [activeIndex, data, imageSizeAcuired]);
90101

91-
// getItemLayout is used to optimize the FlatList performance
92-
const getItemLayout = (data, index) => {
93-
// Use calculated size if available
94-
if (size?.height) {
95-
return {
96-
length: size.height,
97-
offset: size.height * index,
98-
index,
99-
};
102+
if (!imageSizeAcuired || initialSyncDone || !ref.current) {
103+
return;
100104
}
101-
// Fallback if size is not yet calculated: use resolutions prop or screen height
102-
const estimatedHeight = resolutions?.height || height;
103-
return {
104-
length: estimatedHeight,
105-
offset: estimatedHeight * index,
105+
106+
const maxIndex = data.length - 1;
107+
const safeIndex = Math.min(
108+
Math.max(activeIndex ?? 0, 0),
109+
maxIndex,
110+
);
111+
112+
if (safeIndex === 0) {
113+
currentIndexRef.current = 0;
114+
setInitialSyncDone(true);
115+
return;
116+
}
117+
118+
try {
119+
ref.current.scrollToIndex({index: safeIndex, animated: false});
120+
currentIndexRef.current = safeIndex;
121+
setInitialSyncDone(true);
122+
} catch (error) {
123+
// Ignore out of range errors while list recalculates
124+
}
125+
}, [activeIndex, data, imageSizeAcuired, initialSyncDone]);
126+
127+
// getItemLayout is used to optimize the FlatList performance
128+
const itemLength = useMemo(() => {
129+
const baseHeight = size?.height || resolutions?.height || height;
130+
return baseHeight + ITEM_SPACING;
131+
}, [height, resolutions?.height, size?.height]);
132+
133+
const getItemLayout = useCallback(
134+
(_, index) => ({
135+
length: itemLength,
136+
offset: itemLength * index,
106137
index,
107-
};
108-
};
138+
}),
139+
[itemLength],
140+
);
141+
142+
const updateIndexFromOffset = useCallback(
143+
offset => {
144+
if (!Number.isFinite(offset) || !itemLength) {
145+
return;
146+
}
147+
148+
const rawIndex = Math.round(offset / itemLength);
149+
const maxIndex = Math.max(0, (data?.length ?? 1) - 1);
150+
const safeIndex = Math.min(Math.max(rawIndex, 0), maxIndex);
151+
152+
if (safeIndex === currentIndexRef.current) {
153+
return;
154+
}
155+
156+
currentIndexRef.current = safeIndex;
157+
setImageLinkIndex(safeIndex);
158+
},
159+
[data?.length, itemLength, setImageLinkIndex],
160+
);
161+
162+
const handleScrollEnd = useCallback(
163+
event => {
164+
updateIndexFromOffset(event.nativeEvent.contentOffset.y);
165+
},
166+
[updateIndexFromOffset],
167+
);
109168

110169
if (loading || !imagesLinks) {
111170
return (
@@ -136,7 +195,7 @@ export default function VerticalView({
136195
renderItem={({item, index}) => (
137196
<TouchableOpacity
138197
style={{
139-
marginBottom: 20,
198+
marginBottom: ITEM_SPACING,
140199
}}
141200
activeOpacity={0.8}
142201
onPress={() => {
@@ -164,16 +223,10 @@ export default function VerticalView({
164223
)}
165224
</TouchableOpacity>
166225
)}
167-
onScroll={event => {
168-
// Calculate the index based on the scroll position
169-
// and the height of the image (only if size is known)
170-
if (!size?.height) return;
171-
const index = Math.floor(
172-
event.nativeEvent.contentOffset.y / size.height,
173-
);
174-
setImageLinkIndex(index);
175-
}}
226+
onScrollEndDrag={handleScrollEnd}
227+
onMomentumScrollEnd={handleScrollEnd}
176228
showsVerticalScrollIndicator={false}
229+
contentContainerStyle={{paddingBottom: ITEM_SPACING}}
177230
/>
178231
{zoomMode && (
179232
<View

0 commit comments

Comments
 (0)