Fix bugs & Reader improvement (#1009)

* fix: wrong chapter order

* fix: reader footer styles

* make swipe getures less sensitive

* fix: update only ongoing novels option

* synchonize update library

* add horizontal scrollbar

* update swipe gesture sensitive

* no swipe when image modal is visible

* add bionic reading

* fix: import untitled epub
This commit is contained in:
nyagami
2024-03-23 11:50:35 +07:00
committed by GitHub
parent 8906253527
commit 9f6ffc23bf
12 changed files with 509 additions and 175 deletions
+100 -15
View File
@@ -97,7 +97,23 @@ img {
top: 50%;
transform: translateY(-50%);
right: 5vw;
transition: 150ms;
}
#ToolWrapper.horizontal {
top: unset;
right: unset;
left: 50%;
transform: translateX(-50%);
bottom: 120px;
display: flex;
flex-direction: row-reverse;
align-items: center;
}
@media only screen and (min-width: 500px) {
#ToolWrapper.horizontal {
bottom: 80px;
}
}
#ToolWrapper.hidden {
@@ -105,10 +121,20 @@ img {
right: 0;
}
#ToolWrapper.hidden.horizontal {
opacity: 0;
bottom: 0;
right: unset;
}
#TTS-Controller {
display: block;
}
#ToolWrapper.horizontal > #TTS-Controller {
margin-left: 4px;
}
#TTS-Controller button{
background: var(--theme-surface-0-9);
border: 0;
@@ -118,7 +144,7 @@ img {
fill: var(--theme-onSurface);
}
#ScrollBar {
#ToolWrapper > #ScrollBar {
margin-top: 8px;
width: 2.4rem;
height: 45vh;
@@ -130,35 +156,35 @@ img {
user-select: none;
}
.scrollbar-item {
#ScrollBar > .scrollbar-item {
display: flex;
justify-content: center;
align-items: center;
color: var(--theme-onSurface);
}
.scrollbar-item.scrollbar-text {
#ScrollBar > .scrollbar-item.scrollbar-text {
height: 12.5%;
}
.scrollbar-item#scrollbar-slider {
#ScrollBar > .scrollbar-item#scrollbar-slider {
height: 75%;
align-items: unset;
}
#scrollbar-track {
#ScrollBar #scrollbar-track {
width: 0.1rem;
height: 100%;
background-color: var(--theme-outline);
}
#scrollbar-progress {
width: 0.1rem;
#ScrollBar #scrollbar-progress {
width: 100%;
height: 0;
background-color: var(--theme-primary);
}
#scrollbar-thumb-wrapper {
#ScrollBar #scrollbar-thumb-wrapper {
position: relative;
top: 100%;
height: 2rem;
@@ -169,7 +195,62 @@ img {
transform: translate(-0.95rem, -1rem);
}
#scrollbar-thumb {
#ScrollBar #scrollbar-thumb {
top: 100%;
height: 1rem;
width: 1rem;
border-radius: 100%;
background-color: var(--theme-primary);
}
#ToolWrapper.horizontal > #ScrollBar{
display: flex;
width: 80vw;
height: 2.4rem;
min-height: unset;
align-items: center;
margin-top: unset;
}
#ToolWrapper.horizontal > #ScrollBar > .scrollbar-item {
display: flex;
justify-content: center;
color: var(--theme-onSurface);
}
#ToolWrapper.horizontal > #ScrollBar > .scrollbar-item.scrollbar-text {
width: 12.5%;
}
#ToolWrapper.horizontal > #ScrollBar > .scrollbar-item#scrollbar-slider {
width: 75%;
align-items: center;
}
#ToolWrapper.horizontal > #ScrollBar #scrollbar-track {
height: 0.1rem;
width: 100%;
background-color: var(--theme-outline);
}
#ToolWrapper.horizontal > #ScrollBar #scrollbar-progress {
height: 100%;
width: 0;
background-color: var(--theme-primary);
}
#ToolWrapper.horizontal > #ScrollBar #scrollbar-thumb-wrapper {
position: relative;
left: 100%;
height: 2rem;
width: 2rem;
display: flex;
align-items: center;
justify-content: center;
transform: translate(-0.95rem, -1rem);
}
#ToolWrapper.horizontal > #ScrollBar #scrollbar-thumb {
top: 100%;
height: 1rem;
width: 1rem;
@@ -186,10 +267,8 @@ img {
#reader-footer {
display: flex;
flex: 1;
justify-content: space-between;
min-height: 2rem;
padding: 0.5rem 2rem 0 2rem;
flex: 3;
padding: 0.2rem 2rem;
background-color: var(--readerSettings-theme);
color: var(--readerSettings-textColor);
font-size: 1rem;
@@ -198,7 +277,13 @@ img {
}
.reader-footer-item {
display: flex;
flex: 1;
}
.reader-footer-item:first-child {
text-align: left;
}
.reader-footer-item:last-child {
text-align: right;
}
tts.highlight {
+55 -7
View File
@@ -36,6 +36,7 @@ class Reader {
),
);
this.chapter = document.querySelector('chapter');
this.rawHTML = this.chapter.innerHTML;
this.chapterHeight = this.chapter.scrollHeight + this.paddingTop;
this.layoutHeight = window.innerHeight;
this.pluginId = this.chapter.getAttribute('data-plugin-id');
@@ -54,11 +55,13 @@ class Reader {
this.time.innerText = new Date().toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit',
hour12: false,
});
this.timeInterval = setInterval(() => {
this.time.innerText = new Date().toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit',
hour12: false,
});
}, 60000);
this.updateBatteryLevel(batteryLevel);
@@ -110,6 +113,13 @@ class Reader {
updateGeneralSettings = settings => {
this.showScrollPercentage = settings.showScrollPercentage;
this.showBatteryAndTime = settings.showBatteryAndTime;
this.verticalSeekbar = settings.verticalSeekbar;
this.bionicReading = settings.bionicReading;
if (this.bionicReading) {
this.chapter.innerHTML = textVide.textVide(this.rawHTML);
} else {
this.chapter.innerHTML = this.rawHTML;
}
if (settings.swipeGestures) {
swipeHandler.enable();
} else {
@@ -132,6 +142,15 @@ class Reader {
this.time.classList.add('hidden');
}
}
if (!this.verticalSeekbar) {
toolWrapper.$.classList.add('horizontal');
} else {
toolWrapper.$.classList.remove('horizontal');
}
if (scrollHandler) {
scrollHandler.refresh();
scrollHandler.update();
}
};
updateBatteryLevel = level => {
this.battery.innerText = Math.ceil(level * 100) + '%';
@@ -194,7 +213,11 @@ class ScrollHandler {
this.thumb.ontouchend = () => (this.lock = false);
this.thumb.ontouchmove = e => {
const ratio =
(e.changedTouches[0].clientY - this.sliderOffsetY) / this.sliderHeight;
((this.reader.verticalSeekbar
? e.changedTouches[0].clientY
: e.changedTouches[0].clientX) -
this.sliderOffsetY) /
this.sliderHeight;
this.update(ratio < 0 ? 0 : ratio);
};
}
@@ -208,7 +231,13 @@ class ScrollHandler {
}
const percentage = parseInt(ratio * 100);
if (this.toolWrapper.visible) {
this.progress.style.height = percentage + '%';
if (this.reader.verticalSeekbar) {
this.progress.style.height = percentage + '%';
this.progress.style.width = '100%';
} else {
this.progress.style.height = '100%';
this.progress.style.width = percentage + '%';
}
this.percentage.innerText = percentage;
}
if (this.lock) {
@@ -222,8 +251,13 @@ class ScrollHandler {
}
};
refresh = () => {
this.sliderHeight = this.slider.clientHeight;
this.sliderOffsetY = this.slider.getBoundingClientRect().top;
if (this.reader.verticalSeekbar) {
this.sliderHeight = this.slider.clientHeight;
this.sliderOffsetY = this.slider.getBoundingClientRect().top;
} else {
this.sliderHeight = this.slider.clientWidth;
this.sliderOffsetY = this.slider.getBoundingClientRect().left;
}
};
onShow = () => {
this.reader.refresh();
@@ -243,12 +277,26 @@ class SwipeHandler {
this.initialY = e.changedTouches[0].screenY;
};
/**
* @param {TouchEvent} e
*/
touchEndHandler = e => {
let diffX = e.changedTouches[0].screenX - this.initialX;
let diffY = e.changedTouches[0].screenY - this.initialY;
if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 10) {
e.preventDefault();
reader.post({ type: diffX < 0 ? 'next' : 'prev' });
if (
e.target.id?.startsWith('scrollbar') ||
e.target.id === 'Image-Modal-img'
) {
return;
}
if (Math.abs(diffX) > Math.abs(diffY) * 2 && Math.abs(diffX) > 180) {
if (diffX < 0 && this.initialX >= window.innerWidth / 2) {
e.preventDefault();
reader.post({ type: 'next' });
} else if (diffX > 0 && this.initialX <= window.innerWidth / 2) {
e.preventDefault();
reader.post({ type: 'prev' });
}
}
};
+111
View File
@@ -0,0 +1,111 @@
var textVide = (function (L) {
'use strict';
const f = t => t == null || t === '',
h = (t, n) =>
Object.keys(t).reduce((e, s) => (n(e[s]) && delete e[s], e), t),
I = (t, n) => ({ ...n, ...h(t, f) }),
_ = ['<b>', '</b>'],
y = t =>
I(t, {
sep: _,
fixationPoint: 1,
ignoreHtmlTag: !0,
ignoreHtmlEntity: !0,
}),
u = [
[0, 4, 12, 17, 24, 29, 35, 42, 48],
[1, 2, 7, 10, 13, 14, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49],
[
1, 2, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37,
39, 41, 43, 45, 47, 49,
],
[
0, 2, 4, 5, 6, 8, 9, 11, 14, 15, 17, 18, 20, 0, 21, 23, 24, 26, 27, 29,
30, 32, 33, 35, 36, 38, 39, 41, 42, 44, 45, 47, 48,
],
[
0, 2, 3, 5, 6, 7, 8, 10, 11, 12, 14, 15, 17, 19, 20, 21, 23, 24, 25, 26,
28, 29, 30, 32, 33, 34, 35, 37, 38, 39, 41, 42, 43, 44, 46, 47, 48,
],
],
A = (t, n) => {
const { length: i } = t,
e = u[n - 1] ?? u[0],
s = e.findIndex(c => i <= c);
let o = i - s;
return s === -1 && (o = i - e.length), Math.max(o, 0);
},
R = (t, n) =>
typeof n === 'string' ? `${n}${t}${n}` : `${n[0]}${t}${n[1]}`,
m = t =>
Array.from(t).map(n => {
const i = n.index,
[e] = n,
{ length: s } = e;
return [i, i + s - 1];
}),
H = /(<!--[\s\S]*?-->)|(<[^>]*>)/g,
N = t => {
const n = t.matchAll(H),
e = m(n).reverse();
return s => {
const o = s.index,
c = e.find(([r]) => o > r);
if (!c) {
return !1;
}
const [, a] = c;
return o < a;
};
},
O = /(&[\w#]+;)/g,
M = t => {
const n = t.matchAll(O),
e = m(n).reverse();
return s => {
const o = s.index,
c = e.find(([r]) => o > r);
if (!c) {
return !1;
}
const [, a] = c;
return o < a;
};
},
x = /(\p{L}|\p{Nd})*\p{L}(\p{L}|\p{Nd})*/gu,
F = (t, n = {}) => {
if (!(t != null && t.length)) {
return '';
}
const {
fixationPoint: i,
sep: e,
ignoreHtmlTag: s,
ignoreHtmlEntity: o,
} = y(n),
c = Array.from(t.matchAll(x));
let a = '',
l = 0,
r;
s && (r = N(t));
let d;
o && (d = M(t));
for (const g of c) {
if ((r == null ? void 0 : r(g)) || (d == null ? void 0 : d(g))) {
continue;
}
const [D] = g,
T = g.index,
E = T + A(D, i),
U = t.slice(l, T);
(a += U), T !== E && (a += R(t.slice(T, E), e)), (l = E);
}
const G = t.slice(l);
return a + G;
};
return (
(L.textVide = F),
Object.defineProperty(L, Symbol.toStringTag, { value: 'Module' }),
L
);
})({});
+1 -1
View File
@@ -41,7 +41,7 @@ export const getLibraryNovelsFromDb = (
let getLibraryNovelsQuery = 'SELECT * FROM Novel WHERE inLibrary = 1';
if (onlyOngoingNovels) {
getLibraryNovelsQuery += " AND status NOT LIKE 'Completed'";
getLibraryNovelsQuery += " AND status = 'Ongoing'";
}
return new Promise(resolve =>
+2
View File
@@ -81,6 +81,7 @@ export interface ChapterGeneralSettings {
autoScrollOffset: number | null;
verticalSeekbar: boolean;
removeExtraParagraphSpacing: boolean;
bionicReading: boolean;
}
export interface ReaderTheme {
@@ -158,6 +159,7 @@ export const initialChapterGeneralSettings: ChapterGeneralSettings = {
autoScrollOffset: null,
verticalSeekbar: true,
removeExtraParagraphSpacing: false,
bionicReading: false,
};
export const initialChapterReaderSettings: ChapterReaderSettings = {
@@ -33,12 +33,13 @@ const GeneralTab: React.FC = () => {
const {
fullScreenMode,
autoScroll,
// verticalSeekbar,
verticalSeekbar,
showBatteryAndTime,
showScrollPercentage,
useVolumeButtons,
swipeGestures,
removeExtraParagraphSpacing,
bionicReading,
setChapterGeneralSettings,
} = useChapterGeneralSettings();
@@ -58,14 +59,14 @@ const GeneralTab: React.FC = () => {
value={autoScroll}
theme={theme}
/>
{/* <ReaderSheetPreferenceItem
<ReaderSheetPreferenceItem
label={getString('readerScreen.bottomSheet.verticalSeekbar')}
onPress={() =>
setChapterGeneralSettings({ verticalSeekbar: !verticalSeekbar })
}
value={verticalSeekbar}
theme={theme}
/> */}
/>
<ReaderSheetPreferenceItem
label={getString('readerScreen.bottomSheet.showBatteryAndTime')}
onPress={() =>
@@ -110,6 +111,14 @@ const GeneralTab: React.FC = () => {
value={useVolumeButtons}
theme={theme}
/>
<ReaderSheetPreferenceItem
label={getString('readerScreen.bottomSheet.bionicReading')}
onPress={() =>
setChapterGeneralSettings({ bionicReading: !bionicReading })
}
value={bionicReading}
theme={theme}
/>
</View>
);
};
@@ -75,7 +75,13 @@ const WebViewReader: FC<WebViewReaderProps> = props => {
initialChapterReaderSettings,
[],
);
const { showScrollPercentage, swipeGestures, showBatteryAndTime } = useMemo(
const {
showScrollPercentage,
swipeGestures,
showBatteryAndTime,
verticalSeekbar,
bionicReading,
} = useMemo(
() =>
getMMKVObject<ChapterGeneralSettings>(CHAPTER_GENERAL_SETTINGS) ||
initialChapterGeneralSettings,
@@ -223,6 +229,8 @@ const WebViewReader: FC<WebViewReaderProps> = props => {
showScrollPercentage: ${showScrollPercentage},
swipeGestures: ${swipeGestures},
showBatteryAndTime: ${showBatteryAndTime},
verticalSeekbar: ${verticalSeekbar},
bionicReading: ${bionicReading},
}
var batteryLevel = ${batteryLevel};
var autoSaveInterval = 2222;
@@ -269,6 +277,7 @@ const WebViewReader: FC<WebViewReaderProps> = props => {
</div>`
}
</body>
<script src="${assetsUriPrefix}/js/text-vibe.js"></script>
<script src="${assetsUriPrefix}/js/index.js"></script>
<script>
async function fn(){
@@ -20,11 +20,12 @@ const GeneralSettings: React.FC = () => {
const {
useVolumeButtons = false,
// verticalSeekbar = true,
verticalSeekbar = true,
swipeGestures = false,
autoScroll = false,
autoScrollInterval = 10,
autoScrollOffset = null,
bionicReading = false,
setChapterGeneralSettings,
} = useChapterGeneralSettings();
@@ -41,17 +42,15 @@ const GeneralSettings: React.FC = () => {
</List.SubHeader>
{/* will add this later xD */}
{/* <SettingSwitch
<SettingSwitch
label={getString('readerScreen.bottomSheet.verticalSeekbar')}
description={getString(
'readerSettings.verticalSeekbarDesc',
)}
description={getString('readerSettings.verticalSeekbarDesc')}
value={verticalSeekbar}
onPress={() =>
setChapterGeneralSettings({ verticalSeekbar: !verticalSeekbar })
}
theme={theme}
/> */}
/>
<SettingSwitch
label={getString('readerScreen.bottomSheet.volumeButtonsScroll')}
value={useVolumeButtons}
@@ -68,6 +67,14 @@ const GeneralSettings: React.FC = () => {
}
theme={theme}
/>
<SettingSwitch
label={getString('readerScreen.bottomSheet.bionicReading')}
value={bionicReading}
onPress={() =>
setChapterGeneralSettings({ bionicReading: !bionicReading })
}
theme={theme}
/>
<SettingSwitch
label={getString('readerScreen.bottomSheet.autoscroll')}
value={autoScroll}
@@ -48,8 +48,12 @@ const SettingsReaderScreen = () => {
const layoutHeight = Dimensions.get('window').height;
const batteryLevel = useBatteryLevel();
const readerSettings = useChapterReaderSettings();
const { showScrollPercentage, showBatteryAndTime } =
useChapterGeneralSettings();
const {
showScrollPercentage,
showBatteryAndTime,
verticalSeekbar,
bionicReading,
} = useChapterGeneralSettings();
const READER_HEIGHT = 280;
const assetsUriPrefix = useMemo(
() => (__DEV__ ? 'http://localhost:8081/assets' : 'file:///android_asset'),
@@ -165,6 +169,8 @@ const SettingsReaderScreen = () => {
showScrollPercentage: ${showScrollPercentage},
swipeGestures: false,
showBatteryAndTime: ${showBatteryAndTime},
verticalSeekbar: ${verticalSeekbar},
bionicReading: ${bionicReading},
}
var batteryLevel = ${batteryLevel};
var autoSaveInterval = 2222;
@@ -193,6 +199,7 @@ const SettingsReaderScreen = () => {
</div>
</div>
</body>
<script src="${assetsUriPrefix}/js/text-vibe.js"></script>
<script src="${assetsUriPrefix}/js/index.js"></script>
<script>
async function fn(){
+9 -1
View File
@@ -23,6 +23,7 @@ import EpubUtil from '@native/EpubUtil';
interface TaskData {
delay: number;
sourceUri: string;
filename?: string;
}
const insertLocalNovel = (
@@ -167,6 +168,9 @@ const importEpubAction = async (taskData?: TaskData) => {
await sleep(taskData.delay);
const novel = await EpubUtil.parseNovelAndChapters(epubDirPath);
if (!novel.name) {
novel.name = taskData.filename?.replace('.epub', '') || 'Untitled';
}
const novelId = await insertLocalNovel(
novel.name,
epubDirPath + novel.name, // temporary
@@ -278,7 +282,11 @@ export const importEpub = async () => {
taskDesc: getString('common.parsing'),
taskIcon: { name: 'notification_icon', type: 'drawable' },
color: '#00adb5',
parameters: { delay: 500, sourceUri: epubFile.assets[0].uri },
parameters: {
delay: 500,
sourceUri: epubFile.assets[0].uri,
filename: epubFile.assets[0].name,
},
progressBar: { value: 0, max: 4 },
});
} catch (e: any) {
+120 -71
View File
@@ -10,43 +10,129 @@ import { getMMKVObject, setMMKVObject } from '@utils/mmkv/mmkv';
import { NOVEL_PAGE_UPDATES_PREFIX } from '@hooks/persisted/useNovel';
const db = SQLite.openDatabase('lnreader.db');
const updateNovelMetadata = async (
const updateNovelMetadata = (
pluginId: string,
novelId: number,
novel: SourceNovel,
) => {
let { name, cover, summary, author, artist, genres, status, totalPages } =
novel;
const novelDir = NovelDownloadFolder + '/' + pluginId + '/' + novelId;
if (cover) {
const novelCoverUri = 'file://' + novelDir + '/cover.png';
await fetchImage(pluginId, cover).then(base64 => {
if (base64) {
cover = novelCoverUri;
return RNFS.writeFile(novelCoverUri, base64, 'base64');
}
return new Promise(async (resolve, reject) => {
let { name, cover, summary, author, artist, genres, status, totalPages } =
novel;
const novelDir = NovelDownloadFolder + '/' + pluginId + '/' + novelId;
if (!(await RNFS.exists(novelDir))) {
await RNFS.mkdir(novelDir);
}
if (cover) {
const novelCoverUri = 'file://' + novelDir + '/cover.png';
await fetchImage(pluginId, cover)
.then(base64 => {
if (base64) {
cover = novelCoverUri;
return RNFS.writeFile(novelCoverUri, base64, 'base64');
}
})
.catch(reject);
cover += '?' + Date.now();
}
db.transaction(tx => {
tx.executeSql(
`UPDATE Novel SET
name = ?, cover = ?, summary = ?, author = ?, artist = ?,
genres = ?, status = ?, totalPages = ?
WHERE id = ?
`,
[
name,
cover || null,
summary || null,
author || 'unknown',
artist || null,
genres || null,
status || null,
totalPages || 0,
novelId,
],
() => resolve(null),
(txObj, error) => {
reject(error);
return false;
},
);
});
});
};
const updateNovelChapters = (
pluginId: string,
novelId: number,
novel: SourceNovel,
downloadNewChapters?: boolean,
) => {
return new Promise((resolve, reject) => {
db.transaction(async tx => {
const newChapters: { id: number; path: string }[] = [];
for (let position = 0; position < novel.chapters.length; position++) {
const { name, path, releaseTime, page } = novel.chapters[position];
tx.executeSql(
`
INSERT INTO Chapter (path, name, releaseTime, novelId, updatedTime, page, position)
SELECT ?, ?, ?, ?, datetime('now','localtime'), ?, ?
WHERE NOT EXISTS (SELECT id FROM Chapter WHERE path = ? AND novelId = ?);
`,
[
path,
name,
releaseTime || null,
novelId,
page || '1',
position,
path,
novelId,
],
(txObj, { insertId }) => {
if (insertId) {
if (downloadNewChapters) {
newChapters.push({ id: insertId, path: path });
}
} else {
tx.executeSql(
`
UPDATE Chapter SET
name = ?, releaseTime = ?, updatedTime = datetime('now','localtime'), page = ?, position = ?
WHERE path = ? AND novelId = ? AND (name != ? OR releaseTime != ? OR page != ?);
`,
[
name,
releaseTime || null,
page || '1',
position,
path,
novelId,
name,
releaseTime || null,
page || '1',
],
undefined,
(txObj, error) => {
reject(error);
return false;
},
);
}
},
(txObj, error) => {
reject(error);
return false;
},
);
}
if (downloadNewChapters) {
for (const { id, path } of newChapters) {
await downloadChapter(pluginId, novelId, id, path).catch(reject);
}
}
resolve(null);
});
cover += '?' + Date.now();
}
db.transaction(tx => {
tx.executeSql(
`UPDATE Novel SET
name = ?, cover = ?, summary = ?, author = ?, artist = ?,
genres = ?, status = ?, totalPages = ?
WHERE id = ?
`,
[
name,
cover || null,
summary || null,
author || 'unknown',
artist || null,
genres || null,
status || null,
totalPages || 0,
novelId,
],
);
});
};
@@ -67,46 +153,9 @@ const updateNovel = async (
const { downloadNewChapters, refreshNovelMetadata } = options;
const novel = await fetchNovel(pluginId, novelPath);
if (refreshNovelMetadata) {
updateNovelMetadata(pluginId, novelId, novel);
await updateNovelMetadata(pluginId, novelId, novel);
}
db.transaction(tx => {
novel.chapters.forEach(chapter => {
const { name, path, releaseTime, page } = chapter;
tx.executeSql(
`
INSERT INTO Chapter (path, name, releaseTime, novelId, updatedTime, page)
SELECT ?, ?, ?, ?, datetime('now','localtime'), ?
WHERE NOT EXISTS (SELECT id FROM Chapter WHERE path = ? AND novelId = ?);
`,
[path, name, releaseTime || null, novelId, page || '1', path, novelId],
(txObj, { insertId }) => {
if (insertId) {
if (downloadNewChapters) {
downloadChapter(pluginId, novelId, insertId, path);
}
} else {
tx.executeSql(
`
UPDATE Chapter SET
name = ?, releaseTime = ?, updatedTime = datetime('now','localtime'), page = ?
WHERE path = ? AND novelId = ? AND (name != ? OR releaseTime != ? OR page != ?);
`,
[
name,
releaseTime || null,
page || '1',
path,
novelId,
name,
releaseTime || null,
page || '1',
],
);
}
},
);
});
});
await updateNovelChapters(pluginId, novelId, novel, downloadNewChapters);
const key = `${NOVEL_PAGE_UPDATES_PREFIX}_${novelId}`;
const hasUpdates = getMMKVObject<boolean[]>(key);
if (hasUpdates) {
+67 -68
View File
@@ -19,8 +19,63 @@ import { getString } from '@strings/translations';
interface TaskData {
delay: number;
libraryNovels: LibraryNovelInfo[];
options: UpdateNovelOptions;
}
const libraryUpdateBackgroundAction = async (taskData?: TaskData) => {
try {
if (!taskData) {
return;
}
const { delay, libraryNovels, options } = taskData;
MMKVStorage.set(BACKGROUND_ACTION, BackgoundAction.UPDATE_LIBRARY);
MMKVStorage.set(LAST_UPDATE_TIME, dayjs().format('YYYY-MM-DD HH:mm:ss'));
for (
let i = 0;
BackgroundService.isRunning() && i < libraryNovels.length;
i++
) {
try {
if (BackgroundService.isRunning()) {
await updateNovel(
libraryNovels[i].pluginId,
libraryNovels[i].path,
libraryNovels[i].id,
options,
);
/**
* Update notification
*/
await BackgroundService.updateNotification({
taskTitle: '(' + (i + 1) + '/' + libraryNovels.length + ')',
taskDesc: libraryNovels[i].name,
progressBar: { max: libraryNovels.length, value: i + 1 },
});
await sleep(delay ?? 1000);
}
} catch (error: any) {
showToast(libraryNovels[i].name + ': ' + error.message);
continue;
}
}
} finally {
MMKVStorage.delete(BACKGROUND_ACTION);
Notifications.scheduleNotificationAsync({
content: {
title: getString('updatesScreen.libraryUpdated'),
body: getString('updatesScreen.novelsUpdated', {
num: taskData?.libraryNovels.length || 0,
}),
},
trigger: null,
});
BackgroundService.stop();
}
};
const updateLibrary = async (categoryId?: number) => {
const { downloadNewChapters, refreshNovelMetadata, onlyUpdateOngoingNovels } =
getMMKVObject<AppSettings>(APP_SETTINGS) || {};
@@ -30,10 +85,11 @@ const updateLibrary = async (categoryId?: number) => {
};
let libraryNovels: LibraryNovelInfo[] = [];
if (categoryId) {
libraryNovels = await getLibraryWithCategory({
filter: `categoryId = ${categoryId}`,
filter:
`categoryId = ${categoryId}` +
(onlyUpdateOngoingNovels ? " AND status = 'Ongoing'" : ''),
});
} else {
libraryNovels = (await getLibraryNovelsFromDb(
@@ -47,80 +103,23 @@ const updateLibrary = async (categoryId?: number) => {
taskDesc: '(0/' + libraryNovels.length + ')',
taskIcon: { name: 'notification_icon', type: 'drawable' },
color: '#00adb5',
parameters: { delay: 1000 },
parameters: { delay: 1000, libraryNovels, options },
linkingURI: 'lnreader://updates',
progressBar: { max: libraryNovels.length, value: 0 },
};
const libraryUpdateBackgroundAction = async (taskData?: TaskData) => {
try {
MMKVStorage.set(BACKGROUND_ACTION, BackgoundAction.UPDATE_LIBRARY);
MMKVStorage.set(LAST_UPDATE_TIME, dayjs().format('YYYY-MM-DD HH:mm:ss'));
for (
let i = 0;
BackgroundService.isRunning() && i < libraryNovels.length;
i++
) {
try {
if (BackgroundService.isRunning()) {
await updateNovel(
libraryNovels[i].pluginId,
libraryNovels[i].path,
libraryNovels[i].id,
options,
);
/**
* Update notification
*/
await BackgroundService.updateNotification({
taskTitle: libraryNovels[i].name,
taskDesc: '(' + (i + 1) + '/' + libraryNovels.length + ')',
progressBar: { max: libraryNovels.length, value: i + 1 },
});
/**
* When updating library is finished
*/
if (libraryNovels.length === i + 1) {
Notifications.scheduleNotificationAsync({
content: {
title: getString('updatesScreen.libraryUpdated'),
body: getString('updatesScreen.novelsUpdated', {
num: libraryNovels.length,
}),
},
trigger: null,
});
return;
}
const nextNovelIndex = i + 1;
if (
nextNovelIndex in libraryNovels &&
libraryNovels[nextNovelIndex].pluginId ===
libraryNovels[i].pluginId
) {
await sleep(taskData?.delay ?? 0);
}
}
} catch (error: any) {
showToast(libraryNovels[i].name + ': ' + error.message);
continue;
}
}
} finally {
MMKVStorage.delete(BACKGROUND_ACTION);
BackgroundService.stop();
}
};
if (libraryNovels.length > 0 && !MMKVStorage.getString(BACKGROUND_ACTION)) {
const anotherBackground = MMKVStorage.getString(BACKGROUND_ACTION);
if (libraryNovels.length > 0 && !anotherBackground) {
await BackgroundService.start<TaskData>(
libraryUpdateBackgroundAction,
notificationOptions,
);
} else {
if (anotherBackground) {
showToast(getString('browseScreen.migration.anotherServiceIsRunning'));
} else {
showToast("There's no novel to be updated");
}
}
};