Compare commits

..

15 Commits

Author SHA1 Message Date
Rajarshee Chatterjee 3af0b1afbc Merge branch 'main' of https://github.com/rajarsheechatterjee/LNReader into main 2021-04-24 20:47:48 +05:30
Rajarshee Chatterjee b9695e7f56 Revert git rebase 2021-04-24 20:46:41 +05:30
Rajarshee Chatterjee 7c068aa7d5 Add download all chapters option (fixes #4) 2021-04-24 20:32:56 +05:30
Rajarshee Chatterjee 8b182f1e31 Add download all chapters option (fixes #4) 2021-04-24 20:31:38 +05:30
Rajarshee Chatterjee 5029513424 closes #4; Add download all chapters 2021-04-24 20:16:10 +05:30
Rajarshee Chatterjee f4c6a3b4a1 Fix download button animation 2021-04-24 18:03:59 +05:30
Rajarshee Chatterjee 27f5e20107 Minor fixes 2021-04-24 11:45:43 +05:30
Rajarshee Chatterjee c249b52304 Add textAlign and padding in reader settings 2021-04-24 11:45:32 +05:30
Rajarshee Chatterjee c179e0e5bd Fix sort and filter chapters 2021-04-24 10:25:39 +05:30
Rajarshee Chatterjee b78382cc03 Allow navigate to chapter from updates tab 2021-04-24 10:03:22 +05:30
Rajarshee Chatterjee 9df3694c04 Fix start reading button 2021-04-24 09:08:34 +05:30
Rajarshee Chatterjee dfd300c358 Fix library search 2021-04-07 09:21:22 +05:30
Rajarshee Chatterjee 76c108597d Add updates tab and updating library 2021-04-05 11:31:07 +05:30
Rajarshee Chatterjee 23ff02674e Fix crash on updating novel 2021-04-03 14:30:06 +05:30
Rajarshee Chatterjee 55e2371f5d Fix start reading button 2021-04-03 11:36:54 +05:30
44 changed files with 1150 additions and 248 deletions
+2 -1
View File
@@ -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();
}, []);
+6
View File
@@ -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");
});
};
+51 -10
View File
@@ -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
+1 -1
View File
@@ -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)
+20 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
);
+26
View File
@@ -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)
);
});
});
};
+1 -1
View File
@@ -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
)`;
+11
View File
@@ -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
View File
@@ -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>
+1
View File
@@ -35,6 +35,7 @@ export const getChapterAction = (
} else {
chapter = await fetchChapter(extensionId, novelUrl, chapterUrl);
}
console.log(isDownloaded);
dispatch({ type: GET_CHAPTER, payload: chapter });
};
+5 -3
View File
@@ -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,
});
};
+8 -2
View File
@@ -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
View File
@@ -1 +1,2 @@
export const GET_LIBRARY_NOVELS = "GET_LIBRARY_NOVELS";
export const GET_LIBRARY_SEARCH_RESULTS = "GET_LIBRARY_SEARCH_RESULTS";
+64 -41
View File
@@ -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 },
});
};
+9 -14
View File
@@ -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
),
+16 -1
View File
@@ -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,
});
};
+14
View File
@@ -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
View File
@@ -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";
+2
View File
@@ -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,
})
);
+9
View File
@@ -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 });
};
+21
View File
@@ -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;
+2
View File
@@ -0,0 +1,2 @@
export const GET_UPDATES = "GET_UPDATES";
export const LOAD_UPDATES = "LOAD_UPDATES";
+1 -1
View File
@@ -10,7 +10,7 @@ import ExtensionCard from "./components/ExtensionCard";
const Browse = ({ theme, extensions, loading, getSourcesAction }) => {
useEffect(() => {
getSourcesAction();
}, []);
}, [getSourcesAction]);
return (
<>
+28 -11
View File
@@ -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
+8 -3
View File
@@ -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,
},
});
+98 -4
View File
@@ -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>
+6 -3
View File
@@ -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,
});
+2 -2
View File
@@ -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
+10 -2
View File
@@ -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>
</>
+28 -3
View File
@@ -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>
+38 -6
View File
@@ -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({
+15 -30
View File
@@ -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
+70 -45
View File
@@ -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, {
+83 -31
View File
@@ -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={() =>
+67 -3
View File
@@ -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,
},
});
+19
View File
@@ -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
View File
@@ -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)
);
};