Add library sort/filter sheet and notification icon
This commit is contained in:
@@ -9,12 +9,16 @@
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "cover",
|
||||
"backgroundColor": "#000000"
|
||||
"backgroundColor": "#222831"
|
||||
},
|
||||
"updates": {
|
||||
"fallbackToCacheTimeout": 0,
|
||||
"enabled": false
|
||||
},
|
||||
"notification": {
|
||||
"icon": "./assets/notification.png",
|
||||
"color": "#00adb5"
|
||||
},
|
||||
"assetBundlePatterns": ["**/*"],
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 36 KiB |
+11
-2
@@ -14,10 +14,19 @@ const ListSubHeader = ({ children, theme }) => (
|
||||
</List.Subheader>
|
||||
);
|
||||
|
||||
const ListItem = ({ title, description, icon, onPress, theme, right }) => (
|
||||
const ListItem = ({
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
onPress,
|
||||
theme,
|
||||
right,
|
||||
titleStyle,
|
||||
}) => (
|
||||
<List.Item
|
||||
title={title}
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
style={[!right && { paddingVertical: 12 }]}
|
||||
titleStyle={[{ color: theme.textColorPrimary }, titleStyle]}
|
||||
description={description}
|
||||
descriptionStyle={{ color: theme.textColorSecondary }}
|
||||
descriptionNumberOfLines={1}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as SQLite from "expo-sqlite";
|
||||
const db = SQLite.openDatabase("lnreader.db");
|
||||
|
||||
const getLibraryQuery = `
|
||||
const getLibraryQuery = (sort, filter) => `
|
||||
SELECT novels.*, C.chaptersUnread, D.chaptersDownloaded
|
||||
FROM novels
|
||||
LEFT JOIN (
|
||||
@@ -18,15 +18,15 @@ const getLibraryQuery = `
|
||||
GROUP BY chapters.novelId
|
||||
) AS D
|
||||
ON novels.novelId = D.novelId
|
||||
WHERE novels.followed = 1
|
||||
GROUP BY novels.novelId
|
||||
WHERE novels.followed = 1 ${filter ? "AND " + filter : ""}
|
||||
${sort ? "ORDER BY " + sort : ""}
|
||||
`;
|
||||
|
||||
export const getLibrary = () => {
|
||||
export const getLibrary = (sort, filter) => {
|
||||
return new Promise((resolve, reject) =>
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
getLibraryQuery,
|
||||
getLibraryQuery(sort, filter),
|
||||
null,
|
||||
(txObj, { rows: { _array } }) => {
|
||||
// console.log(_array);
|
||||
|
||||
@@ -95,6 +95,12 @@ const useTrackingStatus = () => {
|
||||
return tracker;
|
||||
};
|
||||
|
||||
const useLibraryFilters = () => {
|
||||
const filters = useSelector((state) => state.libraryReducer.filters);
|
||||
|
||||
return filters;
|
||||
};
|
||||
|
||||
export {
|
||||
useTheme,
|
||||
useReaderSettings,
|
||||
@@ -106,4 +112,5 @@ export {
|
||||
useNovel,
|
||||
useChapter,
|
||||
useSavedSettings,
|
||||
useLibraryFilters,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import React, { useState, useCallback, useRef } from "react";
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
@@ -21,12 +21,22 @@ import {
|
||||
import { updateLibraryAction } from "../../redux/updates/updates.actions";
|
||||
import { useTheme } from "../../Hooks/reduxHooks";
|
||||
import { setNovel } from "../../redux/novel/novel.actions";
|
||||
import { Portal } from "react-native-paper";
|
||||
import LibraryFilterSheet from "./components/LibraryFilterSheet";
|
||||
|
||||
const LibraryScreen = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { loading, novels } = useSelector((state) => state.libraryReducer);
|
||||
const libraryFilterSheetRef = useRef(null);
|
||||
|
||||
const {
|
||||
loading,
|
||||
novels,
|
||||
filters: { sort, filter },
|
||||
} = useSelector((state) => state.libraryReducer);
|
||||
|
||||
console.log("1.", sort, filter);
|
||||
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
@@ -34,7 +44,7 @@ const LibraryScreen = ({ navigation }) => {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
setSearchText("");
|
||||
dispatch(getLibraryAction());
|
||||
dispatch(getLibraryAction(sort, filter));
|
||||
}, [getLibraryAction])
|
||||
);
|
||||
|
||||
@@ -45,13 +55,13 @@ const LibraryScreen = ({ navigation }) => {
|
||||
};
|
||||
|
||||
const clearSearchbar = () => {
|
||||
dispatch(getLibraryAction());
|
||||
dispatch(getLibraryAction(sort, filter));
|
||||
setSearchText("");
|
||||
};
|
||||
|
||||
const onChangeText = (text) => {
|
||||
setSearchText(text);
|
||||
dispatch(searchLibraryAction(text));
|
||||
dispatch(searchLibraryAction(text, sort, filter));
|
||||
};
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
@@ -73,6 +83,10 @@ const LibraryScreen = ({ navigation }) => {
|
||||
/>
|
||||
);
|
||||
|
||||
// if (!loading) {
|
||||
// console.log(novels);
|
||||
// }
|
||||
|
||||
const listEmptyComponent = () =>
|
||||
searchText !== "" ? (
|
||||
<Text style={[styles.emptySearch, { color: theme.colorAccent }]}>
|
||||
@@ -99,17 +113,30 @@ const LibraryScreen = ({ navigation }) => {
|
||||
clearSearchbar={clearSearchbar}
|
||||
onChangeText={onChangeText}
|
||||
left="magnify"
|
||||
right="dots-vertical"
|
||||
onPressRight={() => libraryFilterSheetRef.current.show()}
|
||||
theme={theme}
|
||||
/>
|
||||
{loading ? (
|
||||
<ActivityIndicator size="small" color={theme.colorAccent} />
|
||||
) : (
|
||||
<NovelList
|
||||
data={novels}
|
||||
renderItem={renderItem}
|
||||
refreshControl={refreshControl()}
|
||||
ListEmptyComponent={listEmptyComponent}
|
||||
/>
|
||||
<>
|
||||
<NovelList
|
||||
data={novels}
|
||||
renderItem={renderItem}
|
||||
refreshControl={refreshControl()}
|
||||
ListEmptyComponent={listEmptyComponent}
|
||||
/>
|
||||
<Portal>
|
||||
<LibraryFilterSheet
|
||||
bottomSheetRef={libraryFilterSheetRef}
|
||||
theme={theme}
|
||||
dispatch={dispatch}
|
||||
sort={sort}
|
||||
filter={filter}
|
||||
/>
|
||||
</Portal>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
import React, { useState } from "react";
|
||||
import { StyleSheet, View, Text, Animated } from "react-native";
|
||||
|
||||
import { List, RadioButton, Checkbox } from "react-native-paper";
|
||||
|
||||
import Bottomsheet from "rn-sliding-up-panel";
|
||||
import BottomSheetHandle from "../../../Components/BottomSheetHandle";
|
||||
import { ListItem } from "../../../Components/List";
|
||||
import { filterLibrary } from "../../../redux/library/library.actions";
|
||||
|
||||
const LibraryFilterSheet = ({
|
||||
bottomSheetRef,
|
||||
dispatch,
|
||||
sort,
|
||||
filter,
|
||||
theme,
|
||||
}) => {
|
||||
const [animatedValue] = useState(new Animated.Value(0));
|
||||
|
||||
return (
|
||||
<Bottomsheet
|
||||
animatedValue={animatedValue}
|
||||
ref={bottomSheetRef}
|
||||
draggableRange={{ top: 540, bottom: 0 }}
|
||||
snappingPoints={[0, 540]}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.contentContainer,
|
||||
{ backgroundColor: theme.colorPrimary },
|
||||
]}
|
||||
>
|
||||
<BottomSheetHandle theme={theme} />
|
||||
<View style={{ marginTop: 24 }}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.colorAccent,
|
||||
paddingHorizontal: 16,
|
||||
paddingBottom: 8,
|
||||
textTransform: "uppercase",
|
||||
}}
|
||||
>
|
||||
SORT
|
||||
</Text>
|
||||
<ListItem
|
||||
title="Date Added"
|
||||
theme={theme}
|
||||
right={
|
||||
sort === "novels.novelId ASC"
|
||||
? "arrow-up"
|
||||
: sort === "novels.novelId DESC"
|
||||
? "arrow-down"
|
||||
: null
|
||||
}
|
||||
onPress={() =>
|
||||
sort === "novels.novelId ASC"
|
||||
? dispatch(
|
||||
filterLibrary(
|
||||
"novels.novelId DESC",
|
||||
filter
|
||||
)
|
||||
)
|
||||
: dispatch(
|
||||
filterLibrary(
|
||||
"novels.novelId ASC",
|
||||
filter
|
||||
)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ListItem
|
||||
title="Alphabetically"
|
||||
theme={theme}
|
||||
right={
|
||||
sort === "novels.novelName ASC"
|
||||
? "arrow-up"
|
||||
: sort === "novels.novelName DESC"
|
||||
? "arrow-down"
|
||||
: null
|
||||
}
|
||||
onPress={() =>
|
||||
sort === "novels.novelName ASC"
|
||||
? dispatch(
|
||||
filterLibrary(
|
||||
"novels.novelName DESC",
|
||||
filter
|
||||
)
|
||||
)
|
||||
: dispatch(
|
||||
filterLibrary(
|
||||
"novels.novelName ASC",
|
||||
filter
|
||||
)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ListItem
|
||||
title="Unread"
|
||||
theme={theme}
|
||||
right={
|
||||
sort === "novels.unread ASC"
|
||||
? "arrow-up"
|
||||
: sort === "novels.unread DESC"
|
||||
? "arrow-down"
|
||||
: null
|
||||
}
|
||||
onPress={() =>
|
||||
sort === "novels.unread ASC"
|
||||
? dispatch(
|
||||
filterLibrary(
|
||||
"novels.unread DESC",
|
||||
filter
|
||||
)
|
||||
)
|
||||
: dispatch(
|
||||
filterLibrary("novels.unread ASC", filter)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ListItem
|
||||
title="Downloaded"
|
||||
theme={theme}
|
||||
right={
|
||||
sort === "chaptersDownloaded ASC"
|
||||
? "arrow-up"
|
||||
: sort === "chaptersDownloaded DESC"
|
||||
? "arrow-down"
|
||||
: null
|
||||
}
|
||||
onPress={() =>
|
||||
sort === "chaptersDownloaded ASC"
|
||||
? dispatch(
|
||||
filterLibrary(
|
||||
"chaptersDownloaded DESC",
|
||||
filter
|
||||
)
|
||||
)
|
||||
: dispatch(
|
||||
filterLibrary(
|
||||
"chaptersDownloaded ASC",
|
||||
filter
|
||||
)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ListItem
|
||||
title="Total Chapters"
|
||||
theme={theme}
|
||||
right={
|
||||
sort === "chaptersUnread ASC"
|
||||
? "arrow-up"
|
||||
: sort === "chaptersUnread DESC"
|
||||
? "arrow-down"
|
||||
: null
|
||||
}
|
||||
onPress={() =>
|
||||
sort === "chaptersUnread ASC"
|
||||
? dispatch(
|
||||
filterLibrary(
|
||||
"chaptersUnread DESC",
|
||||
filter
|
||||
)
|
||||
)
|
||||
: dispatch(
|
||||
filterLibrary(
|
||||
"chaptersUnread ASC",
|
||||
filter
|
||||
)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.colorAccent,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
textTransform: "uppercase",
|
||||
}}
|
||||
>
|
||||
Filter
|
||||
</Text>
|
||||
<Checkbox.Item
|
||||
label="Downloaded"
|
||||
labelStyle={{ color: theme.textColorPrimary }}
|
||||
color={theme.colorAccent}
|
||||
uncheckedColor={theme.textColorHint}
|
||||
status={
|
||||
filter === "chaptersDownloaded > 0"
|
||||
? "checked"
|
||||
: "unchecked"
|
||||
}
|
||||
onPress={() =>
|
||||
filter === "chaptersDownloaded > 0"
|
||||
? dispatch(filterLibrary(sort, ""))
|
||||
: dispatch(
|
||||
filterLibrary(
|
||||
sort,
|
||||
"chaptersDownloaded > 0"
|
||||
)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Checkbox.Item
|
||||
label="Unread"
|
||||
labelStyle={{ color: theme.textColorPrimary }}
|
||||
color={theme.colorAccent}
|
||||
uncheckedColor={theme.textColorHint}
|
||||
status={
|
||||
filter === "unread = 1" ? "checked" : "unchecked"
|
||||
}
|
||||
onPress={() =>
|
||||
filter === "unread = 1"
|
||||
? dispatch(filterLibrary(sort, ""))
|
||||
: dispatch(filterLibrary(sort, "unread = 1"))
|
||||
}
|
||||
/>
|
||||
<Checkbox.Item
|
||||
label="Completed"
|
||||
labelStyle={{ color: theme.textColorPrimary }}
|
||||
color={theme.colorAccent}
|
||||
uncheckedColor={theme.textColorHint}
|
||||
status={
|
||||
filter === "chaptersUnread IS NULL"
|
||||
? "checked"
|
||||
: "unchecked"
|
||||
}
|
||||
onPress={() =>
|
||||
filter === "chaptersUnread IS NULL"
|
||||
? dispatch(filterLibrary(sort, ""))
|
||||
: dispatch(
|
||||
filterLibrary(
|
||||
sort,
|
||||
"chaptersUnread IS NULL"
|
||||
)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Bottomsheet>
|
||||
);
|
||||
};
|
||||
|
||||
export default LibraryFilterSheet;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
borderTopRightRadius: 8,
|
||||
borderTopLeftRadius: 8,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -56,7 +56,7 @@ const Novel = ({ route, navigation }) => {
|
||||
}, [getNovelAction]);
|
||||
|
||||
const onRefresh = () => {
|
||||
dispatch(updateNovelAction(sourceId, novelUrl, novelId));
|
||||
dispatch(updateNovelAction(sourceId, novelUrl, novelId, sort, filter));
|
||||
showToast(`Updated ${novelName}`);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
GET_LIBRARY_NOVELS,
|
||||
GET_LIBRARY_SEARCH_RESULTS,
|
||||
SET_LIBRARY_LOADING,
|
||||
SORT_FILTER_LIBRARY,
|
||||
} from "./library.types";
|
||||
import {
|
||||
getLibrary,
|
||||
@@ -9,8 +10,9 @@ import {
|
||||
} from "../../Database/queries/LibraryQueries";
|
||||
import { restoreBackup } from "../../Services/backup";
|
||||
|
||||
export const getLibraryAction = () => async (dispatch) => {
|
||||
const res = await getLibrary();
|
||||
export const getLibraryAction = (sort, filter) => async (dispatch) => {
|
||||
// console.log(sort, filter);
|
||||
const res = await getLibrary(sort, filter);
|
||||
|
||||
dispatch({
|
||||
type: GET_LIBRARY_NOVELS,
|
||||
@@ -18,11 +20,23 @@ export const getLibraryAction = () => async (dispatch) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const searchLibraryAction = (searchText) => async (dispatch) => {
|
||||
const res = await searchLibrary(searchText);
|
||||
export const searchLibraryAction =
|
||||
(searchText, sort, filter) => async (dispatch) => {
|
||||
const res = await searchLibrary(searchText, sort, filter);
|
||||
|
||||
dispatch({
|
||||
type: GET_LIBRARY_SEARCH_RESULTS,
|
||||
payload: res,
|
||||
});
|
||||
};
|
||||
|
||||
export const filterLibrary = (sort, filter) => async (dispatch) => {
|
||||
dispatch({ type: SORT_FILTER_LIBRARY, payload: { sort, filter } });
|
||||
|
||||
const res = await getLibrary(sort, filter);
|
||||
|
||||
dispatch({
|
||||
type: GET_LIBRARY_SEARCH_RESULTS,
|
||||
type: GET_LIBRARY_NOVELS,
|
||||
payload: res,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,11 +2,13 @@ import {
|
||||
GET_LIBRARY_NOVELS,
|
||||
GET_LIBRARY_SEARCH_RESULTS,
|
||||
SET_LIBRARY_LOADING,
|
||||
SORT_FILTER_LIBRARY,
|
||||
} from "./library.types";
|
||||
|
||||
const initialState = {
|
||||
novels: [],
|
||||
searchResults: [],
|
||||
filters: { sort: "novels.novelId ASC", filter: "" },
|
||||
loading: true,
|
||||
};
|
||||
|
||||
@@ -20,6 +22,12 @@ const libraryReducer = (state = initialState, action) => {
|
||||
return { ...state, novels: payload, loading: false };
|
||||
case GET_LIBRARY_SEARCH_RESULTS:
|
||||
return { ...state, novels: payload, loading: false };
|
||||
case SORT_FILTER_LIBRARY:
|
||||
console.log("2.", payload.sort, payload.filter);
|
||||
return {
|
||||
...state,
|
||||
filters: { sort: payload.sort, filter: payload.filter },
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export const GET_LIBRARY_NOVELS = "GET_LIBRARY_NOVELS";
|
||||
export const GET_LIBRARY_SEARCH_RESULTS = "GET_LIBRARY_SEARCH_RESULTS";
|
||||
export const SET_LIBRARY_LOADING = "SET_LIBRARY_LOADING";
|
||||
|
||||
export const SORT_FILTER_LIBRARY = "SORT_FILTER_LIBRARY";
|
||||
|
||||
@@ -257,13 +257,13 @@ export const deleteAllChaptersAction = (chapters) => async (dispatch) => {
|
||||
};
|
||||
|
||||
export const updateNovelAction =
|
||||
(sourceId, novelUrl, novelId) => async (dispatch) => {
|
||||
(sourceId, novelUrl, novelId, sort, filter) => async (dispatch) => {
|
||||
dispatch({ type: FETCHING_NOVEL });
|
||||
|
||||
await updateNovel(sourceId, novelUrl, novelId);
|
||||
|
||||
let novel = await getNovel(sourceId, novelUrl);
|
||||
let chapters = await getChapters(novel.novelId);
|
||||
let chapters = await getChapters(novel.novelId, sort, filter);
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_NOVEL,
|
||||
|
||||
Reference in New Issue
Block a user