Add update chapter progress after reading
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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
@@ -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 },
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user