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);
+ }
+ }
+};