Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3af0b1afbc | |||
| b9695e7f56 | |||
| 7c068aa7d5 | |||
| 8b182f1e31 | |||
| 5029513424 | |||
| f4c6a3b4a1 | |||
| 27f5e20107 | |||
| c249b52304 | |||
| c179e0e5bd | |||
| b78382cc03 | |||
| 9df3694c04 | |||
| dfd300c358 | |||
| 76c108597d | |||
| 23ff02674e | |||
| 55e2371f5d |
@@ -10,7 +10,7 @@ import Router from "./src/navigation/Router";
|
||||
import { Provider } from "react-redux";
|
||||
import { persistor, store } from "./src/redux/store";
|
||||
import { PersistGate } from "redux-persist/lib/integration/react";
|
||||
import { createDB } from "./src/database/DBHelper";
|
||||
import { createDB, deleteDb } from "./src/database/DBHelper";
|
||||
|
||||
const getFonts = () =>
|
||||
Font.loadAsync({
|
||||
@@ -21,6 +21,7 @@ const App = () => {
|
||||
const [fontsLoaded, setFontsLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// deleteDb();
|
||||
createDB();
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ import {
|
||||
createChapterIdIndexQuery,
|
||||
} from "./tables/HistoryTable";
|
||||
import { createDownloadTableQuery } from "./tables/DownloadTable";
|
||||
import {
|
||||
createUpdatesTableQuery,
|
||||
updatesSeedDataQuery,
|
||||
} from "./tables/UpdateTable";
|
||||
|
||||
/**
|
||||
* Database Version = 2
|
||||
@@ -29,6 +33,7 @@ export const createDB = () => {
|
||||
tx.executeSql(createChapterTableQuery);
|
||||
tx.executeSql(createHistoryTableQuery);
|
||||
tx.executeSql(createDownloadTableQuery);
|
||||
tx.executeSql(createUpdatesTableQuery);
|
||||
|
||||
//Db indexes
|
||||
tx.executeSql(createUrlIndexQuery);
|
||||
@@ -45,5 +50,6 @@ export const deleteDb = () => {
|
||||
tx.executeSql("DROP TABLE chapters");
|
||||
tx.executeSql("DROP TABLE history");
|
||||
tx.executeSql("DROP TABLE downloads");
|
||||
tx.executeSql("DROP TABLE updates");
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,24 +5,43 @@ const insertChaptersQuery = `INSERT INTO chapters (chapterUrl, chapterName, rele
|
||||
|
||||
export const insertChapters = async (novelId, chapters) => {
|
||||
db.transaction((tx) => {
|
||||
chapters.map((chapter) =>
|
||||
tx.executeSql(insertChaptersQuery, [
|
||||
chapter.chapterUrl,
|
||||
chapter.chapterName,
|
||||
chapter.releaseDate,
|
||||
novelId,
|
||||
])
|
||||
chapters.map(
|
||||
(chapter) =>
|
||||
tx.executeSql(
|
||||
insertChaptersQuery,
|
||||
[
|
||||
chapter.chapterUrl,
|
||||
chapter.chapterName,
|
||||
chapter.releaseDate,
|
||||
novelId,
|
||||
],
|
||||
(txObj, res) => {},
|
||||
(txObj, error) =>
|
||||
console.log(
|
||||
"Error ",
|
||||
error,
|
||||
" Chapter URL: ",
|
||||
chapter.chapterUrl,
|
||||
" Novel ID ",
|
||||
novelId
|
||||
)
|
||||
),
|
||||
(txObj, res) => {
|
||||
console.log("Success");
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const getChaptersQuery = `SELECT * FROM chapters WHERE novelId = ? ORDER BY chapterId ASC`;
|
||||
const getChaptersQuery = (sort, filter) =>
|
||||
`SELECT * FROM chapters WHERE novelId = ? ${filter ?? ""} ${sort ?? ""}`;
|
||||
|
||||
export const getChapters = (novelId) => {
|
||||
export const getChapters = (novelId, sort, filter) => {
|
||||
return new Promise((resolve, reject) =>
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
getChaptersQuery,
|
||||
getChaptersQuery(sort, filter),
|
||||
[novelId],
|
||||
(txObj, { rows: { _array } }) => {
|
||||
resolve(_array);
|
||||
@@ -94,6 +113,8 @@ export const downloadChapter = async (
|
||||
) => {
|
||||
const downloadUrl = `https://lnreader-extensions.herokuapp.com/api/${extensionId}/novel/${novelUrl}${chapterUrl}`;
|
||||
|
||||
// console.log(downloadUrl);
|
||||
|
||||
const response = await fetch(downloadUrl);
|
||||
const chapter = await response.json();
|
||||
|
||||
@@ -138,6 +159,26 @@ export const deleteChapter = async (chapterId) => {
|
||||
});
|
||||
};
|
||||
|
||||
// export const deleteAllChapters = async (novelId) => {
|
||||
// const updateIsDownloadedQuery = `UPDATE chapters SET downloaded = 0 WHERE novelId=?`;
|
||||
// const deleteChapterQuery = `DELETE FROM downloads WHERE downloadChapterId=?`;
|
||||
|
||||
// db.transaction((tx) => {
|
||||
// tx.executeSql(
|
||||
// updateIsDownloadedQuery,
|
||||
// [chapterId],
|
||||
// (tx, res) => console.log(`Chaptermarked not downapdeod deleted`),
|
||||
// (txObj, error) => console.log("Error ", error)
|
||||
// );
|
||||
// tx.executeSql(
|
||||
// deleteChapterQuery,
|
||||
// [chapterId],
|
||||
// (tx, res) => console.log(`Chapter deleted`),
|
||||
// (txObj, error) => console.log("Error ", error)
|
||||
// );
|
||||
// });
|
||||
// };
|
||||
|
||||
const getLastReadChapterQuery = `
|
||||
SELECT chapters.*
|
||||
FROM history
|
||||
|
||||
@@ -30,7 +30,7 @@ export const getHistory = async () => {
|
||||
export const insertHistory = async (novelId, chapterId) => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"INSERT OR REPLACE INTO history (historyNovelId, historyChapterId) VALUES (?, ?)",
|
||||
"INSERT OR REPLACE INTO history (historyNovelId, historyChapterId, historyTimeRead) VALUES (?, ?, (datetime('now','localtime')))",
|
||||
[novelId, chapterId],
|
||||
(txObj, res) => {},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
|
||||
@@ -44,7 +44,26 @@ export const searchLibrary = (searchText) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
`SELECT * FROM novels WHERE followed = 1 AND novelName LIKE '%${searchText}%'`,
|
||||
`
|
||||
SELECT novels.*, C.chaptersUnread, D.chaptersDownloaded
|
||||
FROM novels
|
||||
LEFT JOIN (
|
||||
SELECT chapters.novelId, COUNT(*) AS chaptersUnread
|
||||
FROM chapters
|
||||
WHERE chapters.read = 0
|
||||
GROUP BY chapters.novelId
|
||||
) AS C
|
||||
ON novels.novelId = C.novelId
|
||||
LEFT JOIN (
|
||||
SELECT chapters.novelId, COUNT(*) AS chaptersDownloaded
|
||||
FROM chapters
|
||||
WHERE chapters.downloaded = 1
|
||||
GROUP BY chapters.novelId
|
||||
) AS D
|
||||
ON novels.novelId = D.novelId
|
||||
WHERE novels.followed = 1 AND novelName LIKE '%${searchText}%'
|
||||
GROUP BY novels.novelId
|
||||
`,
|
||||
null,
|
||||
(txObj, { rows: { _array } }) => resolve(_array),
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
|
||||
@@ -79,7 +79,7 @@ export const deleteNovelCache = () => {
|
||||
"DELETE FROM novels WHERE followed = 0",
|
||||
null,
|
||||
(txObj, res) => {
|
||||
console.log("Deleted");
|
||||
console.log("Cleared Novel Cache");
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as SQLite from "expo-sqlite";
|
||||
const db = SQLite.openDatabase("lnreader.db");
|
||||
|
||||
const getHistoryQuery = `
|
||||
SELECT chapters.chapterId, 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
|
||||
JOIN novels
|
||||
ON updates.novelId = novels.novelId
|
||||
ORDER BY updates.updateTime DESC`;
|
||||
|
||||
export const getUpdates = async () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
getHistoryQuery,
|
||||
null,
|
||||
(txObj, { rows: { _array } }) => {
|
||||
resolve(_array);
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -6,6 +6,6 @@ export const createDownloadTableQuery = `
|
||||
chapterText TEXT,
|
||||
nextChapter TEXT,
|
||||
prevChapter TEXT,
|
||||
FOREIGN KEY (downloadChapterId) REFERENCES chapters(chapter_id)
|
||||
FOREIGN KEY (downloadChapterId) REFERENCES chapters(chapterId)
|
||||
ON DELETE CASCADE
|
||||
)`;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export const createUpdatesTableQuery = `
|
||||
CREATE TABLE IF NOT EXISTS updates(
|
||||
updateId INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
chapterId INTEGER NOT NULL,
|
||||
novelId INTEGER NOT NULL,
|
||||
updateTime DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(novelId, chapterId),
|
||||
FOREIGN KEY (chapterId) REFERENCES chapters(chapterId),
|
||||
FOREIGN KEY (novelId) REFERENCES novels(novelId)
|
||||
ON DELETE CASCADE
|
||||
)`;
|
||||
+15
-18
@@ -10,6 +10,7 @@ import { setStatusBarStyle } from "expo-status-bar";
|
||||
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
|
||||
import Library from "../screens/Library/Library";
|
||||
import Updates from "../screens/Updates/Updates";
|
||||
import History from "../screens/History/History";
|
||||
import Browse from "../screens/Browse/Browse";
|
||||
|
||||
@@ -21,8 +22,10 @@ import NovelItem from "../screens/Novel/Novel";
|
||||
import ChapterItem from "../screens/Chapter/Chapter";
|
||||
|
||||
// Extensions
|
||||
import BoxNovel from "../screens/Extensions/boxnovel/BoxNovel";
|
||||
import ReadLightNovel from "../screens/Extensions/readlightnovel/ReadLightNovel";
|
||||
import BoxNovel from "../screens/extensions/boxnovel/BoxNovel";
|
||||
import ReadLightNovel from "../screens/extensions/readlightnovel/ReadLightNovel";
|
||||
import FastNovel from "../screens/extensions/fastnovel/FastNovel";
|
||||
import ReadNovelFull from "../screens/extensions/readnovelfull/ReadNovelFull";
|
||||
|
||||
const Stack = createStackNavigator();
|
||||
|
||||
@@ -33,28 +36,22 @@ const stackNavigatorConfig = {
|
||||
...TransitionPresets.RevealFromBottomAndroid,
|
||||
};
|
||||
|
||||
const MoreStack = () => {
|
||||
const SettingsStack = () => {
|
||||
return (
|
||||
<Stack.Navigator screenOptions={stackNavigatorConfig}>
|
||||
<Stack.Screen name="More" component={More} />
|
||||
<Stack.Screen name="About" component={About} />
|
||||
<Stack.Screen name="Settings" component={Settings} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
const BoxNovelStack = () => {
|
||||
const ExtensionStack = () => {
|
||||
return (
|
||||
<Stack.Navigator screenOptions={stackNavigatorConfig}>
|
||||
<Stack.Screen name="BoxNovel" component={BoxNovel} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
const ReadLightNovelStack = () => {
|
||||
return (
|
||||
<Stack.Navigator screenOptions={stackNavigatorConfig}>
|
||||
<Stack.Screen name="ReadLightNovel" component={ReadLightNovel} />
|
||||
<Stack.Screen name="FastNovel" component={FastNovel} />
|
||||
<Stack.Screen name="ReadNovelFull" component={ReadNovelFull} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
@@ -83,7 +80,7 @@ const BottomNavigator = () => {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{/*
|
||||
|
||||
<Tab.Screen
|
||||
name="Updates"
|
||||
component={Updates}
|
||||
@@ -96,7 +93,7 @@ const BottomNavigator = () => {
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/> */}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="History"
|
||||
component={History}
|
||||
@@ -125,7 +122,7 @@ const BottomNavigator = () => {
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="More"
|
||||
component={MoreStack}
|
||||
component={More}
|
||||
options={{
|
||||
tabBarIcon: ({ color }) => (
|
||||
<MaterialCommunityIcons
|
||||
@@ -164,10 +161,10 @@ const Router = () => {
|
||||
headerTintColor: "white",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen name="BoxNovelStack" component={BoxNovelStack} />
|
||||
<Stack.Screen name="SettingsStack" component={SettingsStack} />
|
||||
<Stack.Screen
|
||||
name="ReadLightNovelStack"
|
||||
component={ReadLightNovelStack}
|
||||
name="ExtensionStack"
|
||||
component={ExtensionStack}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
</Provider>
|
||||
|
||||
@@ -35,6 +35,7 @@ export const getChapterAction = (
|
||||
} else {
|
||||
chapter = await fetchChapter(extensionId, novelUrl, chapterUrl);
|
||||
}
|
||||
console.log(isDownloaded);
|
||||
dispatch({ type: GET_CHAPTER, payload: chapter });
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { GET_LIBRARY_NOVELS } from "./library.types";
|
||||
import {
|
||||
GET_LIBRARY_NOVELS,
|
||||
GET_LIBRARY_SEARCH_RESULTS,
|
||||
} from "./library.types";
|
||||
import {
|
||||
getLibrary,
|
||||
searchLibrary,
|
||||
@@ -14,11 +17,10 @@ export const getLibraryAction = () => async (dispatch) => {
|
||||
};
|
||||
|
||||
export const searchLibraryAction = (searchText) => async (dispatch) => {
|
||||
console.log(searchText);
|
||||
const res = await searchLibrary(searchText);
|
||||
|
||||
dispatch({
|
||||
type: GET_LIBRARY_NOVELS,
|
||||
type: GET_LIBRARY_SEARCH_RESULTS,
|
||||
payload: res,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { FOLLOW_NOVEL, UNFOLLOW_NOVEL } from "../novel/novel.types";
|
||||
import { GET_LIBRARY_NOVELS } from "./library.types";
|
||||
import {
|
||||
GET_LIBRARY_NOVELS,
|
||||
GET_LIBRARY_SEARCH_RESULTS,
|
||||
} from "./library.types";
|
||||
|
||||
const initialState = {
|
||||
novels: [],
|
||||
searchResults: [],
|
||||
loading: true,
|
||||
};
|
||||
|
||||
@@ -11,7 +15,9 @@ const libraryReducer = (state = initialState, action) => {
|
||||
|
||||
switch (type) {
|
||||
case GET_LIBRARY_NOVELS:
|
||||
return { novels: payload, loading: false };
|
||||
return { ...state, novels: payload, loading: false };
|
||||
case GET_LIBRARY_SEARCH_RESULTS:
|
||||
return { ...state, searchResults: payload, loading: false };
|
||||
case FOLLOW_NOVEL:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export const GET_LIBRARY_NOVELS = "GET_LIBRARY_NOVELS";
|
||||
export const GET_LIBRARY_SEARCH_RESULTS = "GET_LIBRARY_SEARCH_RESULTS";
|
||||
|
||||
@@ -14,9 +14,6 @@ import {
|
||||
CHAPTER_DOWNLOADED,
|
||||
CHAPTER_DELETED,
|
||||
UPDATE_NOVEL,
|
||||
UPDATE_LAST_READ,
|
||||
FOLLOW_NOVEL,
|
||||
UNFOLLOW_NOVEL,
|
||||
} from "./novel.types";
|
||||
|
||||
import { updateNovel } from "../../services/updates";
|
||||
@@ -24,7 +21,6 @@ import { fetchChapter, fetchNovel } from "../../source/Source";
|
||||
import {
|
||||
followNovel,
|
||||
insertNovel,
|
||||
checkNovelInCache,
|
||||
getNovel,
|
||||
} from "../../database/queries/NovelQueries";
|
||||
import {
|
||||
@@ -35,20 +31,24 @@ import {
|
||||
getChapter,
|
||||
downloadChapter,
|
||||
deleteChapter,
|
||||
getLastReadChapter,
|
||||
} from "../../database/queries/ChapterQueries";
|
||||
|
||||
export const setNovel = (novel) => async (dispatch) => {
|
||||
dispatch({ type: SET_NOVEL, payload: novel });
|
||||
};
|
||||
|
||||
export const getNovelAction = (followed, sourceId, novelUrl, novelId) => async (
|
||||
dispatch
|
||||
) => {
|
||||
export const getNovelAction = (
|
||||
followed,
|
||||
sourceId,
|
||||
novelUrl,
|
||||
novelId,
|
||||
sort,
|
||||
filter
|
||||
) => async (dispatch) => {
|
||||
dispatch({ type: LOADING_NOVEL });
|
||||
|
||||
if (followed === 1) {
|
||||
const chapters = await getChapters(novelId);
|
||||
const chapters = await getChapters(novelId, sort, filter);
|
||||
|
||||
dispatch({
|
||||
type: GET_CHAPTERS,
|
||||
@@ -57,11 +57,10 @@ export const getNovelAction = (followed, sourceId, novelUrl, novelId) => async (
|
||||
} else {
|
||||
dispatch({ type: FETCHING_NOVEL });
|
||||
|
||||
const inCache = await checkNovelInCache(novelUrl);
|
||||
const novel = await getNovel(novelUrl);
|
||||
|
||||
if (inCache) {
|
||||
const novel = await getNovel(novelUrl);
|
||||
novel.chapters = await getChapters(novel.novelId);
|
||||
if (novel) {
|
||||
novel.chapters = await getChapters(novel.novelId, sort, filter);
|
||||
dispatch({
|
||||
type: GET_NOVEL,
|
||||
payload: novel,
|
||||
@@ -105,18 +104,6 @@ export const sortAndFilterChapters = (novelUrl, filter, sort) => async (
|
||||
export const followNovelAction = (novel) => async (dispatch) => {
|
||||
await followNovel(novel.followed, novel.novelId);
|
||||
|
||||
if (!novel.followed) {
|
||||
dispatch({
|
||||
type: FOLLOW_NOVEL,
|
||||
payload: novel,
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: UNFOLLOW_NOVEL,
|
||||
payload: novel.novelId,
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_IN_LIBRARY,
|
||||
payload: !novel.followed,
|
||||
@@ -161,21 +148,50 @@ export const downloadChapterAction = (
|
||||
) => async (dispatch) => {
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADING,
|
||||
payload: {
|
||||
chapterId,
|
||||
},
|
||||
payload: chapterId,
|
||||
});
|
||||
|
||||
await downloadChapter(extensionId, novelUrl, chapterUrl, chapterId);
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADED,
|
||||
payload: { chapterId },
|
||||
payload: chapterId,
|
||||
});
|
||||
|
||||
ToastAndroid.show(`Downloaded ${chapterName}`, ToastAndroid.SHORT);
|
||||
};
|
||||
|
||||
export const downloadAllChaptersAction = (
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapters
|
||||
) => async (dispatch) => {
|
||||
await chapters.map((chapter, index) => {
|
||||
setTimeout(async () => {
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADING,
|
||||
payload: chapter.chapterId,
|
||||
});
|
||||
|
||||
if (!chapter.downloaded) {
|
||||
await downloadChapter(
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapter.chapterUrl,
|
||||
chapter.chapterId
|
||||
);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADED,
|
||||
payload: chapter.chapterId,
|
||||
});
|
||||
}, 1000 * index);
|
||||
});
|
||||
|
||||
ToastAndroid.show(`All chapters downloaded`, ToastAndroid.SHORT);
|
||||
};
|
||||
|
||||
export const deleteChapterAction = (chapterId, chapterName) => async (
|
||||
dispatch
|
||||
) => {
|
||||
@@ -183,30 +199,37 @@ export const deleteChapterAction = (chapterId, chapterName) => async (
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DELETED,
|
||||
payload: { chapterId },
|
||||
payload: chapterId,
|
||||
});
|
||||
|
||||
ToastAndroid.show(`Deleted ${chapterName}`, ToastAndroid.SHORT);
|
||||
};
|
||||
|
||||
export const deleteAllChaptersAction = (chapters) => async (dispatch) => {
|
||||
await chapters.map((chapter) => {
|
||||
deleteChapter(chapter.chapterId);
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DELETED,
|
||||
payload: chapter.chapterId,
|
||||
});
|
||||
});
|
||||
|
||||
ToastAndroid.show(`Deleted all chapters`, ToastAndroid.SHORT);
|
||||
};
|
||||
|
||||
export const updateNovelAction = (extensionId, novelUrl, novelId) => async (
|
||||
dispatch
|
||||
) => {
|
||||
dispatch({ type: FETCHING_NOVEL });
|
||||
|
||||
const updatedNovel = await updateNovel(extensionId, novelUrl, novelId);
|
||||
await updateNovel(extensionId, novelUrl, novelId);
|
||||
|
||||
let novel = await getNovel(novelUrl);
|
||||
let chapters = await getChapters(novel.novelId);
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_NOVEL,
|
||||
payload: { novel: updatedNovel, chapters: updatedNovel.chapters },
|
||||
});
|
||||
};
|
||||
|
||||
export const getLastReadChapterAction = (novelId) => async (dispatch) => {
|
||||
const lastReadChapter = await getLastReadChapter(novelId);
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_LAST_READ,
|
||||
payload: lastReadChapter,
|
||||
payload: { novel, chapters },
|
||||
});
|
||||
};
|
||||
|
||||
@@ -58,16 +58,7 @@ const novelReducer = (state = initialState, action) => {
|
||||
case UPDATE_NOVEL:
|
||||
return {
|
||||
...state,
|
||||
novel: {
|
||||
...state.novel,
|
||||
novelName: payload.novel.novelName,
|
||||
novelUrl: payload.novel.novelUrl,
|
||||
novelSummary: payload.novel.novelSummary,
|
||||
author: payload.novel.author,
|
||||
artist: payload.novel.artist,
|
||||
genre: payload.novel.genre,
|
||||
status: payload.novel.status,
|
||||
},
|
||||
novel: payload.novel,
|
||||
chapters: payload.chapters,
|
||||
loading: false,
|
||||
fetching: false,
|
||||
@@ -91,14 +82,18 @@ const novelReducer = (state = initialState, action) => {
|
||||
case CHAPTER_DOWNLOADING:
|
||||
return {
|
||||
...state,
|
||||
downloading: payload.chapterId,
|
||||
chapters: state.chapters.map((chapter) =>
|
||||
chapter.chapterId === payload
|
||||
? { ...chapter, downloaded: 3 }
|
||||
: chapter
|
||||
),
|
||||
};
|
||||
case CHAPTER_DOWNLOADED:
|
||||
return {
|
||||
...state,
|
||||
downloading: null,
|
||||
// downloading: state.downloading.filter((id) => id === payload),
|
||||
chapters: state.chapters.map((chapter) =>
|
||||
chapter.chapterId === payload.chapterId
|
||||
chapter.chapterId === payload
|
||||
? { ...chapter, downloaded: 1 }
|
||||
: chapter
|
||||
),
|
||||
@@ -107,7 +102,7 @@ const novelReducer = (state = initialState, action) => {
|
||||
return {
|
||||
...state,
|
||||
chapters: state.chapters.map((chapter) =>
|
||||
chapter.chapterId === payload.chapterId
|
||||
chapter.chapterId === payload
|
||||
? { ...chapter, downloaded: 0 }
|
||||
: chapter
|
||||
),
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
SET_ITEMS_PER_ROW,
|
||||
UPDATE_READER_TEXT_SIZE,
|
||||
UPDATE_READER_THEME,
|
||||
UPDATE_READER_TEXT_ALIGN,
|
||||
UPDATE_READER_PADDING,
|
||||
} from "./settings.types";
|
||||
|
||||
export const setDisplayMode = (val) => (dispatch) => {
|
||||
@@ -13,7 +15,6 @@ export const setDisplayMode = (val) => (dispatch) => {
|
||||
};
|
||||
|
||||
export const setItemsPerRow = (val) => (dispatch) => {
|
||||
console.log("called" + val);
|
||||
dispatch({
|
||||
type: SET_ITEMS_PER_ROW,
|
||||
payload: val,
|
||||
@@ -33,3 +34,17 @@ export const updateReaderTextSize = (val) => (dispatch) => {
|
||||
payload: val,
|
||||
});
|
||||
};
|
||||
|
||||
export const updateReaderTextAlign = (val) => (dispatch) => {
|
||||
dispatch({
|
||||
type: UPDATE_READER_TEXT_ALIGN,
|
||||
payload: val,
|
||||
});
|
||||
};
|
||||
|
||||
export const updateReaderPadding = (val) => (dispatch) => {
|
||||
dispatch({
|
||||
type: UPDATE_READER_PADDING,
|
||||
payload: val,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
SET_DISPLAY_MODE,
|
||||
SET_ITEMS_PER_ROW,
|
||||
UPDATE_READER_PADDING,
|
||||
UPDATE_READER_TEXT_ALIGN,
|
||||
UPDATE_READER_TEXT_SIZE,
|
||||
UPDATE_READER_THEME,
|
||||
} from "./settings.types";
|
||||
@@ -11,6 +13,8 @@ const initialState = {
|
||||
reader: {
|
||||
theme: 1,
|
||||
textSize: 16,
|
||||
textAlign: "left",
|
||||
padding: "5",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -48,6 +52,16 @@ const settingsReducer = (state = initialState, action) => {
|
||||
return { ...state, reader: { ...state.reader, theme: payload } };
|
||||
case UPDATE_READER_TEXT_SIZE:
|
||||
return { ...state, reader: { ...state.reader, textSize: payload } };
|
||||
case UPDATE_READER_TEXT_ALIGN:
|
||||
return {
|
||||
...state,
|
||||
reader: { ...state.reader, textAlign: payload },
|
||||
};
|
||||
case UPDATE_READER_PADDING:
|
||||
return {
|
||||
...state,
|
||||
reader: { ...state.reader, padding: payload },
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -2,3 +2,5 @@ export const SET_DISPLAY_MODE = "SET_DISPLAY_MODE";
|
||||
export const SET_ITEMS_PER_ROW = "SET_ITEMS_PER_ROW";
|
||||
export const UPDATE_READER_THEME = "UPDATE_READER_THEME";
|
||||
export const UPDATE_READER_TEXT_SIZE = "UPDATE_READER_TEXT_SIZE";
|
||||
export const UPDATE_READER_TEXT_ALIGN = "UPDATE_READER_TEXT_ALIGN";
|
||||
export const UPDATE_READER_PADDING = "UPDATE_READER_PADDING";
|
||||
|
||||
@@ -9,6 +9,7 @@ import libraryReducer from "./library/library.reducer";
|
||||
import extensionReducer from "./source/source.reducers";
|
||||
import novelReducer from "./novel/novel.reducer";
|
||||
import historyReducer from "./history/history.reducer";
|
||||
import updatesReducer from "./updates/updates.reducer";
|
||||
|
||||
const persistConfig = {
|
||||
key: "root",
|
||||
@@ -25,6 +26,7 @@ const persistedReducer = persistReducer(
|
||||
extensionReducer,
|
||||
novelReducer,
|
||||
historyReducer,
|
||||
updatesReducer,
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { GET_UPDATES } from "./updates.types";
|
||||
|
||||
import { getUpdates } from "../../database/queries/UpdateQueries";
|
||||
|
||||
export const getUpdatesAction = () => async (dispatch) => {
|
||||
const updates = await getUpdates();
|
||||
|
||||
dispatch({ type: GET_UPDATES, payload: updates });
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { LOAD_UPDATES, GET_UPDATES } from "./updates.types";
|
||||
|
||||
const initialState = {
|
||||
updates: [],
|
||||
loading: true,
|
||||
};
|
||||
|
||||
const updateReducer = (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
switch (type) {
|
||||
case LOAD_UPDATES:
|
||||
return { ...state, loading: true };
|
||||
case GET_UPDATES:
|
||||
return { ...state, updates: payload, loading: false };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default updateReducer;
|
||||
@@ -0,0 +1,2 @@
|
||||
export const GET_UPDATES = "GET_UPDATES";
|
||||
export const LOAD_UPDATES = "LOAD_UPDATES";
|
||||
@@ -10,7 +10,7 @@ import ExtensionCard from "./components/ExtensionCard";
|
||||
const Browse = ({ theme, extensions, loading, getSourcesAction }) => {
|
||||
useEffect(() => {
|
||||
getSourcesAction();
|
||||
}, []);
|
||||
}, [getSourcesAction]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useNavigation } from "@react-navigation/native";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const ExtensionCard = ({ item }) => {
|
||||
const { sourceName, sourceCover, sourceLanguage } = item;
|
||||
const { sourceId, sourceName, sourceCover, sourceLanguage } = item;
|
||||
|
||||
const navigation = useNavigation();
|
||||
|
||||
@@ -15,7 +15,11 @@ const ExtensionCard = ({ item }) => {
|
||||
return (
|
||||
<TouchableRipple
|
||||
style={styles.extensionCard}
|
||||
onPress={() => navigation.navigate(sourceName + "Stack")}
|
||||
onPress={() =>
|
||||
navigation.navigate("ExtensionStack", {
|
||||
screen: sourceName,
|
||||
})
|
||||
}
|
||||
rippleColor={theme.rippleColor}
|
||||
borderless
|
||||
>
|
||||
@@ -37,14 +41,25 @@ const ExtensionCard = ({ item }) => {
|
||||
>
|
||||
{sourceName}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{sourceLanguage}
|
||||
</Text>
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{sourceLanguage}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: "#D63B2F",
|
||||
fontSize: 12,
|
||||
marginLeft: 5,
|
||||
}}
|
||||
>
|
||||
{sourceId === 3 && "(Not Working)"}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View>
|
||||
<Button
|
||||
@@ -54,7 +69,9 @@ const ExtensionCard = ({ item }) => {
|
||||
uppercase={false}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() =>
|
||||
navigation.navigate(sourceName + "Stack")
|
||||
navigation.navigate("ExtensionStack", {
|
||||
screen: sourceName,
|
||||
})
|
||||
}
|
||||
>
|
||||
Browse
|
||||
|
||||
@@ -105,10 +105,12 @@ const ChapterItem = ({
|
||||
size={26}
|
||||
disabled={!chapter.prevChapter}
|
||||
onPress={() => {
|
||||
navigation.push("ChapterItem", {
|
||||
navigation.replace("ChapterItem", {
|
||||
chapterUrl: chapter.prevChapter,
|
||||
chapterId: chapterId - 1,
|
||||
extensionId,
|
||||
novelUrl,
|
||||
novelId,
|
||||
chapterName: chapter.chapterName,
|
||||
});
|
||||
}}
|
||||
@@ -119,10 +121,12 @@ const ChapterItem = ({
|
||||
size={26}
|
||||
disabled={!chapter.nextChapter}
|
||||
onPress={() => {
|
||||
navigation.push("ChapterItem", {
|
||||
navigation.replace("ChapterItem", {
|
||||
chapterUrl: chapter.nextChapter,
|
||||
extensionId,
|
||||
novelUrl,
|
||||
novelId,
|
||||
chapterId: chapterId + 1,
|
||||
chapterName: chapter.chapterName,
|
||||
});
|
||||
}}
|
||||
@@ -166,6 +170,8 @@ const ChapterItem = ({
|
||||
fontSize: reader.textSize,
|
||||
color: readerTextColor(reader.theme),
|
||||
lineHeight: readerLineHeight(reader.textSize),
|
||||
textAlign: reader.textAlign,
|
||||
paddingHorizontal: `${reader.padding}%`,
|
||||
},
|
||||
]}
|
||||
>
|
||||
@@ -196,7 +202,6 @@ export default connect(mapStateToProps, {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexGrow: 1,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import React from "react";
|
||||
|
||||
import { Animated, StyleSheet, View, Text, Dimensions } from "react-native";
|
||||
import { ToggleButton } from "react-native-paper";
|
||||
import { ToggleButton, IconButton } from "react-native-paper";
|
||||
import Slider from "@react-native-community/slider";
|
||||
import Bottomsheet from "rn-sliding-up-panel";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import {
|
||||
updateReaderPadding,
|
||||
updateReaderTextAlign,
|
||||
updateReaderTextSize,
|
||||
updateReaderTheme,
|
||||
} from "../../../redux/settings/settings.actions";
|
||||
@@ -19,13 +21,15 @@ const ChapterBottomSheet = ({
|
||||
reader,
|
||||
updateReaderTextSize,
|
||||
updateReaderTheme,
|
||||
updateReaderTextAlign,
|
||||
updateReaderPadding,
|
||||
}) => {
|
||||
return (
|
||||
<Bottomsheet
|
||||
animatedValue={new Animated.Value(0)}
|
||||
ref={bottomSheetRef}
|
||||
draggableRange={{ top: 200, bottom: 0 }}
|
||||
snappingPoints={[0, 200]}
|
||||
draggableRange={{ top: 360, bottom: 0 }}
|
||||
snappingPoints={[0, 360]}
|
||||
showBackdrop={false}
|
||||
>
|
||||
<View style={styles.contentContainer}>
|
||||
@@ -47,7 +51,7 @@ const ChapterBottomSheet = ({
|
||||
value={reader.textSize}
|
||||
minimumValue={12}
|
||||
maximumValue={20}
|
||||
step={4}
|
||||
step={2}
|
||||
minimumTrackTintColor={theme.colorAccentDark}
|
||||
maximumTrackTintColor="#000000"
|
||||
thumbTintColor={theme.colorAccentDark}
|
||||
@@ -94,6 +98,94 @@ const ChapterBottomSheet = ({
|
||||
}}
|
||||
/>
|
||||
</ToggleButton.Row>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontWeight: "bold",
|
||||
marginTop: 10,
|
||||
}}
|
||||
>
|
||||
Text Align
|
||||
</Text>
|
||||
<ToggleButton.Row
|
||||
onValueChange={(value) =>
|
||||
updateReaderTextAlign(value ?? "left")
|
||||
}
|
||||
value={reader.textAlign}
|
||||
style={{ marginTop: 10 }}
|
||||
>
|
||||
<ToggleButton
|
||||
icon="format-align-left"
|
||||
color="#000000"
|
||||
value="left"
|
||||
style={{
|
||||
backgroundColor: "#FFFFFF",
|
||||
marginHorizontal: 10,
|
||||
}}
|
||||
/>
|
||||
<ToggleButton
|
||||
icon="format-align-justify"
|
||||
color="#000000"
|
||||
value="justify"
|
||||
style={{
|
||||
backgroundColor: "#FFFFFF",
|
||||
marginHorizontal: 10,
|
||||
}}
|
||||
/>
|
||||
<ToggleButton
|
||||
icon="format-align-right"
|
||||
color="#000000"
|
||||
value="right"
|
||||
style={{
|
||||
backgroundColor: "#FFFFFF",
|
||||
marginHorizontal: 10,
|
||||
}}
|
||||
/>
|
||||
</ToggleButton.Row>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontWeight: "bold",
|
||||
marginTop: 10,
|
||||
}}
|
||||
>
|
||||
Padding
|
||||
</Text>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
icon="minus"
|
||||
color={theme.colorAccentDark}
|
||||
size={26}
|
||||
disabled={reader.padding <= 0 ? true : false}
|
||||
onPress={() =>
|
||||
updateReaderPadding(reader.padding - 1)
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
paddingHorizontal: 50,
|
||||
}}
|
||||
>
|
||||
{`${reader.padding}%`}
|
||||
</Text>
|
||||
<IconButton
|
||||
icon="plus"
|
||||
color={theme.colorAccentDark}
|
||||
size={26}
|
||||
disabled={reader.padding >= 10 ? true : false}
|
||||
onPress={() =>
|
||||
updateReaderPadding(reader.padding + 1)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Bottomsheet>
|
||||
@@ -108,6 +200,8 @@ const mapStateToProps = (state) => ({
|
||||
export default connect(mapStateToProps, {
|
||||
updateReaderTextSize,
|
||||
updateReaderTheme,
|
||||
updateReaderTextAlign,
|
||||
updateReaderPadding,
|
||||
})(ChapterBottomSheet);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
||||
@@ -49,7 +49,7 @@ const HistoryCard = ({ item, deleteHistoryAction, navigation }) => {
|
||||
numberOfLines={1}
|
||||
>
|
||||
{`${item.chapterName} - ${moment(
|
||||
item.lastRead
|
||||
item.historyTimeRead
|
||||
).calendar()}`}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from "../../redux/library/library.actions";
|
||||
import { SearchAppbar } from "../../components/common/Appbar";
|
||||
import { setNovel } from "../../redux/novel/novel.actions";
|
||||
import { updateAllNovels } from "../../services/updates";
|
||||
|
||||
const LibraryScreen = ({
|
||||
navigation,
|
||||
@@ -29,6 +30,7 @@ const LibraryScreen = ({
|
||||
getLibraryAction,
|
||||
searchLibraryAction,
|
||||
setNovel,
|
||||
searchResults,
|
||||
itemsPerRow,
|
||||
}) => {
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
@@ -37,7 +39,7 @@ const LibraryScreen = ({
|
||||
|
||||
useEffect(() => {
|
||||
getLibraryAction();
|
||||
}, [getLibraryAction]);
|
||||
}, [getLibraryAction, novels]);
|
||||
|
||||
/**
|
||||
* TODO: fix refreshing
|
||||
@@ -45,7 +47,7 @@ const LibraryScreen = ({
|
||||
const onRefresh = () => {
|
||||
ToastAndroid.show("Updating library", ToastAndroid.SHORT);
|
||||
setRefreshing(true);
|
||||
getLibraryAction();
|
||||
updateAllNovels();
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
@@ -83,7 +85,7 @@ const LibraryScreen = ({
|
||||
}}
|
||||
numColumns={itemsPerRow}
|
||||
key={itemsPerRow}
|
||||
data={novels}
|
||||
data={!searchText ? novels : searchResults}
|
||||
keyExtractor={(item) => item.novelUrl}
|
||||
renderItem={({ item }) => (
|
||||
<NovelCover
|
||||
@@ -127,6 +129,7 @@ const mapStateToProps = (state) => ({
|
||||
theme: state.themeReducer.theme,
|
||||
itemsPerRow: state.settingsReducer.itemsPerRow,
|
||||
novels: state.libraryReducer.novels,
|
||||
searchResults: state.libraryReducer.searchResults,
|
||||
loading: state.libraryReducer.loading,
|
||||
});
|
||||
|
||||
|
||||
@@ -24,13 +24,13 @@ const AboutScreen = ({ navigation }) => {
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
title="Version"
|
||||
descriptionStyle={{ color: theme.textColorSecondary }}
|
||||
description="Stable 1.0.10"
|
||||
description="Stable 1.0.12"
|
||||
/>
|
||||
<List.Item
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
title="Build Time"
|
||||
descriptionStyle={{ color: theme.textColorSecondary }}
|
||||
description="21-03-21 11:10 AM"
|
||||
description="08-04-21 08:00 AM"
|
||||
/>
|
||||
|
||||
<List.Item
|
||||
|
||||
@@ -30,7 +30,11 @@ const MoreScreen = ({ navigation }) => {
|
||||
icon="cog-outline"
|
||||
/>
|
||||
)}
|
||||
onPress={() => navigation.navigate("Settings")}
|
||||
onPress={() =>
|
||||
navigation.navigate("SettingsStack", {
|
||||
screen: "Settings",
|
||||
})
|
||||
}
|
||||
/>
|
||||
<List.Item
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
@@ -41,7 +45,11 @@ const MoreScreen = ({ navigation }) => {
|
||||
icon="information-outline"
|
||||
/>
|
||||
)}
|
||||
onPress={() => navigation.navigate("About")}
|
||||
onPress={() =>
|
||||
navigation.navigate("SettingsStack", {
|
||||
screen: "About",
|
||||
})
|
||||
}
|
||||
/>
|
||||
</List.Section>
|
||||
</>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
import { View, Text } from "react-native";
|
||||
import { View, Text, Dimensions } from "react-native";
|
||||
import { setStatusBarStyle } from "expo-status-bar";
|
||||
import { List, Modal, Portal } from "react-native-paper";
|
||||
import Slider from "@react-native-community/slider";
|
||||
import InputSpinner from "react-native-input-spinner";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
@@ -172,9 +173,17 @@ const SettingsScreen = ({
|
||||
marginBottom: 10,
|
||||
}}
|
||||
>
|
||||
Items per row
|
||||
Grid size
|
||||
</Text>
|
||||
<InputSpinner
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
marginBottom: 4,
|
||||
}}
|
||||
>
|
||||
{itemsPerRow} per row
|
||||
</Text>
|
||||
{/* <InputSpinner
|
||||
style={{
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 16,
|
||||
@@ -195,6 +204,22 @@ const SettingsScreen = ({
|
||||
inputStyle={{
|
||||
color: theme.textColorPrimary,
|
||||
}}
|
||||
/> */}
|
||||
|
||||
<Slider
|
||||
style={{
|
||||
width: "100%",
|
||||
height: 40,
|
||||
// backgroundColor: "red",
|
||||
}}
|
||||
value={itemsPerRow}
|
||||
minimumValue={1}
|
||||
maximumValue={5}
|
||||
step={1}
|
||||
minimumTrackTintColor={theme.colorAccentDark}
|
||||
maximumTrackTintColor="#000000"
|
||||
thumbTintColor={theme.colorAccentDark}
|
||||
onValueChange={(value) => setItemsPerRow(value)}
|
||||
/>
|
||||
</Modal>
|
||||
</Portal>
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { StyleSheet, View, FlatList, RefreshControl } from "react-native";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
FlatList,
|
||||
RefreshControl,
|
||||
ToastAndroid,
|
||||
} from "react-native";
|
||||
import { Provider, Portal } from "react-native-paper";
|
||||
|
||||
import ChapterCard from "./components/ChapterCard";
|
||||
@@ -13,6 +19,8 @@ import {
|
||||
followNovelAction,
|
||||
sortAndFilterChapters,
|
||||
updateNovelAction,
|
||||
downloadAllChaptersAction,
|
||||
deleteAllChaptersAction,
|
||||
} from "../../redux/novel/novel.actions";
|
||||
|
||||
const Novel = ({
|
||||
@@ -26,6 +34,8 @@ const Novel = ({
|
||||
followNovelAction,
|
||||
sortAndFilterChapters,
|
||||
updateNovelAction,
|
||||
downloadAllChaptersAction,
|
||||
deleteAllChaptersAction,
|
||||
}) => {
|
||||
const {
|
||||
sourceId,
|
||||
@@ -38,9 +48,14 @@ const Novel = ({
|
||||
|
||||
let _panel = useRef(null); // Bottomsheet ref
|
||||
|
||||
const [sort, setSort] = useState("ORDER BY chapterId ASC");
|
||||
const [filter, setFilter] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
getNovelAction(followed, sourceId, novelUrl, novelId);
|
||||
}, []);
|
||||
getNovelAction(followed, sourceId, novelUrl, novelId, sort, filter);
|
||||
}, [getNovelAction, followNovelAction, sort, filter]);
|
||||
|
||||
let lastRead = !loading && chapters.find((obj) => obj.read === 0);
|
||||
|
||||
const renderChapterCard = ({ item }) => (
|
||||
<ChapterCard
|
||||
@@ -52,6 +67,7 @@ const Novel = ({
|
||||
|
||||
const onRefresh = () => {
|
||||
updateNovelAction(sourceId, novelUrl, novelId);
|
||||
ToastAndroid.show(`Updated ${novelName}`, ToastAndroid.SHORT);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -64,7 +80,7 @@ const Novel = ({
|
||||
>
|
||||
<FlatList
|
||||
data={!loading && chapters}
|
||||
keyExtractor={(item) => item.chapterUrl}
|
||||
keyExtractor={(item) => item.chapterId.toString()}
|
||||
removeClippedSubviews={true}
|
||||
maxToRenderPerBatch={5}
|
||||
windowSize={15}
|
||||
@@ -78,8 +94,18 @@ const Novel = ({
|
||||
noOfChapters={chapters?.length}
|
||||
followNovelAction={followNovelAction}
|
||||
loading={loading}
|
||||
lastRead={lastRead}
|
||||
bottomSheetRef={_panel}
|
||||
firstChapter={chapters[0]}
|
||||
downloadAllChapters={() =>
|
||||
downloadAllChaptersAction(
|
||||
sourceId,
|
||||
novelUrl,
|
||||
chapters
|
||||
)
|
||||
}
|
||||
deleteAllChapters={() =>
|
||||
deleteAllChaptersAction(chapters)
|
||||
}
|
||||
/>
|
||||
}
|
||||
refreshControl={
|
||||
@@ -96,6 +122,10 @@ const Novel = ({
|
||||
novelUrl={novelUrl}
|
||||
bottomSheetRef={_panel}
|
||||
sortAndFilterChapters={sortAndFilterChapters}
|
||||
sort={sort}
|
||||
filter={filter}
|
||||
setSort={setSort}
|
||||
setFilter={setFilter}
|
||||
/>
|
||||
</Portal>
|
||||
</View>
|
||||
@@ -115,6 +145,8 @@ export default connect(mapStateToProps, {
|
||||
followNovelAction,
|
||||
sortAndFilterChapters,
|
||||
updateNovelAction,
|
||||
downloadAllChaptersAction,
|
||||
deleteAllChaptersAction,
|
||||
})(Novel);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
||||
@@ -7,23 +7,14 @@ import { useSelector } from "react-redux";
|
||||
|
||||
export const BottomSheet = ({
|
||||
bottomSheetRef,
|
||||
sortAndFilterChapters,
|
||||
sort,
|
||||
filter,
|
||||
setSort,
|
||||
setFilter,
|
||||
novelUrl,
|
||||
}) => {
|
||||
const checkBoxStyle = { flexDirection: "row", alignItems: "center" };
|
||||
|
||||
const [chapterFlags, setChapterFlags] = useState({ sort: "", filter: "" });
|
||||
|
||||
const sortChapters = (sort) => {
|
||||
setChapterFlags({ ...chapterFlags, sort: sort });
|
||||
sortAndFilterChapters(novelUrl, chapterFlags.filter, sort);
|
||||
};
|
||||
|
||||
const filterChapters = (filter) => {
|
||||
setChapterFlags({ ...chapterFlags, filter: filter });
|
||||
sortAndFilterChapters(novelUrl, filter, chapterFlags.sort);
|
||||
};
|
||||
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
return (
|
||||
@@ -51,8 +42,8 @@ export const BottomSheet = ({
|
||||
</List.Subheader>
|
||||
<View>
|
||||
<RadioButton.Group
|
||||
onValueChange={(newValue) => sortChapters(newValue)}
|
||||
value={chapterFlags.sort}
|
||||
onValueChange={(newValue) => setSort(newValue)}
|
||||
value={sort}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
@@ -88,7 +79,7 @@ export const BottomSheet = ({
|
||||
<RadioButton.Item
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
value=""
|
||||
value="ORDER BY chapterId ASC"
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
@@ -113,12 +104,10 @@ export const BottomSheet = ({
|
||||
</List.Subheader>
|
||||
<View style={checkBoxStyle}>
|
||||
<Checkbox.Item
|
||||
status={
|
||||
chapterFlags.filter === "" ? "checked" : "unchecked"
|
||||
}
|
||||
status={filter === "" ? "checked" : "unchecked"}
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => filterChapters("")}
|
||||
onPress={() => setFilter("")}
|
||||
/>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Show all
|
||||
@@ -127,13 +116,11 @@ export const BottomSheet = ({
|
||||
<View style={checkBoxStyle}>
|
||||
<Checkbox.Item
|
||||
status={
|
||||
chapterFlags.filter === "AND `read`=1"
|
||||
? "checked"
|
||||
: "unchecked"
|
||||
filter === "AND `read`=1" ? "checked" : "unchecked"
|
||||
}
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => filterChapters("AND `read`=1")}
|
||||
onPress={() => setFilter("AND `read`=1")}
|
||||
/>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Show read chapters
|
||||
@@ -142,13 +129,11 @@ export const BottomSheet = ({
|
||||
<View style={checkBoxStyle}>
|
||||
<Checkbox.Item
|
||||
status={
|
||||
chapterFlags.filter === "AND `read`=0"
|
||||
? "checked"
|
||||
: "unchecked"
|
||||
filter === "AND `read`=0" ? "checked" : "unchecked"
|
||||
}
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => filterChapters("AND `read`=0")}
|
||||
onPress={() => setFilter("AND `read`=0")}
|
||||
/>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Show unread chapters
|
||||
@@ -157,13 +142,13 @@ export const BottomSheet = ({
|
||||
<View style={checkBoxStyle}>
|
||||
<Checkbox.Item
|
||||
status={
|
||||
chapterFlags.filter === "AND downloaded=1"
|
||||
filter === "AND downloaded=1"
|
||||
? "checked"
|
||||
: "unchecked"
|
||||
}
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => filterChapters("AND downloaded=1")}
|
||||
onPress={() => setFilter("AND downloaded=1")}
|
||||
/>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Show downloaded chapters
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { View, Text, ActivityIndicator, StyleSheet } from "react-native";
|
||||
import { TouchableRipple, IconButton } from "react-native-paper";
|
||||
import { TouchableRipple, IconButton, Menu } from "react-native-paper";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
|
||||
import {
|
||||
@@ -14,12 +14,77 @@ const ChapterCard = ({
|
||||
chapter,
|
||||
extensionId,
|
||||
theme,
|
||||
downloading,
|
||||
// downloading,
|
||||
downloadChapterAction,
|
||||
deleteChapterAction,
|
||||
}) => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const openMenu = () => setVisible(true);
|
||||
const closeMenu = () => setVisible(false);
|
||||
|
||||
const displayDownloadButton = () => {
|
||||
if (chapter.downloaded === 3) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
color={theme.textColorHintDark}
|
||||
size={25}
|
||||
style={{ margin: 3.5, padding: 5 }}
|
||||
/>
|
||||
);
|
||||
} else if (chapter.downloaded === 1) {
|
||||
return (
|
||||
<Menu
|
||||
visible={visible}
|
||||
onDismiss={closeMenu}
|
||||
anchor={
|
||||
<IconButton
|
||||
icon="check-circle"
|
||||
animated
|
||||
color={theme.textColorPrimary}
|
||||
size={25}
|
||||
onPress={openMenu}
|
||||
style={{ margin: 2 }}
|
||||
/>
|
||||
}
|
||||
contentStyle={{ backgroundColor: "#242529" }}
|
||||
>
|
||||
<Menu.Item
|
||||
onPress={() =>
|
||||
deleteChapterAction(
|
||||
chapter.chapterId,
|
||||
chapter.chapterName
|
||||
)
|
||||
}
|
||||
title="Delete"
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<IconButton
|
||||
icon="arrow-down-circle-outline"
|
||||
animated
|
||||
color={theme.textColorHintDark}
|
||||
size={25}
|
||||
onPress={() => {
|
||||
downloadChapterAction(
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapter.chapterUrl,
|
||||
chapter.chapterName,
|
||||
chapter.chapterId
|
||||
);
|
||||
// console.log(downloading);
|
||||
}}
|
||||
style={{ margin: 2 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
style={styles.chapterCardContainer}
|
||||
@@ -68,47 +133,7 @@ const ChapterCard = ({
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<View>
|
||||
{downloading === chapter.chapterId ? (
|
||||
<ActivityIndicator
|
||||
color={theme.textColorHintDark}
|
||||
size={25}
|
||||
style={{ margin: 3.5, padding: 5 }}
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
icon={
|
||||
chapter.downloaded
|
||||
? "check-circle"
|
||||
: "arrow-down-circle-outline"
|
||||
}
|
||||
animated
|
||||
color={
|
||||
chapter.downloaded
|
||||
? theme.textColorPrimary
|
||||
: theme.textColorHintDark
|
||||
}
|
||||
size={25}
|
||||
onPress={() => {
|
||||
if (!chapter.downloaded) {
|
||||
downloadChapterAction(
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapter.chapterUrl,
|
||||
chapter.chapterName,
|
||||
chapter.chapterId
|
||||
);
|
||||
} else {
|
||||
deleteChapterAction(
|
||||
chapter.chapterId,
|
||||
chapter.chapterName
|
||||
);
|
||||
}
|
||||
}}
|
||||
style={{ margin: 2 }}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View>{displayDownloadButton()}</View>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
);
|
||||
@@ -116,7 +141,7 @@ const ChapterCard = ({
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
theme: state.themeReducer.theme,
|
||||
downloading: state.novelReducer.downloading,
|
||||
// downloading: state.novelReducer.downloading,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
|
||||
@@ -8,7 +8,13 @@ import {
|
||||
FlatList,
|
||||
Share,
|
||||
} from "react-native";
|
||||
import { TouchableRipple, IconButton, Button, Chip } from "react-native-paper";
|
||||
import {
|
||||
TouchableRipple,
|
||||
IconButton,
|
||||
Button,
|
||||
Chip,
|
||||
Menu,
|
||||
} from "react-native-paper";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
|
||||
@@ -23,7 +29,9 @@ const NovelInfoHeader = ({
|
||||
loading,
|
||||
bottomSheetRef,
|
||||
theme,
|
||||
firstChapter,
|
||||
lastRead,
|
||||
downloadAllChapters,
|
||||
deleteAllChapters,
|
||||
}) => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
@@ -31,6 +39,10 @@ const NovelInfoHeader = ({
|
||||
!loading && novel.followed !== 1 ? true : false
|
||||
);
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const openMenu = () => setVisible(true);
|
||||
const closeMenu = () => setVisible(false);
|
||||
|
||||
const renderGenreChip = ({ item }) => (
|
||||
<Text
|
||||
style={[
|
||||
@@ -161,6 +173,33 @@ const NovelInfoHeader = ({
|
||||
color={theme.colorAccentDark}
|
||||
size={21}
|
||||
/>
|
||||
|
||||
<Menu
|
||||
visible={visible}
|
||||
onDismiss={closeMenu}
|
||||
anchor={
|
||||
<IconButton
|
||||
icon="download"
|
||||
color={theme.colorAccentDark}
|
||||
size={21}
|
||||
onPress={openMenu}
|
||||
/>
|
||||
}
|
||||
contentStyle={{ backgroundColor: "#242529" }}
|
||||
>
|
||||
<Menu.Item
|
||||
title="Download all"
|
||||
style={{ backgroundColor: "#242529" }}
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
onPress={downloadAllChapters}
|
||||
/>
|
||||
<Menu.Item
|
||||
title="Delete downloads"
|
||||
style={{ backgroundColor: "#242529" }}
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
onPress={deleteAllChapters}
|
||||
/>
|
||||
</Menu>
|
||||
</View>
|
||||
|
||||
{novel.novelSummary !== "" && (
|
||||
@@ -217,35 +256,48 @@ const NovelInfoHeader = ({
|
||||
renderItem={renderGenreChip}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
/>
|
||||
|
||||
<Button
|
||||
color="white"
|
||||
style={[
|
||||
{ backgroundColor: theme.colorAccentDark },
|
||||
styles.startButton,
|
||||
]}
|
||||
uppercase={false}
|
||||
labelStyle={{ letterSpacing: 0 }}
|
||||
// onPress={() => {
|
||||
// console.log(firstChapter.chapterName);
|
||||
// navigation.navigate("ChapterItem", {
|
||||
// chapterUrl:
|
||||
// novel.lastRead.chapterUrl ??
|
||||
// firstChapter.chapterUrl,
|
||||
// novelUrl: novel.novelUrl,
|
||||
// extensionId: novel.extensionId,
|
||||
// chapterName:
|
||||
// novel.lastRead.chapterUrl ??
|
||||
// firstChapter.chapterName,
|
||||
// });
|
||||
// }}
|
||||
>
|
||||
{/* {novel.lastRead
|
||||
? `Continue reading ${novel.lastRead.chapterName}`
|
||||
: `Start reading ${firstChapter.chapterName}`} */}
|
||||
Start reading
|
||||
</Button>
|
||||
|
||||
{noOfChapters > 0 &&
|
||||
(lastRead ? (
|
||||
<Button
|
||||
color="white"
|
||||
style={[
|
||||
{ backgroundColor: theme.colorAccentDark },
|
||||
styles.startButton,
|
||||
]}
|
||||
uppercase={false}
|
||||
labelStyle={{ letterSpacing: 0 }}
|
||||
onPress={() => {
|
||||
navigation.navigate("ChapterItem", {
|
||||
chapterId: lastRead.chapterId,
|
||||
chapterUrl: lastRead.chapterUrl,
|
||||
novelUrl: novel.novelUrl,
|
||||
novelId: lastRead.novelId,
|
||||
extensionId: novel.sourceId,
|
||||
chapterName: lastRead.chapterName,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{novel.unread
|
||||
? `Start reading `
|
||||
: `Continue reading `}
|
||||
{lastRead.chapterName}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
color={theme.textColorPrimary}
|
||||
style={[
|
||||
{
|
||||
backgroundColor: theme.textColorPrimary,
|
||||
opacity: 0.2,
|
||||
},
|
||||
styles.startButton,
|
||||
]}
|
||||
uppercase={false}
|
||||
labelStyle={{ letterSpacing: 0 }}
|
||||
>
|
||||
All chapters read
|
||||
</Button>
|
||||
))}
|
||||
<TouchableRipple
|
||||
style={styles.bottomsheet}
|
||||
onPress={() =>
|
||||
|
||||
@@ -1,3 +1,67 @@
|
||||
/**
|
||||
* Updates Screen
|
||||
*/
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
import { StyleSheet, View, FlatList, ActivityIndicator } from "react-native";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import EmptyView from "../../components/common/EmptyView";
|
||||
import { getUpdatesAction } from "../../redux/updates/updates.actions";
|
||||
import { Appbar } from "./components/Appbar";
|
||||
import UpdateCard from "./components/UpdateCard";
|
||||
|
||||
const Updates = ({ theme, updates, loading, getUpdatesAction }) => {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
getUpdatesAction();
|
||||
}, [getUpdatesAction])
|
||||
);
|
||||
|
||||
const renderUpdateCard = ({ item }) => <UpdateCard item={item} />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar />
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<FlatList
|
||||
contentContainerStyle={{ flexGrow: 1, paddingVertical: 8 }}
|
||||
data={updates}
|
||||
keyExtractor={(item) => item.updateId.toString()}
|
||||
renderItem={renderUpdateCard}
|
||||
ListFooterComponent={
|
||||
loading && (
|
||||
<ActivityIndicator
|
||||
size="small"
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
)
|
||||
}
|
||||
ListEmptyComponent={
|
||||
!loading && (
|
||||
<EmptyView
|
||||
icon="(˘・_・˘)"
|
||||
description="No recent updates"
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
theme: state.themeReducer.theme,
|
||||
updates: state.updatesReducer.updates,
|
||||
loading: state.updatesReducer.loading,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { getUpdatesAction })(Updates);
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import { Appbar as MaterialAppbar } from "react-native-paper";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
import { updateAllNovels } from "../../../services/updates";
|
||||
|
||||
export const Appbar = () => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
return (
|
||||
<MaterialAppbar.Header style={{ backgroundColor: theme.colorPrimary }}>
|
||||
<MaterialAppbar.Content
|
||||
title="Updates"
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
/>
|
||||
<MaterialAppbar.Action icon="reload" onPress={updateAllNovels} />
|
||||
</MaterialAppbar.Header>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
import React from "react";
|
||||
import { View, Text, Image, StyleSheet } from "react-native";
|
||||
import { TouchableRipple, Button } from "react-native-paper";
|
||||
import moment from "moment";
|
||||
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const UpdateCard = ({ item }) => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
style={styles.updateCard}
|
||||
onPress={() =>
|
||||
// console.log(item);
|
||||
navigation.navigate("ChapterItem", {
|
||||
chapterId: item.chapterId,
|
||||
chapterUrl: item.chapterUrl,
|
||||
extensionId: item.sourceId,
|
||||
novelUrl: item.novelUrl,
|
||||
chapterName: item.chapterName,
|
||||
novelId: item.novelId,
|
||||
})
|
||||
}
|
||||
rippleColor={theme.rippleColor}
|
||||
borderless
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
source={{ uri: item.novelCover }}
|
||||
style={styles.updateIcon}
|
||||
/>
|
||||
<View style={styles.extensionDetails}>
|
||||
<View>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{`${item.chapterName} · ${moment(
|
||||
item.updateTime
|
||||
).calendar()}`}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateCard;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
updateCard: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
// borderRadius: 4,
|
||||
},
|
||||
updateIcon: {
|
||||
width: 42,
|
||||
height: 42,
|
||||
borderRadius: 4,
|
||||
},
|
||||
extensionDetails: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginLeft: 16,
|
||||
},
|
||||
});
|
||||
@@ -109,6 +109,14 @@ const BoxNovel = ({ navigation }) => {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
ListEmptyComponent={
|
||||
novels.length === 0 && (
|
||||
<EmptyView
|
||||
icon="(; ̄Д ̄)"
|
||||
description="No results found"
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { StyleSheet, View, FlatList, ActivityIndicator } from "react-native";
|
||||
import { Provider } from "react-native-paper";
|
||||
|
||||
import NovelCover from "../../../components/common/NovelCover";
|
||||
import { SearchAppbar } from "../../../components/common/Appbar";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
import EmptyView from "../../../components/common/EmptyView";
|
||||
|
||||
const FastNovel = ({ navigation }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
const library = useSelector((state) => state.libraryReducer.novels);
|
||||
const itemsPerRow = useSelector(
|
||||
(state) => state.settingsReducer.itemsPerRow
|
||||
);
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [novels, setNovels] = useState();
|
||||
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const getNovels = () => {
|
||||
fetch(`https://lnreader-extensions.herokuapp.com/api/3/novels/`)
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
setNovels(json);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const getSearchResults = (searchText) => {
|
||||
setLoading(true);
|
||||
fetch(
|
||||
`https://lnreader-extensions.herokuapp.com/api/3/search/?s=${searchText}`
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
setNovels(json);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const checkIFInLibrary = (id) => {
|
||||
return library.some((obj) => obj.novelUrl === id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getNovels();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<SearchAppbar
|
||||
screen="Extension"
|
||||
placeholder="Search FastNovel"
|
||||
searchText={searchText}
|
||||
setSearchText={setSearchText}
|
||||
getSearchResults={getSearchResults}
|
||||
getNovels={getNovels}
|
||||
setLoading={setLoading}
|
||||
/>
|
||||
|
||||
{loading ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<ActivityIndicator
|
||||
size={60}
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
numColumns={itemsPerRow}
|
||||
key={itemsPerRow}
|
||||
data={novels}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyExtractor={(item) => item.novelUrl}
|
||||
renderItem={({ item }) => (
|
||||
<NovelCover
|
||||
item={item}
|
||||
onPress={() =>
|
||||
navigation.navigate("NovelItem", {
|
||||
novelName: item.novelName,
|
||||
novelCover: item.novelCover,
|
||||
novelUrl: item.novelUrl,
|
||||
sourceId: item.extensionId,
|
||||
})
|
||||
}
|
||||
libraryStatus={
|
||||
checkIFInLibrary(item.novelUrl)
|
||||
? true
|
||||
: false
|
||||
}
|
||||
/>
|
||||
)}
|
||||
ListEmptyComponent={
|
||||
novels.length === 0 && (
|
||||
<EmptyView
|
||||
icon="(; ̄Д ̄)"
|
||||
description="No results found"
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default FastNovel;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 4,
|
||||
},
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
@@ -3,10 +3,10 @@ import { StyleSheet, View, FlatList, ActivityIndicator } from "react-native";
|
||||
import { Provider } from "react-native-paper";
|
||||
|
||||
import NovelCover from "../../../components/common/NovelCover";
|
||||
import EmptyView from "../../../components/common/EmptyView";
|
||||
import { SearchAppbar } from "../../../components/common/Appbar";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
import EmptyView from "../../../components/common/EmptyView";
|
||||
|
||||
const ReadLightNovel = ({ navigation }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
@@ -94,11 +94,7 @@ const ReadLightNovel = ({ navigation }) => {
|
||||
sourceId: item.extensionId,
|
||||
})
|
||||
}
|
||||
libraryStatus={
|
||||
checkIFInLibrary(item.novelUrl)
|
||||
? true
|
||||
: false
|
||||
}
|
||||
libraryStatus={checkIFInLibrary(item.novelUrl)}
|
||||
/>
|
||||
)}
|
||||
ListEmptyComponent={
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { StyleSheet, View, FlatList, ActivityIndicator } from "react-native";
|
||||
import { Provider } from "react-native-paper";
|
||||
|
||||
import NovelCover from "../../../components/common/NovelCover";
|
||||
import { SearchAppbar } from "../../../components/common/Appbar";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
import EmptyView from "../../../components/common/EmptyView";
|
||||
|
||||
const FastNovel = ({ navigation }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
const library = useSelector((state) => state.libraryReducer.novels);
|
||||
const itemsPerRow = useSelector(
|
||||
(state) => state.settingsReducer.itemsPerRow
|
||||
);
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [novels, setNovels] = useState();
|
||||
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const getNovels = () => {
|
||||
fetch(`https://lnreader-extensions.herokuapp.com/api/4/novels/`)
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
setNovels(json);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const getSearchResults = (searchText) => {
|
||||
setLoading(true);
|
||||
fetch(
|
||||
`https://lnreader-extensions.herokuapp.com/api/4/search/?s=${searchText}`
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
setNovels(json);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const checkIFInLibrary = (id) => {
|
||||
return library.some((obj) => obj.novelUrl === id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getNovels();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<SearchAppbar
|
||||
screen="Extension"
|
||||
placeholder="Search ReadNovelFull"
|
||||
searchText={searchText}
|
||||
setSearchText={setSearchText}
|
||||
getSearchResults={getSearchResults}
|
||||
getNovels={getNovels}
|
||||
setLoading={setLoading}
|
||||
/>
|
||||
|
||||
{loading ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<ActivityIndicator
|
||||
size={60}
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
numColumns={itemsPerRow}
|
||||
key={itemsPerRow}
|
||||
data={novels}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyExtractor={(item) => item.novelUrl}
|
||||
renderItem={({ item }) => (
|
||||
<NovelCover
|
||||
item={item}
|
||||
onPress={() =>
|
||||
navigation.navigate("NovelItem", {
|
||||
novelName: item.novelName,
|
||||
novelCover: item.novelCover,
|
||||
novelUrl: item.novelUrl,
|
||||
sourceId: item.extensionId,
|
||||
})
|
||||
}
|
||||
libraryStatus={
|
||||
checkIFInLibrary(item.novelUrl)
|
||||
? true
|
||||
: false
|
||||
}
|
||||
/>
|
||||
)}
|
||||
ListEmptyComponent={
|
||||
novels.length === 0 && (
|
||||
<EmptyView
|
||||
icon="(; ̄Д ̄)"
|
||||
description="No results found"
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default FastNovel;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 4,
|
||||
},
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
+32
-3
@@ -1,6 +1,8 @@
|
||||
import * as SQLite from "expo-sqlite";
|
||||
import { getLibrary } from "../database/queries/LibraryQueries";
|
||||
import { fetchNovel } from "../source/Source";
|
||||
const db = SQLite.openDatabase("lnreader.db");
|
||||
import { ToastAndroid } from "react-native";
|
||||
|
||||
const updateNovelDetails = async (novel, novelId) => {
|
||||
db.transaction((tx) => {
|
||||
@@ -37,11 +39,38 @@ export const updateNovel = async (extensionId, novelUrl, novelId) => {
|
||||
chapter.releaseDate,
|
||||
novelId,
|
||||
],
|
||||
(txObj, res) => {},
|
||||
(txObj, { insertId }) => {
|
||||
if (insertId !== -1) {
|
||||
tx.executeSql(
|
||||
"INSERT OR IGNORE INTO updates (chapterId, novelId, updateTime) values (?, ?, (datetime('now','localtime')))",
|
||||
[insertId, novelId],
|
||||
(txObj, res) => {
|
||||
console.log(
|
||||
"Inserted Chapter Id -> " +
|
||||
insertId +
|
||||
" Novel Id " +
|
||||
novelId
|
||||
);
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
}
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
return novel;
|
||||
};
|
||||
|
||||
export const updateAllNovels = async () => {
|
||||
ToastAndroid.show("Updating library", ToastAndroid.SHORT);
|
||||
|
||||
const libraryNovels = await getLibrary();
|
||||
|
||||
libraryNovels.map((novel, index) =>
|
||||
setTimeout(async () => {
|
||||
updateNovel(novel.sourceId, novel.novelUrl, novel.novelId);
|
||||
console.log(novel.novelName + " Updated");
|
||||
}, 3000 * index)
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user