@@ -7,7 +7,6 @@ module.exports = {
|
||||
{
|
||||
files: ['*.js', '*.jsx', '*.ts', '*.tsx'],
|
||||
rules: {
|
||||
'prettier/prettier': ['error', { 'endOfLine': 'auto' }],
|
||||
'@typescript-eslint/no-shadow': ['warn'],
|
||||
'no-shadow': 'off',
|
||||
'no-undef': 'off',
|
||||
|
||||
Generated
-33
@@ -21,7 +21,6 @@
|
||||
"@react-navigation/stack": "^6.0.11",
|
||||
"@reduxjs/toolkit": "^1.7.2",
|
||||
"@shopify/flash-list": "^1.2.1",
|
||||
"buffer": "^6.0.3",
|
||||
"cheerio": "^1.0.0-rc.10",
|
||||
"color": "^4.2.3",
|
||||
"dayjs": "^1.11.4",
|
||||
@@ -5325,29 +5324,6 @@
|
||||
"node-int64": "^0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-alloc": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
|
||||
@@ -19937,15 +19913,6 @@
|
||||
"node-int64": "^0.4.0"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"requires": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"buffer-alloc": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
"@react-navigation/stack": "^6.0.11",
|
||||
"@reduxjs/toolkit": "^1.7.2",
|
||||
"@shopify/flash-list": "^1.2.1",
|
||||
"buffer": "^6.0.3",
|
||||
"cheerio": "^1.0.0-rc.10",
|
||||
"color": "^4.2.3",
|
||||
"dayjs": "^1.11.4",
|
||||
|
||||
@@ -6,7 +6,6 @@ import { ChapterItem } from '../types';
|
||||
import * as cheerio from 'cheerio';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import { txnErrorCallback } from '@database/utils/helpers';
|
||||
import { LoadingImageSrc } from '@screens/reader/utils/LoadImage';
|
||||
|
||||
const db = SQLite.openDatabase('lnreader.db');
|
||||
|
||||
@@ -297,9 +296,7 @@ const downloadImages = async (
|
||||
if (fileurl.charAt(0) === '-') {
|
||||
return loadedCheerio.html();
|
||||
}
|
||||
elem.replaceWith(
|
||||
`<img class='load-icon' src="${LoadingImageSrc}" file-path="${fileurl}">`,
|
||||
);
|
||||
elem.attr('src', `file://${fileurl}`);
|
||||
const exists = await RNFetchBlob.fs.exists(fileurl).catch(() => {
|
||||
showToast(
|
||||
`Unexpected storage error!\nRemove ${fileurl} and try downloading again`,
|
||||
|
||||
@@ -94,10 +94,8 @@ const BrowseSourceScreen: React.FC<BrowseSourceScreenProps> = ({ route }) => {
|
||||
navigate(
|
||||
'Novel' as never,
|
||||
{
|
||||
novelName: item.novelName,
|
||||
novelCover: item.novelCover,
|
||||
novelUrl: item.novelUrl,
|
||||
sourceId,
|
||||
...item,
|
||||
sourceId: sourceId,
|
||||
} as never,
|
||||
),
|
||||
[sourceId],
|
||||
|
||||
@@ -118,7 +118,7 @@ const HistoryScreen = () => {
|
||||
<SectionList
|
||||
contentContainerStyle={styles.listContainer}
|
||||
sections={groupHistoryByDate(searchText ? searchResults : history)}
|
||||
keyExtractor={item => item.chapterId.toString()}
|
||||
keyExtractor={(item, index) => 'history' + index}
|
||||
renderSectionHeader={({ section: { date } }) => (
|
||||
<Text style={[styles.dateHeader, { color: theme.onSurface }]}>
|
||||
{dayjs(date).calendar()}
|
||||
|
||||
+311
-315
@@ -358,237 +358,273 @@ const Novel = ({ route, navigation }) => {
|
||||
const [editInfoModal, showEditInfoModal] = useState(false);
|
||||
const downloadCustomChapterModal = useBoolean();
|
||||
|
||||
if (loading) {
|
||||
return <NovelScreenLoading theme={theme} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
<View style={[styles.container, { backgroundColor: theme.background }]}>
|
||||
{loading && <NovelScreenLoading theme={theme} />}
|
||||
{selected.length === 0 && (
|
||||
<View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
zIndex: 1,
|
||||
height: StatusBar.currentHeight + 54,
|
||||
width: '100%',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
icon="arrow-left"
|
||||
iconColor={theme.onBackground}
|
||||
size={24}
|
||||
style={{ marginTop: StatusBar.currentHeight + 8 }}
|
||||
onPress={() => navigation.goBack()}
|
||||
/>
|
||||
<Row>
|
||||
<Portal>
|
||||
{selected.length === 0 ? (
|
||||
<View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
height: StatusBar.currentHeight + 54,
|
||||
width: '100%',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
icon="share-variant"
|
||||
icon="arrow-left"
|
||||
iconColor={theme.onBackground}
|
||||
size={21}
|
||||
style={{
|
||||
marginTop: StatusBar.currentHeight + 8,
|
||||
}}
|
||||
onPress={() =>
|
||||
Share.share({
|
||||
message: novel.sourceUrl,
|
||||
})
|
||||
}
|
||||
size={24}
|
||||
style={{ marginTop: StatusBar.currentHeight + 8 }}
|
||||
onPress={() => navigation.goBack()}
|
||||
/>
|
||||
<IconButton
|
||||
icon="text-box-search-outline"
|
||||
iconColor={theme.onBackground}
|
||||
size={21}
|
||||
style={{
|
||||
marginTop: StatusBar.currentHeight + 8,
|
||||
}}
|
||||
onPress={() => showJumpToChapterModal(true)}
|
||||
/>
|
||||
|
||||
<Menu
|
||||
visible={downloadMenu}
|
||||
onDismiss={() => showDownloadMenu(false)}
|
||||
anchor={
|
||||
<IconButton
|
||||
icon="download-outline"
|
||||
iconColor={theme.onBackground}
|
||||
size={24}
|
||||
style={{
|
||||
marginTop: StatusBar.currentHeight + 8,
|
||||
}}
|
||||
onPress={() => showDownloadMenu(true)}
|
||||
/>
|
||||
}
|
||||
contentStyle={{ backgroundColor: theme.surface2 }}
|
||||
>
|
||||
<Menu.Item
|
||||
title="Next chapter"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{ color: theme.onSurface }}
|
||||
onPress={() => {
|
||||
dispatch(
|
||||
downloadAllChaptersAction(
|
||||
novel.sourceId,
|
||||
novel.novelUrl,
|
||||
[
|
||||
chapters.find(
|
||||
chapter =>
|
||||
!!chapter.read === false &&
|
||||
!!chapter.downloaded === false,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
showDownloadMenu(false);
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
title="Next 5 chapter"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{
|
||||
color: theme.onSurface,
|
||||
}}
|
||||
onPress={() => {
|
||||
dispatch(
|
||||
downloadAllChaptersAction(
|
||||
novel.sourceId,
|
||||
novel.novelUrl,
|
||||
chapters
|
||||
.filter(
|
||||
chapter =>
|
||||
!!chapter.read === false &&
|
||||
!!chapter.downloaded === false,
|
||||
)
|
||||
.slice(0, 5),
|
||||
),
|
||||
);
|
||||
showDownloadMenu(false);
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
title="Next 10 chapter"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{
|
||||
color: theme.onSurface,
|
||||
}}
|
||||
onPress={() => {
|
||||
dispatch(
|
||||
downloadAllChaptersAction(
|
||||
novel.sourceId,
|
||||
novel.novelUrl,
|
||||
chapters
|
||||
.filter(
|
||||
chapter =>
|
||||
!!chapter.read === false &&
|
||||
!!chapter.downloaded === false,
|
||||
)
|
||||
.slice(0, 10),
|
||||
),
|
||||
);
|
||||
showDownloadMenu(false);
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
title="Custom"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{ color: theme.onSurface }}
|
||||
onPress={() => {
|
||||
downloadCustomChapterModal.setTrue();
|
||||
showDownloadMenu(false);
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
title="Unread"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{
|
||||
color: theme.onSurface,
|
||||
}}
|
||||
onPress={() => {
|
||||
dispatch(
|
||||
downloadAllChaptersAction(
|
||||
novel.sourceId,
|
||||
novel.novelUrl,
|
||||
chapters.filter(chapter => !!chapter.read === false),
|
||||
),
|
||||
);
|
||||
showDownloadMenu(false);
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
title="All"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{
|
||||
color: theme.onSurface,
|
||||
}}
|
||||
onPress={() => {
|
||||
dispatch(
|
||||
downloadAllChaptersAction(
|
||||
novel.sourceId,
|
||||
novel.novelUrl,
|
||||
chapters,
|
||||
),
|
||||
);
|
||||
showDownloadMenu(false);
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
title="Delete downloads"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{
|
||||
color: theme.onSurface,
|
||||
<Row>
|
||||
<IconButton
|
||||
icon="share-variant"
|
||||
iconColor={theme.onBackground}
|
||||
size={21}
|
||||
style={{
|
||||
marginTop: StatusBar.currentHeight + 8,
|
||||
}}
|
||||
onPress={() =>
|
||||
dispatch(deleteAllChaptersAction(sourceId, chapters))
|
||||
Share.share({
|
||||
message: novel.sourceUrl,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Menu>
|
||||
<IconButton
|
||||
icon="text-box-search-outline"
|
||||
iconColor={theme.onBackground}
|
||||
size={21}
|
||||
style={{
|
||||
marginTop: StatusBar.currentHeight + 8,
|
||||
}}
|
||||
onPress={() => showJumpToChapterModal(true)}
|
||||
/>
|
||||
|
||||
<Menu
|
||||
visible={extraMenu}
|
||||
onDismiss={() => showExtraMenu(false)}
|
||||
anchor={
|
||||
<IconButton
|
||||
icon="dots-vertical"
|
||||
iconColor={theme.onBackground}
|
||||
size={21}
|
||||
style={{
|
||||
marginTop: StatusBar.currentHeight + 8,
|
||||
marginRight: 16,
|
||||
<Menu
|
||||
visible={downloadMenu}
|
||||
onDismiss={() => showDownloadMenu(false)}
|
||||
anchor={
|
||||
<IconButton
|
||||
icon="download-outline"
|
||||
iconColor={theme.onBackground}
|
||||
size={24}
|
||||
style={{
|
||||
marginTop: StatusBar.currentHeight + 8,
|
||||
}}
|
||||
onPress={() => showDownloadMenu(true)}
|
||||
/>
|
||||
}
|
||||
contentStyle={{ backgroundColor: theme.surface2 }}
|
||||
>
|
||||
<Menu.Item
|
||||
title="Next chapter"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{ color: theme.onSurface }}
|
||||
onPress={() => {
|
||||
dispatch(
|
||||
downloadAllChaptersAction(
|
||||
novel.sourceId,
|
||||
novel.novelUrl,
|
||||
[
|
||||
chapters.find(
|
||||
chapter =>
|
||||
!!chapter.read === false &&
|
||||
!!chapter.downloaded === false,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
showDownloadMenu(false);
|
||||
}}
|
||||
onPress={() => showExtraMenu(true)}
|
||||
/>
|
||||
}
|
||||
contentStyle={{
|
||||
backgroundColor: theme.surface2,
|
||||
<Menu.Item
|
||||
title="Next 5 chapter"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{
|
||||
color: theme.onSurface,
|
||||
}}
|
||||
onPress={() => {
|
||||
dispatch(
|
||||
downloadAllChaptersAction(
|
||||
novel.sourceId,
|
||||
novel.novelUrl,
|
||||
chapters
|
||||
.filter(
|
||||
chapter =>
|
||||
!!chapter.read === false &&
|
||||
!!chapter.downloaded === false,
|
||||
)
|
||||
.slice(0, 5),
|
||||
),
|
||||
);
|
||||
showDownloadMenu(false);
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
title="Next 10 chapter"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{
|
||||
color: theme.onSurface,
|
||||
}}
|
||||
onPress={() => {
|
||||
dispatch(
|
||||
downloadAllChaptersAction(
|
||||
novel.sourceId,
|
||||
novel.novelUrl,
|
||||
chapters
|
||||
.filter(
|
||||
chapter =>
|
||||
!!chapter.read === false &&
|
||||
!!chapter.downloaded === false,
|
||||
)
|
||||
.slice(0, 10),
|
||||
),
|
||||
);
|
||||
showDownloadMenu(false);
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
title="Custom"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{ color: theme.onSurface }}
|
||||
onPress={() => {
|
||||
downloadCustomChapterModal.setTrue();
|
||||
showDownloadMenu(false);
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
title="Unread"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{
|
||||
color: theme.onSurface,
|
||||
}}
|
||||
onPress={() => {
|
||||
dispatch(
|
||||
downloadAllChaptersAction(
|
||||
novel.sourceId,
|
||||
novel.novelUrl,
|
||||
chapters.filter(chapter => !!chapter.read === false),
|
||||
),
|
||||
);
|
||||
showDownloadMenu(false);
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
title="All"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{
|
||||
color: theme.onSurface,
|
||||
}}
|
||||
onPress={() => {
|
||||
dispatch(
|
||||
downloadAllChaptersAction(
|
||||
novel.sourceId,
|
||||
novel.novelUrl,
|
||||
chapters,
|
||||
),
|
||||
);
|
||||
showDownloadMenu(false);
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
title="Delete downloads"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{
|
||||
color: theme.onSurface,
|
||||
}}
|
||||
onPress={() =>
|
||||
dispatch(deleteAllChaptersAction(sourceId, chapters))
|
||||
}
|
||||
/>
|
||||
</Menu>
|
||||
|
||||
<Menu
|
||||
visible={extraMenu}
|
||||
onDismiss={() => showExtraMenu(false)}
|
||||
anchor={
|
||||
<IconButton
|
||||
icon="dots-vertical"
|
||||
iconColor={theme.onBackground}
|
||||
size={21}
|
||||
style={{
|
||||
marginTop: StatusBar.currentHeight + 8,
|
||||
marginRight: 16,
|
||||
}}
|
||||
onPress={() => showExtraMenu(true)}
|
||||
/>
|
||||
}
|
||||
contentStyle={{
|
||||
backgroundColor: theme.surface2,
|
||||
}}
|
||||
>
|
||||
<Menu.Item
|
||||
title="Edit info"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{
|
||||
color: theme.onSurface,
|
||||
}}
|
||||
onPress={() => {
|
||||
showEditInfoModal(true);
|
||||
showExtraMenu(false);
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
title="Edit cover"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{
|
||||
color: theme.onSurface,
|
||||
}}
|
||||
onPress={setCustomNovelCover}
|
||||
/>
|
||||
</Menu>
|
||||
</Row>
|
||||
</View>
|
||||
) : (
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(150)}
|
||||
exiting={FadeOut.duration(150)}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
elevation: 2,
|
||||
backgroundColor: theme.surface2,
|
||||
paddingTop: StatusBar.currentHeight,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingBottom: 8,
|
||||
}}
|
||||
>
|
||||
<Appbar.Action
|
||||
icon="close"
|
||||
iconColor={theme.onBackground}
|
||||
onPress={() => setSelected([])}
|
||||
/>
|
||||
<Appbar.Content
|
||||
title={`${selected.length}`}
|
||||
titleStyle={{ color: theme.onSurface }}
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon="select-all"
|
||||
iconColor={theme.onBackground}
|
||||
onPress={() => {
|
||||
setSelected(chapters);
|
||||
}}
|
||||
>
|
||||
<Menu.Item
|
||||
title="Edit info"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{
|
||||
color: theme.onSurface,
|
||||
}}
|
||||
onPress={() => {
|
||||
showEditInfoModal(true);
|
||||
showExtraMenu(false);
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
title="Edit cover"
|
||||
style={{ backgroundColor: theme.surface2 }}
|
||||
titleStyle={{
|
||||
color: theme.onSurface,
|
||||
}}
|
||||
onPress={setCustomNovelCover}
|
||||
/>
|
||||
</Menu>
|
||||
</Row>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
</Animated.View>
|
||||
)}
|
||||
</Portal>
|
||||
<View style={{ minHeight: 3, flex: 1 }}>
|
||||
<FlashList
|
||||
ref={ref => (flatlistRef.current = ref)}
|
||||
ref={flatlistRef}
|
||||
estimatedItemSize={64}
|
||||
data={!loading && chapters}
|
||||
data={chapters}
|
||||
extraData={[downloadQueue, selected]}
|
||||
removeClippedSubviews={true}
|
||||
maxToRenderPerBatch={5}
|
||||
@@ -603,7 +639,6 @@ const Novel = ({ route, navigation }) => {
|
||||
novel={novel}
|
||||
theme={theme}
|
||||
filter={filter}
|
||||
loading={loading}
|
||||
lastRead={lastReadChapter}
|
||||
setCustomNovelCover={setCustomNovelCover}
|
||||
dispatch={dispatch}
|
||||
@@ -617,28 +652,25 @@ const Novel = ({ route, navigation }) => {
|
||||
refreshControl={refreshControl()}
|
||||
/>
|
||||
</View>
|
||||
{useFabForContinueReading &&
|
||||
!loading &&
|
||||
chapters.length > 0 &&
|
||||
lastReadChapter && (
|
||||
<FAB
|
||||
style={[
|
||||
styles.fab,
|
||||
{ backgroundColor: theme.primary, marginBottom: bottomInset },
|
||||
]}
|
||||
small
|
||||
color={theme.onPrimary}
|
||||
uppercase={false}
|
||||
label={novel.unread ? 'Start' : 'Resume'}
|
||||
icon="play"
|
||||
onPress={() => {
|
||||
navigation.navigate(
|
||||
'Chapter',
|
||||
openChapter(novel, lastReadChapter),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{useFabForContinueReading && chapters.length > 0 && lastReadChapter && (
|
||||
<FAB
|
||||
style={[
|
||||
styles.fab,
|
||||
{ backgroundColor: theme.primary, marginBottom: bottomInset },
|
||||
]}
|
||||
small
|
||||
color={theme.onPrimary}
|
||||
uppercase={false}
|
||||
label={novel.unread ? 'Start' : 'Resume'}
|
||||
icon="play"
|
||||
onPress={() => {
|
||||
navigation.navigate(
|
||||
'Chapter',
|
||||
openChapter(novel, lastReadChapter),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Portal>
|
||||
<Actionbar
|
||||
active={selected.length > 0}
|
||||
@@ -663,84 +695,48 @@ const Novel = ({ route, navigation }) => {
|
||||
</Text>
|
||||
</Snackbar>
|
||||
</Portal>
|
||||
{!loading && (
|
||||
<Portal>
|
||||
<JumpToChapterModal
|
||||
modalVisible={jumpToChapterModal}
|
||||
hideModal={() => showJumpToChapterModal(false)}
|
||||
chapters={chapters}
|
||||
novel={novel}
|
||||
chapterListRef={flatlistRef.current}
|
||||
navigation={navigation}
|
||||
/>
|
||||
<EditInfoModal
|
||||
modalVisible={editInfoModal}
|
||||
hideModal={() => showEditInfoModal(false)}
|
||||
novel={novel}
|
||||
theme={theme}
|
||||
dispatch={dispatch}
|
||||
/>
|
||||
<DownloadCustomChapterModal
|
||||
modalVisible={downloadCustomChapterModal.value}
|
||||
hideModal={downloadCustomChapterModal.setFalse}
|
||||
novel={novel}
|
||||
chapters={chapters}
|
||||
theme={theme}
|
||||
dispatch={dispatch}
|
||||
/>
|
||||
<NovelBottomSheet
|
||||
novelUrl={novelUrl}
|
||||
bottomSheetRef={novelBottomSheetRef}
|
||||
dispatch={dispatch}
|
||||
sortAndFilterChapters={sortAndFilterChapters}
|
||||
novelId={novel.novelId}
|
||||
sort={sort}
|
||||
theme={theme}
|
||||
filter={filter}
|
||||
showChapterTitles={showChapterTitles}
|
||||
/>
|
||||
<TrackSheet
|
||||
bottomSheetRef={trackerSheetRef}
|
||||
novelId={novel.novelId}
|
||||
novelName={novel.novelName}
|
||||
theme={theme}
|
||||
/>
|
||||
</Portal>
|
||||
)}
|
||||
{selected.length > 0 ? (
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(150)}
|
||||
exiting={FadeOut.duration(150)}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
zIndex: 20,
|
||||
width: Dimensions.get('window').width,
|
||||
elevation: 2,
|
||||
backgroundColor: theme.surface2,
|
||||
paddingTop: StatusBar.currentHeight,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingBottom: 8,
|
||||
}}
|
||||
>
|
||||
<Appbar.Action
|
||||
icon="close"
|
||||
iconColor={theme.onBackground}
|
||||
onPress={() => setSelected([])}
|
||||
/>
|
||||
<Appbar.Content
|
||||
title={`${selected.length}`}
|
||||
titleStyle={{ color: theme.onSurface }}
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon="select-all"
|
||||
iconColor={theme.onBackground}
|
||||
onPress={() => {
|
||||
setSelected(chapters);
|
||||
}}
|
||||
/>
|
||||
</Animated.View>
|
||||
) : null}
|
||||
<Portal>
|
||||
<JumpToChapterModal
|
||||
modalVisible={jumpToChapterModal}
|
||||
hideModal={() => showJumpToChapterModal(false)}
|
||||
chapters={chapters}
|
||||
novel={novel}
|
||||
chapterListRef={flatlistRef.current}
|
||||
navigation={navigation}
|
||||
/>
|
||||
<EditInfoModal
|
||||
modalVisible={editInfoModal}
|
||||
hideModal={() => showEditInfoModal(false)}
|
||||
novel={novel}
|
||||
theme={theme}
|
||||
dispatch={dispatch}
|
||||
/>
|
||||
<DownloadCustomChapterModal
|
||||
modalVisible={downloadCustomChapterModal.value}
|
||||
hideModal={downloadCustomChapterModal.setFalse}
|
||||
novel={novel}
|
||||
chapters={chapters}
|
||||
theme={theme}
|
||||
dispatch={dispatch}
|
||||
/>
|
||||
<NovelBottomSheet
|
||||
novelUrl={novelUrl}
|
||||
bottomSheetRef={novelBottomSheetRef}
|
||||
dispatch={dispatch}
|
||||
sortAndFilterChapters={sortAndFilterChapters}
|
||||
novelId={novel.novelId}
|
||||
sort={sort}
|
||||
theme={theme}
|
||||
filter={filter}
|
||||
showChapterTitles={showChapterTitles}
|
||||
/>
|
||||
<TrackSheet
|
||||
bottomSheetRef={trackerSheetRef}
|
||||
novelId={novel.novelId}
|
||||
novelName={novel.novelName}
|
||||
theme={theme}
|
||||
/>
|
||||
</Portal>
|
||||
</View>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { StyleSheet, Text, View, TextInput } from 'react-native';
|
||||
|
||||
import { IconButton, Modal, Portal } from 'react-native-paper';
|
||||
import { Button, IconButton, Modal, Portal } from 'react-native-paper';
|
||||
import { downloadAllChaptersAction } from '../../../redux/novel/novel.actions';
|
||||
|
||||
const DownloadCustomChapterModal = ({
|
||||
@@ -91,6 +91,13 @@ const DownloadCustomChapterModal = ({
|
||||
onPress={() => setText(prevState => prevState + 10)}
|
||||
/>
|
||||
</View>
|
||||
<Button
|
||||
onPress={onSubmit}
|
||||
textColor={theme.onPrimary}
|
||||
buttonColor={theme.primary}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
|
||||
@@ -54,8 +54,11 @@ const EditInfoModal = ({ theme, hideModal, modalVisible, novel, dispatch }) => {
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
>
|
||||
{status.map(item => (
|
||||
<View style={{ borderRadius: 8, overflow: 'hidden' }} key={item}>
|
||||
{status.map((item, index) => (
|
||||
<View
|
||||
style={{ borderRadius: 8, overflow: 'hidden' }}
|
||||
key={'novelInfo' + index}
|
||||
>
|
||||
<Pressable
|
||||
style={{
|
||||
backgroundColor:
|
||||
@@ -133,7 +136,7 @@ const EditInfoModal = ({ theme, hideModal, modalVisible, novel, dispatch }) => {
|
||||
contentContainerStyle={{ marginVertical: 8 }}
|
||||
horizontal
|
||||
data={info.genre.split(',')}
|
||||
keyExtractor={item => item}
|
||||
keyExtractor={(item, index) => 'novelTag' + index}
|
||||
renderItem={({ item }) => (
|
||||
<GenreChip theme={theme} onPress={() => removeTag(item)}>
|
||||
{item}
|
||||
|
||||
@@ -37,7 +37,7 @@ const CoverImage = ({ children, source, theme, hideBackdrop }) => {
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: `${theme.background}B4`,
|
||||
backgroundColor: color(theme.background).alpha(0.7).string(),
|
||||
}}
|
||||
>
|
||||
{source.uri ? (
|
||||
@@ -57,7 +57,7 @@ const CoverImage = ({ children, source, theme, hideBackdrop }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const NovelThumbnail = ({ source, setCustomNovelCover }) => {
|
||||
const NovelThumbnail = ({ source, theme, setCustomNovelCover }) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
if (!expanded) {
|
||||
@@ -69,24 +69,15 @@ const NovelThumbnail = ({ source, setCustomNovelCover }) => {
|
||||
} else {
|
||||
return (
|
||||
<Portal>
|
||||
<IconButton
|
||||
icon="close"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: StatusBar.currentHeight,
|
||||
left: 0,
|
||||
zIndex: 10,
|
||||
}}
|
||||
onPress={() => setExpanded(false)}
|
||||
/>
|
||||
<IconButton
|
||||
icon="pencil-outline"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: StatusBar.currentHeight,
|
||||
right: 0,
|
||||
top: StatusBar.currentHeight + 10,
|
||||
right: 10,
|
||||
zIndex: 10,
|
||||
}}
|
||||
iconColor={theme.onBackground}
|
||||
onPress={setCustomNovelCover}
|
||||
/>
|
||||
<Pressable
|
||||
|
||||
@@ -33,7 +33,6 @@ const NovelInfoHeader = ({
|
||||
novel,
|
||||
theme,
|
||||
filter,
|
||||
loading,
|
||||
chapters,
|
||||
lastRead,
|
||||
navigation,
|
||||
@@ -58,13 +57,13 @@ const NovelInfoHeader = ({
|
||||
return (
|
||||
<>
|
||||
<CoverImage
|
||||
source={{ uri: !loading ? novel.novelCover : item.novelCover }}
|
||||
source={{ uri: novel.novelCover }}
|
||||
theme={theme}
|
||||
hideBackdrop={hideBackdrop}
|
||||
>
|
||||
<NovelInfoContainer>
|
||||
<NovelThumbnail
|
||||
source={{ uri: !loading ? novel.novelCover : item.novelCover }}
|
||||
source={{ uri: novel.novelCover }}
|
||||
theme={theme}
|
||||
setCustomNovelCover={setCustomNovelCover}
|
||||
/>
|
||||
@@ -85,7 +84,7 @@ const NovelInfoHeader = ({
|
||||
</NovelTitle>
|
||||
<>
|
||||
<NovelAuthor theme={theme}>
|
||||
{!loading && novel.author ? novel.author : 'Unknown author'}
|
||||
{novel.author || 'Unknown author'}
|
||||
</NovelAuthor>
|
||||
<Row>
|
||||
<MaterialCommunityIcons
|
||||
@@ -95,58 +94,52 @@ const NovelInfoHeader = ({
|
||||
style={{ marginRight: 4 }}
|
||||
/>
|
||||
<NovelInfo theme={theme}>
|
||||
{!loading
|
||||
? (novel.status || 'Unknown status') + ' • ' + novel.source
|
||||
: 'Unknown status • Unknown source'}
|
||||
{(novel.status || 'Unknown status') + ' • ' + novel.source}
|
||||
</NovelInfo>
|
||||
</Row>
|
||||
</>
|
||||
</View>
|
||||
</NovelInfoContainer>
|
||||
</CoverImage>
|
||||
{!loading && (
|
||||
<>
|
||||
<NovelScreenButtonGroup
|
||||
novel={novel}
|
||||
handleFollowNovel={() => {
|
||||
dispatch(followNovelAction(novel));
|
||||
if (
|
||||
novel.followed &&
|
||||
chapters.some(chapter => chapter.downloaded === 1)
|
||||
) {
|
||||
deleteDownloadsSnackbar.setTrue();
|
||||
}
|
||||
}}
|
||||
handleTrackerSheet={() => trackerSheetRef.current.expand()}
|
||||
theme={theme}
|
||||
<>
|
||||
<NovelScreenButtonGroup
|
||||
novel={novel}
|
||||
handleFollowNovel={() => {
|
||||
dispatch(followNovelAction(novel));
|
||||
if (
|
||||
novel.followed &&
|
||||
chapters.some(chapter => chapter.downloaded === 1)
|
||||
) {
|
||||
deleteDownloadsSnackbar.setTrue();
|
||||
}
|
||||
}}
|
||||
handleTrackerSheet={() => trackerSheetRef.current.expand()}
|
||||
theme={theme}
|
||||
/>
|
||||
<NovelSummary
|
||||
summary={novel.novelSummary}
|
||||
isExpanded={!novel.followed}
|
||||
theme={theme}
|
||||
/>
|
||||
{novel.genre ? <NovelGenres theme={theme} genre={novel.genre} /> : null}
|
||||
<ReadButton novel={novel} chapters={chapters} lastRead={lastRead} />
|
||||
<Pressable
|
||||
style={styles.bottomsheet}
|
||||
onPress={() => novelBottomSheetRef.current.expand()}
|
||||
android_ripple={{
|
||||
color: color(theme.primary).alpha(0.12).string(),
|
||||
}}
|
||||
>
|
||||
<Text style={[{ color: theme.onSurface }, styles.chapters]}>
|
||||
{`${chapters.length} ${getString('novelScreen.chapters')}`}
|
||||
</Text>
|
||||
<IconButton
|
||||
icon="filter-variant"
|
||||
iconColor={filter ? filterColor(theme.isDark) : theme.onSurface}
|
||||
size={24}
|
||||
/>
|
||||
<NovelSummary
|
||||
summary={novel.novelSummary}
|
||||
isExpanded={!novel.followed}
|
||||
theme={theme}
|
||||
/>
|
||||
{novel.genre ? (
|
||||
<NovelGenres theme={theme} genre={novel.genre} />
|
||||
) : null}
|
||||
<ReadButton novel={novel} chapters={chapters} lastRead={lastRead} />
|
||||
<Pressable
|
||||
style={styles.bottomsheet}
|
||||
onPress={() => novelBottomSheetRef.current.expand()}
|
||||
android_ripple={{
|
||||
color: color(theme.primary).alpha(0.12).string(),
|
||||
}}
|
||||
>
|
||||
<Text style={[{ color: theme.onSurface }, styles.chapters]}>
|
||||
{`${chapters.length} ${getString('novelScreen.chapters')}`}
|
||||
</Text>
|
||||
<IconButton
|
||||
icon="filter-variant"
|
||||
iconColor={filter ? filterColor(theme.isDark) : theme.onSurface}
|
||||
size={24}
|
||||
/>
|
||||
</Pressable>
|
||||
</>
|
||||
)}
|
||||
</Pressable>
|
||||
</>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -325,9 +325,7 @@ const ChapterContent = ({ route, navigation }) => {
|
||||
nextChapter={nextChapter}
|
||||
webViewRef={webViewRef}
|
||||
onLayout={() => {
|
||||
if (useVolumeButtons) {
|
||||
onLayout();
|
||||
}
|
||||
useVolumeButtons && onLayout();
|
||||
scrollTo(position?.position);
|
||||
}}
|
||||
onPress={hideHeader}
|
||||
@@ -335,39 +333,44 @@ const ChapterContent = ({ route, navigation }) => {
|
||||
navigateToChapterBySwipe={navigateToChapterBySwipe}
|
||||
onWebViewNavigationStateChange={onWebViewNavigationStateChange}
|
||||
/>
|
||||
<BottomInfoBar scrollPercentage={position?.percentage || 0} />
|
||||
<BottomInfoBar
|
||||
scrollPercentage={
|
||||
position?.percentage || Math.round(minScroll.current) || 0
|
||||
}
|
||||
/>
|
||||
<Portal>
|
||||
<ReaderBottomSheetV2 bottomSheetRef={readerSheetRef} />
|
||||
</Portal>
|
||||
<ReaderAppbar
|
||||
bookmark={bookmark}
|
||||
novelName={novelName}
|
||||
chapterId={chapterId}
|
||||
chapterName={chapterName || chapter.chapterName}
|
||||
hide={hidden}
|
||||
tts={startTts}
|
||||
textToSpeech={ttsStatus}
|
||||
theme={theme}
|
||||
/>
|
||||
<ReaderSeekBar
|
||||
hide={hidden}
|
||||
theme={theme}
|
||||
minScroll={minScroll.current}
|
||||
verticalSeekbar={verticalSeekbar}
|
||||
percentage={position?.percentage}
|
||||
scrollTo={scrollTo}
|
||||
/>
|
||||
<ReaderFooter
|
||||
hide={hidden}
|
||||
theme={theme}
|
||||
chapterUrl={chapterUrl}
|
||||
nextChapter={nextChapter}
|
||||
prevChapter={prevChapter}
|
||||
readerSheetRef={readerSheetRef}
|
||||
scrollTo={scrollTo}
|
||||
navigateToChapterBySwipe={navigateToChapterBySwipe}
|
||||
openDrawer={openDrawer}
|
||||
/>
|
||||
{!hidden && (
|
||||
<>
|
||||
<ReaderAppbar
|
||||
bookmark={bookmark}
|
||||
novelName={novelName}
|
||||
chapterId={chapterId}
|
||||
chapterName={chapterName || chapter.chapterName}
|
||||
tts={startTts}
|
||||
textToSpeech={ttsStatus}
|
||||
theme={theme}
|
||||
/>
|
||||
<ReaderSeekBar
|
||||
theme={theme}
|
||||
minScroll={minScroll.current}
|
||||
verticalSeekbar={verticalSeekbar}
|
||||
percentage={position?.percentage}
|
||||
scrollTo={scrollTo}
|
||||
/>
|
||||
<ReaderFooter
|
||||
theme={theme}
|
||||
chapterUrl={chapterUrl}
|
||||
nextChapter={nextChapter}
|
||||
prevChapter={prevChapter}
|
||||
readerSheetRef={readerSheetRef}
|
||||
scrollTo={scrollTo}
|
||||
navigateToChapterBySwipe={navigateToChapterBySwipe}
|
||||
openDrawer={openDrawer}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,7 +14,6 @@ const ReaderAppbar = ({
|
||||
novelName,
|
||||
chapterId,
|
||||
chapterName,
|
||||
hide,
|
||||
tts,
|
||||
textToSpeech,
|
||||
theme,
|
||||
@@ -23,10 +22,6 @@ const ReaderAppbar = ({
|
||||
const { goBack } = useNavigation();
|
||||
const [bookmarked, setBookmarked] = useState(bookmark);
|
||||
|
||||
if (hide) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(150)}
|
||||
|
||||
@@ -8,7 +8,6 @@ import color from 'color';
|
||||
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
|
||||
|
||||
const ChapterFooter = ({
|
||||
hide,
|
||||
theme,
|
||||
chapterUrl,
|
||||
nextChapter,
|
||||
@@ -26,16 +25,11 @@ const ChapterFooter = ({
|
||||
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
if (hide) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(150)}
|
||||
exiting={FadeOut.duration(150)}
|
||||
style={styles.footer}
|
||||
active={hide}
|
||||
animationDuration={150}
|
||||
>
|
||||
<View
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useDeviceOrientation } from '@hooks/useDeviceOrientation';
|
||||
|
||||
const VerticalScrollbar = ({
|
||||
hide,
|
||||
theme,
|
||||
minScroll = 0,
|
||||
verticalSeekbar,
|
||||
@@ -22,10 +21,6 @@ const VerticalScrollbar = ({
|
||||
};
|
||||
const screenOrientation = useDeviceOrientation();
|
||||
|
||||
if (hide) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (screenOrientation === 'potrait' && verticalSeekbar) {
|
||||
return (
|
||||
<View
|
||||
|
||||
@@ -71,6 +71,7 @@ const WebViewReader: React.FC<WebViewReaderProps> = props => {
|
||||
<WebView
|
||||
ref={webViewRef}
|
||||
style={{ backgroundColor }}
|
||||
allowFileAccess={true}
|
||||
originWhitelist={['*']}
|
||||
scalesPageToFit={true}
|
||||
showsVerticalScrollIndicator={false}
|
||||
@@ -90,53 +91,16 @@ const WebViewReader: React.FC<WebViewReaderProps> = props => {
|
||||
case 'prev':
|
||||
navigateToChapterBySwipe('SWIPE_RIGHT');
|
||||
break;
|
||||
case 'imgfiles':
|
||||
if (event.data) {
|
||||
if (Array.isArray(event.data.imgs)) {
|
||||
const promises: Promise<{ data: any; id: number }>[] = [];
|
||||
if (event.data.type === 'online') {
|
||||
for (let i = 0; i < event.data.imgs.length; i++) {
|
||||
const { url, id } = event.data.imgs[i];
|
||||
if (url && id) {
|
||||
promises.push(
|
||||
RNFetchBlob.fetch('get', url, headers).then(res => ({
|
||||
data: res.base64(),
|
||||
id: id,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < event.data.imgs.length; i++) {
|
||||
const { url, id } = event.data.imgs[i];
|
||||
if (url && id) {
|
||||
promises.push(
|
||||
RNFetchBlob.fs.readFile(url, 'base64').then(base64 => {
|
||||
return { data: base64, id: id };
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Promise.all(promises)
|
||||
.then(datas => {
|
||||
const inject = datas.reduce((p, data) => {
|
||||
return (
|
||||
p +
|
||||
`document.querySelector("img[file-id='${data.id}']").setAttribute("src", "data:image/jpg;base64,${data.data}");
|
||||
document.querySelector("img[file-id='${data.id}']").classList.remove("load-icon");`
|
||||
);
|
||||
}, '');
|
||||
webViewRef.current?.injectJavaScript(
|
||||
inject +
|
||||
'window.requestAnimationFrame(()=>sendHeight());',
|
||||
);
|
||||
})
|
||||
.catch(e => {
|
||||
e; // CloudFlare is too strong :D
|
||||
});
|
||||
}
|
||||
case 'imgfile':
|
||||
if (event.data && typeof event.data === 'string') {
|
||||
RNFetchBlob.fetch('get', event.data, headers).then(res => {
|
||||
const base64 = res.base64();
|
||||
webViewRef.current?.injectJavaScript(
|
||||
`document.querySelector("img[delayed-src='${event.data}']").src="data:image/jpg;base64,${base64}";
|
||||
document.querySelector("img[delayed-src='${event.data}']").classList.remove("load-icon");
|
||||
sendHeight(500);`,
|
||||
);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'scrollend':
|
||||
@@ -151,6 +115,7 @@ const WebViewReader: React.FC<WebViewReaderProps> = props => {
|
||||
const contentHeight = Math.round(Number(event.data));
|
||||
minScroll.current = (layoutHeight / contentHeight) * 100;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}}
|
||||
source={{
|
||||
@@ -261,22 +226,13 @@ const WebViewReader: React.FC<WebViewReaderProps> = props => {
|
||||
</chapter>
|
||||
</div>
|
||||
<script>
|
||||
const isOffline = !!document.querySelector("input[offline]")
|
||||
if(isOffline){
|
||||
imgs = [...document.querySelectorAll("img")].map((img, index)=>{
|
||||
img.setAttribute("file-id", index.toString());
|
||||
return {url:img.getAttribute("file-path"), id:img.getAttribute("file-id")};
|
||||
if(!document.querySelector("input[offline]") && ${!!headers}){
|
||||
document.querySelectorAll("img").forEach(img => {
|
||||
window.ReactNativeWebView.postMessage(JSON.stringify({type:"imgfile",data:img.getAttribute("delayed-src")}));
|
||||
});
|
||||
window.ReactNativeWebView.postMessage(JSON.stringify({type:"imgfiles",data:{imgs:imgs,type:"offline"}}));
|
||||
}else if(${!!headers}){
|
||||
imgs = [...document.querySelectorAll("img")].map((img, index)=>{
|
||||
img.setAttribute("file-id", index.toString());
|
||||
return {url:img.getAttribute("delayed-src"), id:img.getAttribute("file-id")};
|
||||
});
|
||||
window.ReactNativeWebView.postMessage(JSON.stringify({type:"imgfiles",data:{imgs:imgs,type:"online"}}));
|
||||
}
|
||||
|
||||
var scrollTimeout;
|
||||
|
||||
window.addEventListener("scroll", (event) => {
|
||||
window.clearTimeout( scrollTimeout );
|
||||
scrollTimeout = setTimeout(() => {
|
||||
@@ -293,17 +249,17 @@ const WebViewReader: React.FC<WebViewReaderProps> = props => {
|
||||
);
|
||||
}, 100);
|
||||
});
|
||||
const sendHeight = () => {
|
||||
window.ReactNativeWebView.postMessage(
|
||||
JSON.stringify(
|
||||
{
|
||||
type:"height",
|
||||
data:document.body.scrollHeight
|
||||
}
|
||||
)
|
||||
|
||||
let sendHeightTimeout;
|
||||
const sendHeight = (timeOut) => {
|
||||
clearTimeout(sendHeightTimeout);
|
||||
sendHeightTimeout = setTimeout(
|
||||
window.ReactNativeWebView.postMessage(
|
||||
JSON.stringify({type:"height",data:document.body.scrollHeight})
|
||||
), timeOut
|
||||
);
|
||||
}
|
||||
window.requestAnimationFrame(()=>sendHeight());
|
||||
sendHeight(1000);
|
||||
</script>
|
||||
<div class="infoText">
|
||||
${getString(
|
||||
@@ -325,28 +281,19 @@ const WebViewReader: React.FC<WebViewReaderProps> = props => {
|
||||
swipeGestures
|
||||
? `
|
||||
<script>
|
||||
var initialX = null;
|
||||
var initialY = null;
|
||||
let initialX = null;
|
||||
let initialY = null;
|
||||
document.addEventListener("touchstart", e => {
|
||||
initialX = e.changedTouches[0].screenX;
|
||||
initialY = e.changedTouches[0].screenY;
|
||||
});
|
||||
document.addEventListener("touchend", e => {
|
||||
if (initialX === null || initialY === null) return;
|
||||
var currentX = e.changedTouches[0].screenX;
|
||||
var currentY = e.changedTouches[0].screenY;
|
||||
var diffX = currentX - initialX;
|
||||
var diffY = currentY - initialY;
|
||||
let diffX = e.changedTouches[0].screenX - initialX;
|
||||
let diffY = e.changedTouches[0].screenY - initialY;
|
||||
if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 10) {
|
||||
e.preventDefault();
|
||||
if (diffX < 0) {
|
||||
window.ReactNativeWebView.postMessage(JSON.stringify({type:"next"}))
|
||||
} else {
|
||||
window.ReactNativeWebView.postMessage(JSON.stringify({type:"prev"}))
|
||||
}
|
||||
window.ReactNativeWebView.postMessage(JSON.stringify({type: diffX<0 ? "next" : "prev"}))
|
||||
}
|
||||
initialX = null;
|
||||
initialY = null;
|
||||
});
|
||||
</script>`
|
||||
: ''
|
||||
|
||||
@@ -13,16 +13,25 @@ export const sanitizeChapterText = (
|
||||
html: string,
|
||||
options?: Options,
|
||||
): string => {
|
||||
if (html) {
|
||||
html = html.replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
let text = sanitizeHtml(html, {
|
||||
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img', 'input']),
|
||||
allowedTags: sanitizeHtml.defaults.allowedTags.concat([
|
||||
'img',
|
||||
'input',
|
||||
'i',
|
||||
'em',
|
||||
'b',
|
||||
'a',
|
||||
]),
|
||||
allowedAttributes: {
|
||||
'img': ['src', 'type', 'file-path', 'file-id', 'offline', 'class'],
|
||||
'img': ['src', 'class'],
|
||||
'a': ['href'],
|
||||
'input': ['type', 'offline'],
|
||||
},
|
||||
allowedSchemes: ['data', 'http', 'https'],
|
||||
allowedSchemes: ['data', 'http', 'https', 'file'],
|
||||
});
|
||||
|
||||
if (text) {
|
||||
if (options?.removeExtraParagraphSpacing) {
|
||||
text = text.replace(/<\s*br[^>]*>/gi, '\n').replace(/\n{2,}/g, '\n\n');
|
||||
@@ -49,6 +58,5 @@ export const sanitizeChapterText = (
|
||||
text =
|
||||
"Chapter is empty.\n\nReport on <a href='https://github.com/LNReader/lnreader-sources/issues/new/choose'>github</a> if it's available in webview.";
|
||||
}
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
@@ -169,6 +169,7 @@ const GenralSettings: React.FC<GenralSettingsProps> = ({ navigation }) => {
|
||||
label={getString(
|
||||
'moreScreen.settingsScreen.generalSettingsScreen.updateLibrary',
|
||||
)}
|
||||
description={'Not recommended for low devices'}
|
||||
value={updateLibraryOnLaunch}
|
||||
onPress={() =>
|
||||
dispatch(
|
||||
|
||||
Reference in New Issue
Block a user