From 458cdb2c2a8dd5ba1fab6bd46253be6eb967dbb9 Mon Sep 17 00:00:00 2001 From: Rajarshee Chatterjee Date: Fri, 30 Apr 2021 11:16:14 +0530 Subject: [PATCH] Add update chapter progress after reading --- src/components/NovelCover.js | 92 +++++++++++++++++++++++----- src/redux/tracker/tracker.actions.js | 16 +++++ src/redux/tracker/tracker.reducer.js | 16 +++++ src/redux/tracker/tracker.types.js | 2 + src/screens/Chapter/Chapter.js | 35 ++++++++++- src/screens/Library/Library.js | 2 - src/screens/More/Trackers.js | 30 ++++++++- src/screens/Novel/Novel.js | 56 ++++++++--------- src/trackers/MyAnimeList.js | 58 +++++++++++++++++- 9 files changed, 257 insertions(+), 50 deletions(-) diff --git a/src/components/NovelCover.js b/src/components/NovelCover.js index c79662c4..d1171813 100644 --- a/src/components/NovelCover.js +++ b/src/components/NovelCover.js @@ -148,24 +148,86 @@ const NovelCover = ({ item, onPress, libraryStatus }) => { style={styles.listView} onPress={onPress} > - <> - - + - {item.novelName} - - + + + {item.novelName} + + + + {item.chaptersDownloaded && ( + + {item.chaptersDownloaded} + + )} + {item.chaptersUnread && ( + + {item.chaptersUnread} + + )} + + ); }; diff --git a/src/redux/tracker/tracker.actions.js b/src/redux/tracker/tracker.actions.js index c7a44e7f..476085a0 100644 --- a/src/redux/tracker/tracker.actions.js +++ b/src/redux/tracker/tracker.actions.js @@ -5,8 +5,11 @@ import { TRACK_NOVEL, UNTRACK_NOVEL, UPDATE_TRACKER, + UPDATE_CHAPTERS_READ, } from "./tracker.types"; +import { updateMalChaptersRead } from "../../trackers/MyAnimeList"; + export const setTracker = (res) => async (dispatch) => { dispatch({ type: SET_TRACKER, @@ -39,3 +42,16 @@ export const updateTracker = (malId, accessToken, body) => async (dispatch) => { export const untrackNovel = (id) => async (dispatch) => { dispatch({ type: UNTRACK_NOVEL, payload: id }); }; + +export const updateChaptersRead = (malId, accessToken, chaptersRead) => async ( + dispatch +) => { + let res = await updateMalChaptersRead(malId, accessToken, { + num_chapters_read: chaptersRead, + }); + + dispatch({ + type: UPDATE_CHAPTERS_READ, + payload: { malId, chaptersRead: res }, + }); +}; diff --git a/src/redux/tracker/tracker.reducer.js b/src/redux/tracker/tracker.reducer.js index 2e88e2b4..045444e8 100644 --- a/src/redux/tracker/tracker.reducer.js +++ b/src/redux/tracker/tracker.reducer.js @@ -3,6 +3,7 @@ import { SET_TRACKER, TRACK_NOVEL, UNTRACK_NOVEL, + UPDATE_CHAPTERS_READ, UPDATE_TRACKER, } from "./tracker.types"; @@ -57,6 +58,21 @@ const trackerReducer = (state = initialState, action) => { (id) => id === payload ), }; + case UPDATE_CHAPTERS_READ: + return { + ...state, + trackedNovels: state.trackedNovels.map((novel) => + novel.id === payload.malId + ? { + ...novel, + my_list_status: { + ...novel.my_list_status, + num_chapters_read: payload.chaptersRead, + }, + } + : novel + ), + }; default: return state; } diff --git a/src/redux/tracker/tracker.types.js b/src/redux/tracker/tracker.types.js index e2323223..b08f1262 100644 --- a/src/redux/tracker/tracker.types.js +++ b/src/redux/tracker/tracker.types.js @@ -5,3 +5,5 @@ export const TRACK_NOVEL = "TRACK_NOVEL"; export const UNTRACK_NOVEL = "UNTRACK_NOVEL"; export const UPDATE_TRACKER = "UPDATE_TRACKER"; + +export const UPDATE_CHAPTERS_READ = "UPDATE_CHAPTERS_READ"; diff --git a/src/screens/Chapter/Chapter.js b/src/screens/Chapter/Chapter.js index 2dddf5e4..8e6279f9 100644 --- a/src/screens/Chapter/Chapter.js +++ b/src/screens/Chapter/Chapter.js @@ -12,7 +12,9 @@ import { markChapterReadAction, } from "../../redux/novel/novel.actions"; -import { connect } from "react-redux"; +import { connect, useSelector, useDispatch } from "react-redux"; +import { parseChapterNumber } from "../../services/updates"; +import { updateChaptersRead } from "../../redux/tracker/tracker.actions"; const ChapterItem = ({ route, @@ -35,6 +37,19 @@ const ChapterItem = ({ let _panel = useRef(null); + const dispatch = useDispatch(); + + const trackedNovels = useSelector( + (state) => state.trackerReducer.trackedNovels + ); + const tracker = useSelector((state) => state.trackerReducer.tracker); + + let isTracked = false; + + if (!loading) { + isTracked = trackedNovels.find((obj) => obj.novelId === novelId); + } + useEffect(() => { getChapterAction(extensionId, chapterUrl, novelUrl, chapterId); insertHistoryAction(novelId, chapterId); @@ -56,6 +71,8 @@ const ChapterItem = ({ return textColor; }; + const chapterNumber = !loading && parseChapterNumber(chapter.chapterName); + const readerLineHeight = (val) => { const lineHeight = { 12: 20, @@ -79,6 +96,8 @@ const ChapterItem = ({ ); }; + // !loading && parseChapterNumber(chapter.chapterName); + return ( { if (isCloseToBottom(nativeEvent)) { markChapterReadAction(chapterId); + if ( + isTracked && + chapterNumber && + chapterNumber > + isTracked.my_list_status.num_chapters_read + ) { + dispatch( + updateChaptersRead( + isTracked.id, + tracker.access_token, + chapterNumber + ) + ); + } } }} > diff --git a/src/screens/Library/Library.js b/src/screens/Library/Library.js index 02f87d47..f046e61b 100644 --- a/src/screens/Library/Library.js +++ b/src/screens/Library/Library.js @@ -30,7 +30,6 @@ const LibraryScreen = ({ getLibraryAction, searchLibraryAction, setNovel, - // searchResults, itemsPerRow, }) => { const [refreshing, setRefreshing] = useState(false); @@ -132,7 +131,6 @@ const mapStateToProps = (state) => ({ theme: state.themeReducer.theme, itemsPerRow: state.settingsReducer.itemsPerRow, novels: state.libraryReducer.novels, - // searchResults: state.libraryReducer.searchResults, loading: state.libraryReducer.loading, }); diff --git a/src/screens/More/Trackers.js b/src/screens/More/Trackers.js index 2954f0b8..0496f179 100644 --- a/src/screens/More/Trackers.js +++ b/src/screens/More/Trackers.js @@ -3,11 +3,20 @@ import { View } from "react-native"; import { Modal, Portal, Text, Button, Provider } from "react-native-paper"; import * as WebBrowser from "expo-web-browser"; -import { myAnimeListConfig, getAccessToken } from "../../trackers/MyAnimeList"; +import { + myAnimeListConfig, + getAccessToken, + malTokenWatcher, +} from "../../trackers/MyAnimeList"; import { Appbar } from "../../components/Appbar"; import { useSelector, useDispatch } from "react-redux"; -import { ListItem, ListSection, ListSubHeader } from "../../components/List"; +import { + Divider, + ListItem, + ListSection, + ListSubHeader, +} from "../../components/List"; import { removeTracker, setTracker } from "../../redux/tracker/tracker.actions"; const TrackerScreen = ({ navigation }) => { @@ -70,7 +79,24 @@ const TrackerScreen = ({ navigation }) => { right={tracker && "check"} theme={theme} /> + {tracker.expires_in < new Date(Date.now()) && ( + <> + + + Settings + + { + const res = malTokenWatcher(tracker); + dispatch(setTracker(res)); + }} + theme={theme} + /> + + )} + { getNovelAction(followed, sourceId, novelUrl, novelId, sort, filter); }, [getNovelAction, sort, filter]); + const onRefresh = async () => { + await updateNovelAction(sourceId, novelUrl, novelId); + ToastAndroid.show(`Updated ${novelName}`, ToastAndroid.SHORT); + }; + const renderChapterCard = ({ item }) => ( ); - const onRefresh = async () => { - await updateNovelAction(sourceId, novelUrl, novelId); - ToastAndroid.show(`Updated ${novelName}`, ToastAndroid.SHORT); - }; - - // Tracker Modal - return ( } /> - - - - + {!loading && ( + + + + + )} ); @@ -140,7 +140,5 @@ export default connect(mapStateToProps, { })(Novel); const styles = StyleSheet.create({ - container: { - flex: 1, - }, + container: { flex: 1 }, }); diff --git a/src/trackers/MyAnimeList.js b/src/trackers/MyAnimeList.js index 29ac622f..ee816569 100644 --- a/src/trackers/MyAnimeList.js +++ b/src/trackers/MyAnimeList.js @@ -55,7 +55,14 @@ export const getAccessToken = async (code, codeChallenge) => { body: qs.stringify(body), }); - const tokenResponse = await response.json(); + let tokenResponse = await response.json(); + + tokenResponse = { + ...tokenResponse, + expires_in: new Date(Date.now() + tokenResponse.expires_in), + }; + + console.log(tokenResponse); return tokenResponse; }; @@ -113,3 +120,52 @@ export const updateItem = async (malId, bearerToken, body) => { console.log(listItem); return listItem; }; + +export const updateMalChaptersRead = async (malId, bearerToken, body) => { + const url = `${baseApiUrl}/manga/${malId}/my_list_status`; + + const headers = { + Authorization: "Bearer " + bearerToken, + "Content-Type": "application/x-www-form-urlencoded", + }; + + const res = await fetch(url, { + method: "PUT", + headers, + body: qs.stringify(body), + }); + + const listItem = await res.json(); + console.log(listItem.num_chapters_read); + return listItem.num_chapters_read; +}; + +const revalidateMalToken = async (refreshToken) => { + const headers = { + "Content-Type": "application/x-www-form-urlencoded", + }; + + const body = { + grant_type: "refresh_token", + refresh_token: refreshToken, + }; + + const res = await fetch(url, { + headers, + method: "POST", + body: qs.stringify(body), + }); + + let tokenResponse = res.json(); + + return tokenResponse; +}; + +export const malTokenWatcher = async (tracker) => { + if (tracker) { + const date = new Date(Date.now()); + if (tracker.expires_in < date) { + return await revalidateMalToken(tracker.refresh_token); + } + } +};