Add update chapter progress after reading

This commit is contained in:
Rajarshee Chatterjee
2021-04-30 11:16:14 +05:30
parent 0a476ba6b4
commit 458cdb2c2a
9 changed files with 257 additions and 50 deletions
+77 -15
View File
@@ -148,24 +148,86 @@ const NovelCover = ({ item, onPress, libraryStatus }) => {
style={styles.listView}
onPress={onPress}
>
<>
<Image
source={{ uri: item.novelCover }}
style={styles.extensionIcon}
/>
<Text
<View
style={{
flex: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
>
<View
style={{
flex: 1,
color: theme.textColorPrimary,
marginLeft: 16,
fontSize: 15,
paddingRight: 8,
flexDirection: "row",
alignItems: "center",
}}
numberOfLines={1}
>
{item.novelName}
</Text>
</>
<Image
source={{ uri: item.novelCover }}
style={styles.extensionIcon}
/>
<Text
style={{
color: theme.textColorPrimary,
marginLeft: 16,
fontSize: 15,
paddingRight: 8,
}}
numberOfLines={1}
>
{item.novelName}
</Text>
</View>
<View
style={{
flexDirection: "row",
alignItems: "center",
}}
>
{item.chaptersDownloaded && (
<Text
style={[
{
backgroundColor: "#47a84a",
color: "#FFFFFF",
borderTopLeftRadius: 4,
borderBottomLeftRadius: 4,
paddingTop: 2,
paddingHorizontal: 4,
fontSize: 12,
},
!item.chaptersUnread && {
borderTopRightRadius: 4,
borderBottomRightRadius: 4,
},
]}
>
{item.chaptersDownloaded}
</Text>
)}
{item.chaptersUnread && (
<Text
style={[
{
backgroundColor: "#2979FF",
color: "#FFFFFF",
borderTopRightRadius: 4,
borderBottomRightRadius: 4,
paddingTop: 2,
paddingHorizontal: 4,
fontSize: 12,
},
!item.chaptersDownloaded && {
borderTopLeftRadius: 4,
borderBottomLeftRadius: 4,
},
]}
>
{item.chaptersUnread}
</Text>
)}
</View>
</View>
</TouchableRipple>
);
};
+16
View File
@@ -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 },
});
};
+16
View File
@@ -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;
}
+2
View File
@@ -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";
+34 -1
View File
@@ -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 (
<Provider>
<CollapsibleHeaderScrollView
@@ -154,6 +173,20 @@ const ChapterItem = ({
onScroll={({ nativeEvent }) => {
if (isCloseToBottom(nativeEvent)) {
markChapterReadAction(chapterId);
if (
isTracked &&
chapterNumber &&
chapterNumber >
isTracked.my_list_status.num_chapters_read
) {
dispatch(
updateChaptersRead(
isTracked.id,
tracker.access_token,
chapterNumber
)
);
}
}
}}
>
-2
View File
@@ -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,
});
+28 -2
View File
@@ -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()) && (
<>
<Divider theme={theme} />
<ListSubHeader theme={theme}>
Settings
</ListSubHeader>
<ListItem
title="Revalidate MyAnimeList"
onPress={() => {
const res = malTokenWatcher(tracker);
dispatch(setTracker(res));
}}
theme={theme}
/>
</>
)}
</ListSection>
<Portal>
<Modal
visible={visible}
+27 -29
View File
@@ -12,7 +12,7 @@ import ChapterCard from "./components/ChapterCard";
import NovelInfoHeader from "./components/NovelHeader";
import ChaptersSettingsSheet from "./components/ChaptersSettingsSheet";
import { connect, useSelector } from "react-redux";
import { connect } from "react-redux";
import {
getNovelAction,
@@ -44,13 +44,18 @@ const Novel = ({
let chaptersSettingsSheetRef = useRef(null);
let trackerSheetRef = useRef(null);
const [sort, setSort] = useState("");
const [sort, setSort] = useState("ORDER BY chapterId ASC");
const [filter, setFilter] = useState("");
useEffect(() => {
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 }) => (
<ChapterCard
novelUrl={novelUrl}
@@ -59,13 +64,6 @@ const Novel = ({
/>
);
const onRefresh = async () => {
await updateNovelAction(sourceId, novelUrl, novelId);
ToastAndroid.show(`Updated ${novelName}`, ToastAndroid.SHORT);
};
// Tracker Modal
return (
<Provider>
<View
@@ -103,23 +101,25 @@ const Novel = ({
/>
}
/>
<Portal>
<ChaptersSettingsSheet
novelUrl={novelUrl}
bottomSheetRef={chaptersSettingsSheetRef}
sortAndFilterChapters={sortAndFilterChapters}
sort={sort}
filter={filter}
setSort={setSort}
setFilter={setFilter}
/>
<TrackSheet
bottomSheetRef={trackerSheetRef}
novelId={novel.novelId}
novelName={novel.novelName}
theme={theme}
/>
</Portal>
{!loading && (
<Portal>
<ChaptersSettingsSheet
novelUrl={novelUrl}
bottomSheetRef={chaptersSettingsSheetRef}
sortAndFilterChapters={sortAndFilterChapters}
sort={sort}
filter={filter}
setSort={setSort}
setFilter={setFilter}
/>
<TrackSheet
bottomSheetRef={trackerSheetRef}
novelId={novel.novelId}
novelName={novel.novelName}
theme={theme}
/>
</Portal>
)}
</View>
</Provider>
);
@@ -140,7 +140,5 @@ export default connect(mapStateToProps, {
})(Novel);
const styles = StyleSheet.create({
container: {
flex: 1,
},
container: { flex: 1 },
});
+57 -1
View File
@@ -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);
}
}
};