Refactor Chapter

This commit is contained in:
Rajarshee Chatterjee
2021-05-25 10:32:21 +05:30
parent 2c2e814a0d
commit 9a7e1f7e53
23 changed files with 299 additions and 203 deletions
+5 -1
View File
@@ -30,7 +30,11 @@
},
"package": "com.rajarsheechatterjee.LNReader"
},
"androidStatusBar": {
"barStyle": "light-content",
"backgroundColor": "#00000000",
"translucent": true
},
"androidNavigationBar": {
"barStyle": "light-content",
"backgroundColor": "#000000"
+5
View File
@@ -3969,6 +3969,11 @@
"resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-9.1.2.tgz",
"integrity": "sha512-IFBQPimeZtdqtvmqQZEmiI0D4j9xE53tSZxLFrFPplNWzgDefk+dP+flof3duf9JgCQTsvOUU2h2/XwjD3vBTA=="
},
"expo-speech": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/expo-speech/-/expo-speech-9.1.0.tgz",
"integrity": "sha512-5yIMVo1auAwClVnlJnCRyJqKS82IeQXzTCamP4iP4LmHDR5+9sae/D/mD5mX7KV3brWf3n0sEyFhuU7WqgMQvg=="
},
"expo-splash-screen": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.10.2.tgz",
+2 -1
View File
@@ -54,7 +54,8 @@
"redux-devtools-extension": "^2.13.9",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.3.0",
"rn-sliding-up-panel": "^2.4.4"
"rn-sliding-up-panel": "^2.4.4",
"expo-speech": "~9.1.0"
},
"devDependencies": {
"@babel/core": "~7.9.0"
+31 -1
View File
@@ -56,7 +56,7 @@ export const getChapters = (novelId, sort, filter) => {
const getChapterQuery = `SELECT * FROM downloads WHERE downloadChapterId = ?`;
export const getChapter = async (chapterId) => {
export const getChapterFromDB = async (chapterId) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
@@ -71,6 +71,36 @@ export const getChapter = async (chapterId) => {
);
};
export const getNextChapterFromDB = async (novelId, chapterId) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
`SELECT * FROM chapters WHERE novelId = ? AND chapterId > ?`,
[novelId, chapterId],
(tx, results) => {
resolve(results.rows.item(0));
},
(txObj, error) => console.log("Error ", error)
);
})
);
};
export const getPrevChapterFromDB = async (novelId, chapterId) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
`SELECT * FROM chapters WHERE novelId = ? AND chapterId < ?`,
[novelId, chapterId],
(tx, results) => {
resolve(results.rows.item(results.rows.length - 1));
},
(txObj, error) => console.log("Error ", error)
);
})
);
};
const markChapterReadQuery = `UPDATE chapters SET \`read\` = 1 WHERE chapterId = ?`;
export const markChapterRead = async (chapterId) => {
+1 -1
View File
@@ -2,7 +2,7 @@ import * as SQLite from "expo-sqlite";
const db = SQLite.openDatabase("lnreader.db");
const getUpdatesQuery = `
SELECT chapters.chapterId, chapters.chapterUrl, chapters.chapterName, novels.novelUrl, novels.novelId, novels.novelCover, novels.novelName, novels.sourceId, updates.updateTime, updates.updateId
SELECT chapters.chapterId, chapters.read, chapters.downloaded, chapters.chapterUrl, chapters.chapterName, novels.novelUrl, novels.novelId, novels.novelCover, novels.novelName, novels.sourceId, updates.updateTime, updates.updateId
FROM updates
JOIN chapters
ON updates.chapterId = chapters.chapterId
+15
View File
@@ -89,6 +89,20 @@ const useContinueReading = (chapters, novelId) => {
return { lastReadChapter, position };
};
const usePosition = (novelId, chapterId) => {
let novel, position;
novel = findNovel(novelId);
if (novel) {
if (novel.position) {
position = novel.position[chapterId] || null;
}
}
return position;
};
const useTrackingStatus = () => {
const tracker = useSelector((state) => state.trackerReducer);
@@ -113,4 +127,5 @@ export {
useChapter,
useSavedSettings,
useLibraryFilters,
usePosition,
};
+103 -121
View File
@@ -4,68 +4,95 @@ import {
View,
Text,
ActivityIndicator,
ScrollView,
InteractionManager,
Button,
} from "react-native";
import Constants from "expo-constants";
import { Appbar, Portal } from "react-native-paper";
import { CollapsibleHeaderScrollView } from "react-native-collapsible-header-views";
import ReaderSheet from "./components/ReaderSheet";
import { insertHistoryAction } from "../../redux/history/history.actions";
import {
getChapterAction,
saveScrollPosition,
} from "../../redux/chapter/chapter.actions";
import { markChapterReadAction } from "../../redux/novel/novel.actions";
import { updateChaptersRead } from "../../redux/tracker/tracker.actions";
import { useDispatch } from "react-redux";
import { parseChapterNumber } from "../../Services/updates";
import Constants from "expo-constants";
import { Portal } from "react-native-paper";
import { CollapsibleHeaderScrollView } from "react-native-collapsible-header-views";
import { getChapterFromDB } from "../../Database/queries/ChapterQueries";
import { fetchChapter } from "../../Services/Source/source";
import { showToast } from "../../Hooks/showToast";
import {
useChapter,
usePosition,
useReaderSettings,
useTheme,
useTrackingStatus,
} from "../../Hooks/reduxHooks";
import { updateChaptersRead } from "../../redux/tracker/tracker.actions";
import { insertHistoryAction } from "../../redux/history/history.actions";
import {
readerBackground,
readerLineHeight,
readerTextColor,
} from "./readerStyleController";
import { markChapterReadAction } from "../../redux/novel/novel.actions";
import { saveScrollPosition } from "../../redux/preferences/preference.actions";
import { parseChapterNumber } from "../../Services/updates";
import ChapterAppbar from "./components/ChapterAppbar";
import ReaderSheet from "./components/ReaderSheet";
const Chapter = ({ route, navigation }) => {
const { chapterId, sourceId, chapterUrl, novelUrl, novelId, position } =
route.params;
const {
sourceId,
chapterId,
chapterUrl,
novelId,
novelUrl,
novelName,
chapterName,
} = route.params;
let scrollViewRef;
let readerSheetRef = useRef(null);
const theme = useTheme();
const reader = useReaderSettings();
const dispatch = useDispatch();
const reader = useReaderSettings();
const { tracker, trackedNovels } = useTrackingStatus();
const position = usePosition(novelId, chapterId);
const isTracked = trackedNovels.find((obj) => obj.novelId === novelId);
const { chapter, loading } = useChapter();
const [chapter, setChapter] = useState({});
const [loading, setLoading] = useState(true);
const [scrollPercentage, setScrollPercentage] = useState(0);
const [firstLayout, setFirstLayout] = useState(true);
let scrollViewRef;
const getChapter = async (chapterId) => {
try {
if (chapterId) {
const chapterDownloaded = await getChapterFromDB(chapterId);
if (chapterDownloaded) {
setChapter(chapterDownloaded);
} else {
const res = await fetchChapter(
sourceId,
novelUrl,
chapterUrl
);
setChapter(res);
}
} else {
const res = await fetchChapter(sourceId, novelUrl, chapterUrl);
setChapter(res);
}
setLoading(false);
} catch (error) {
showToast(error.message);
}
};
useEffect(() => {
dispatch(getChapterAction(sourceId, novelUrl, chapterUrl, chapterId));
getChapter(chapterId);
dispatch(insertHistoryAction(novelId, chapterId));
}, []);
let chapterNumber;
if (!loading) {
chapterNumber = parseChapterNumber(chapter.chapterName);
}
const isCloseToBottom = ({
layoutMeasurement,
contentOffset,
@@ -78,39 +105,40 @@ const Chapter = ({ route, navigation }) => {
);
};
const updateTracker = () =>
const updateTracker = () => {
const chapterNumber = parseChapterNumber(chapterName);
isTracked &&
chapterNumber &&
chapterNumber > isTracked.my_list_status.num_chapters_read &&
dispatch(
updateChaptersRead(
isTracked.id,
tracker.access_token,
chapterNumber
)
chapterNumber &&
chapterNumber > isTracked.my_list_status.num_chapters_read &&
dispatch(
updateChaptersRead(
isTracked.id,
tracker.access_token,
chapterNumber
)
);
};
const onScroll = ({ nativeEvent }) => {
const offsetY = nativeEvent.contentOffset.y;
const position =
nativeEvent.contentOffset.y + nativeEvent.layoutMeasurement.height;
const percentage = Math.round(
(position / nativeEvent.contentSize.height) * 100
);
const navigateToPreviousChapter = () =>
navigation.replace("Chapter", {
chapterUrl: chapter.prevChapter,
chapterId: chapterId - 1,
sourceId,
novelUrl,
novelId,
chapterName: chapter.chapterName,
});
setScrollPercentage(percentage);
dispatch(saveScrollPosition(offsetY, percentage, chapterId, novelId));
const navigateToNextChapter = () =>
navigation.replace("Chapter", {
chapterUrl: chapter.nextChapter,
sourceId,
novelUrl,
novelId,
chapterId: chapterId + 1,
chapterName: chapter.chapterName,
});
if (isCloseToBottom(nativeEvent)) {
dispatch(markChapterReadAction(chapterId, novelId));
updateTracker();
}
};
const scrollToInitialPosition = () => {
const scrollToSavedProgress = () => {
if (position && firstLayout) {
position.percentage < 100 &&
scrollViewRef.getNode().scrollTo({
@@ -137,74 +165,28 @@ const Chapter = ({ route, navigation }) => {
},
];
const onScroll = ({ nativeEvent }) => {
const offsetY = nativeEvent.contentOffset.y;
const position =
nativeEvent.contentOffset.y + nativeEvent.layoutMeasurement.height;
const percentage = Math.round(
(position / nativeEvent.contentSize.height) * 100
);
setScrollPercentage(percentage);
dispatch(saveScrollPosition(offsetY, percentage, chapterId, novelId));
if (isCloseToBottom(nativeEvent)) {
dispatch(markChapterReadAction(chapterId, novelId));
updateTracker();
}
};
return (
<>
<CollapsibleHeaderScrollView
headerContainerBackgroundColor="rgba(0,0,0,0.4)"
ref={(ref) => {
scrollViewRef = ref;
}}
ref={(ref) => (scrollViewRef = ref)}
CollapsibleHeaderComponent={
<Appbar.Header
style={{ backgroundColor: "transparent", elevation: 0 }}
>
<Appbar.BackAction
onPress={() => navigation.goBack()}
color="#FFFFFF"
size={26}
style={{ marginRight: 0 }}
/>
<Appbar.Content
title={loading ? "Chapter" : chapter.chapterName}
titleStyle={{ color: "#FFFFFF" }}
/>
{!loading && (
<>
<Appbar.Action
icon="chevron-left"
size={26}
disabled={!chapter.prevChapter}
onPress={navigateToPreviousChapter}
color="#FFFFFF"
/>
<Appbar.Action
icon="chevron-right"
size={26}
disabled={!chapter.nextChapter}
onPress={navigateToNextChapter}
color="#FFFFFF"
/>
</>
)}
<Appbar.Action
icon="dots-vertical"
size={26}
onPress={() => readerSheetRef.current.show()}
color="#FFFFFF"
/>
</Appbar.Header>
<ChapterAppbar
navigation={navigation}
sourceId={sourceId}
novelId={novelId}
novelUrl={novelUrl}
novelName={novelName}
chapterName={chapterName}
chapterId={chapterId}
chapter={chapter}
theme={theme}
readerSheetRef={readerSheetRef}
/>
}
headerHeight={Constants.statusBarHeight + 60}
headerHeight={Constants.statusBarHeight + 56}
contentContainerStyle={[
styles.container,
styles.screenContainer,
{ backgroundColor: readerBackground(reader.theme) },
]}
onScroll={onScroll}
@@ -219,7 +201,7 @@ const Chapter = ({ route, navigation }) => {
) : (
<Text
style={readerStyles}
onLayout={scrollToInitialPosition}
onLayout={scrollToSavedProgress}
selectable={true}
>
{chapter.chapterText.trim()}
@@ -249,7 +231,7 @@ const Chapter = ({ route, navigation }) => {
export default Chapter;
const styles = StyleSheet.create({
container: {
screenContainer: {
flexGrow: 1,
paddingVertical: 10,
},
@@ -0,0 +1,95 @@
import React, { useEffect, useState } from "react";
import { Appbar } from "react-native-paper";
import {
getNextChapterFromDB,
getPrevChapterFromDB,
} from "../../../Database/queries/ChapterQueries";
const ChapterAppbar = ({
navigation,
sourceId,
novelId,
novelName,
novelUrl,
chapterId,
chapterName,
theme,
readerSheetRef,
}) => {
const [nextChapter, setNextChapter] = useState({});
const [prevChapter, setPrevChapter] = useState({});
const setPrevAndNextChap = async () => {
const nextChap = await getNextChapterFromDB(novelId, chapterId);
const prevChap = await getPrevChapterFromDB(novelId, chapterId);
setNextChapter(nextChap);
setPrevChapter(prevChap);
};
useEffect(() => {
setPrevAndNextChap();
}, []);
const navigateToPrevChapter = () =>
navigation.replace("Chapter", {
chapterUrl: prevChapter.chapterUrl,
chapterId: prevChapter.chapterId,
sourceId,
novelUrl,
novelId,
chapterName: prevChapter.chapterName,
novelName: novelName,
});
const navigateToNextChapter = () =>
navigation.replace("Chapter", {
chapterUrl: nextChapter.nextChapter,
sourceId,
novelUrl,
novelId,
chapterId: nextChapter.chapterId,
chapterName: nextChapter.chapterName,
novelName: novelName,
});
return (
<Appbar.Header style={{ backgroundColor: "transparent", elevation: 0 }}>
<Appbar.BackAction
onPress={() => navigation.goBack()}
color="#FFFFFF"
size={26}
style={{ marginRight: 0 }}
/>
<Appbar.Content
title={novelName}
titleStyle={{ color: theme.textColorPrimary }}
subtitle={chapterName}
subtitleStyle={{ color: theme.textColorSecondary }}
/>
<Appbar.Action
icon="chevron-left"
size={26}
disabled={!prevChapter}
onPress={navigateToPrevChapter}
color="#FFFFFF"
/>
<Appbar.Action
icon="chevron-right"
size={26}
disabled={!nextChapter}
onPress={navigateToNextChapter}
color="#FFFFFF"
/>
<Appbar.Action
icon="dots-vertical"
size={26}
onPress={() => readerSheetRef.current.show()}
color="#FFFFFF"
/>
</Appbar.Header>
);
};
export default ChapterAppbar;
@@ -77,6 +77,7 @@ const HistoryCard = ({ item, deleteHistoryAction, navigation, theme }) => {
novelUrl: item.novelUrl,
novelId: item.novelId,
chapterName: item.chapterName,
novelName: item.novelName,
})
}
/>
+4 -4
View File
@@ -13,11 +13,11 @@ const GridSizeModal = ({
theme,
}) => {
const gridSizes = {
1: "XS",
2: "S",
5: "XS",
4: "S",
3: "M",
4: "L",
5: "XL",
2: "L",
1: "XL",
};
return (
+1
View File
@@ -81,6 +81,7 @@ const Novel = ({ route, navigation }) => {
position={position}
downloading={downloading}
selected={selected}
novelName={novelName}
setSelected={setSelected}
chapterActionsSheetRef={chapterActionsSheetRef}
/>
@@ -43,6 +43,7 @@ const ChapterActionsSheet = ({
paddingBottom: 8,
fontSize: 16,
}}
numberOfLines={1}
>
{selected?.chapterName}
</Text>
+3 -4
View File
@@ -9,6 +9,7 @@ import {
const ChapterItem = ({
novelUrl,
novelName,
chapter,
sourceId,
dispatch,
@@ -31,9 +32,7 @@ const ChapterItem = ({
chapterId: chapter.chapterId,
chapterUrl: chapter.chapterUrl,
chapterName: chapter.chapterName,
position: !chapter.read
? position && position[chapter.chapterId]
: 0,
novelName: novelName,
});
const displayDownloadButton = () => {
@@ -179,7 +178,7 @@ const ChapterItem = ({
<Text
style={[
{
color: theme.textColorSecondary,
color: theme.textColorHint,
fontSize: 12,
marginLeft: 0,
},
@@ -23,6 +23,7 @@ const ReadButton = ({ navigation, novel, chapters, theme, lastRead }) => {
novelId: lastRead.novelId,
sourceId: novel.sourceId,
chapterName: lastRead.chapterName,
novelName: novel.novelName,
});
}}
>
+1
View File
@@ -47,6 +47,7 @@ const Updates = ({ navigation }) => {
novelUrl: item.novelUrl,
chapterName: item.chapterName,
novelId: item.novelId,
novelName: item.novelName,
});
const renderItem = ({ item }) => (
+12 -8
View File
@@ -18,20 +18,24 @@ const UpdateCard = ({ item, theme, onPress }) => {
/>
<View style={styles.chapterDetails}>
<Text
style={{
color: theme.textColorPrimary,
fontSize: 14,
}}
style={[
{ color: theme.textColorPrimary, fontSize: 14 },
item.read && {
color: theme.textColorHint,
},
]}
numberOfLines={1}
>
{item.novelName}
</Text>
<View style={{ flexDirection: "row" }}>
<Text
style={{
color: theme.textColorPrimary,
fontSize: 12,
}}
style={[
{ color: theme.textColorPrimary, fontSize: 12 },
item.read && {
color: theme.textColorHint,
},
]}
numberOfLines={1}
>
{`${item.chapterName} · ${moment(
-1
View File
@@ -12,7 +12,6 @@ export const fetchSources = async () => {
export const fetchNovel = async (sourceId, novelUrl) => {
const url = `https://lnreader-extensions.vercel.app/api/${sourceId}/novel/${novelUrl}`;
console.log(url);
let data = await fetch(url);
let res = await data.json();
-36
View File
@@ -1,36 +0,0 @@
import { getChapter } from "../../Database/queries/ChapterQueries";
import { fetchChapter } from "../../Services/Source/source";
import { SAVE_SCROLL_POSITION } from "../preferences/preference.types";
import { CHAPTER_LOADING, GET_CHAPTER } from "./chapter.types";
export const getChapterAction =
(sourceId, novelUrl, chapterUrl, chapterId) => async (dispatch) => {
dispatch({ type: CHAPTER_LOADING });
/**
* Check if chapter is downloaded.
*/
let chapter = await getChapter(chapterId);
if (!chapter) {
/**
* Fetch chapter from source.
*/
chapter = await fetchChapter(sourceId, novelUrl, chapterUrl);
}
dispatch({ type: GET_CHAPTER, payload: chapter });
};
export const saveScrollPosition =
(position, percentage, chapterId, novelId) => async (dispatch) => {
dispatch({
type: SAVE_SCROLL_POSITION,
payload: {
position,
percentage,
chapterId,
novelId,
},
});
};
-21
View File
@@ -1,21 +0,0 @@
import { CHAPTER_LOADING, GET_CHAPTER } from "./chapter.types";
const initialState = {
chapter: {},
loading: true,
};
const chapterReducer = (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case CHAPTER_LOADING:
return { ...state, loading: true };
case GET_CHAPTER:
return { ...state, chapter: payload, loading: false };
default:
return state;
}
};
export default chapterReducer;
-2
View File
@@ -1,2 +0,0 @@
export const GET_CHAPTER = "GET_CHAPTER";
export const CHAPTER_LOADING = "CHAPTER_LOADING";
@@ -0,0 +1,14 @@
import { SAVE_SCROLL_POSITION } from "../preferences/preference.types";
export const saveScrollPosition =
(position, percentage, chapterId, novelId) => async (dispatch) => {
dispatch({
type: SAVE_SCROLL_POSITION,
payload: {
position,
percentage,
chapterId,
novelId,
},
});
};
+3 -1
View File
@@ -11,9 +11,11 @@ export const getSourcesAction = () => async (dispatch) => {
};
export const searchSourcesAction = (searchText) => async (dispatch) => {
const res = searchText.toLowerCase();
dispatch({
type: SEARCH_SOURCES,
payload: searchText,
payload: res,
});
};
+1 -1
View File
@@ -27,7 +27,7 @@ const sourceReducer = (state = initialState, action) => {
return {
...state,
search: state.sources.filter((source) =>
source.sourceName.includes(payload)
source.sourceName.toLowerCase().includes(payload)
),
};
default: