Add global search (#12)
* Add global search * Split GlobalSearch into components
This commit is contained in:
committed by
GitHub
parent
4251ec7f20
commit
489d5ba714
@@ -25,9 +25,9 @@ export const Searchbar = ({
|
||||
|
||||
const displayMenuIcon = () => {
|
||||
const icons = {
|
||||
0: "view-grid",
|
||||
0: "view-module",
|
||||
1: "view-list",
|
||||
2: "view-grid",
|
||||
2: "view-module",
|
||||
};
|
||||
|
||||
return icons[displayMode];
|
||||
@@ -92,7 +92,7 @@ export const Searchbar = ({
|
||||
)}
|
||||
{right && (
|
||||
<IconButton
|
||||
icon="earth"
|
||||
icon={right}
|
||||
color={theme.textColorSecondary}
|
||||
style={{ marginRight: 0 }}
|
||||
size={23}
|
||||
|
||||
@@ -8,6 +8,7 @@ import BottomNavigator from "./BottomNavigator";
|
||||
import Novel from "../Screens/Novel/Novel";
|
||||
import Chapter from "../Screens/Chapter/Chapter";
|
||||
import Extension from "../Screens/Extension/Extension";
|
||||
import GlobalSearch from "../Screens/Browse/GlobalSearch";
|
||||
import MoreStack from "./More";
|
||||
|
||||
import { setStatusBarStyle } from "../Hooks/setStatusBarStyle";
|
||||
@@ -53,6 +54,7 @@ const MainNavigator = () => {
|
||||
/>
|
||||
<Stack.Screen name="MoreStack" component={MoreStack} />
|
||||
<Stack.Screen name="Extension" component={Extension} />
|
||||
<Stack.Screen name="GlobalSearch" component={GlobalSearch} />
|
||||
</Stack.Navigator>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -20,7 +20,7 @@ import SourceCard from "./components/SourceCard";
|
||||
import { showToast } from "../../Hooks/showToast";
|
||||
import { Searchbar } from "../../Components/Searchbar";
|
||||
|
||||
const Browse = () => {
|
||||
const Browse = ({ navigation }) => {
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
// const [globalSearch, setGlobalSearch] = useState(false);
|
||||
@@ -79,6 +79,8 @@ const Browse = () => {
|
||||
searchText={searchText}
|
||||
onChangeText={onChangeText}
|
||||
clearSearchbar={clearSearchbar}
|
||||
right="book-search"
|
||||
onPressRight={() => navigation.navigate("GlobalSearch")}
|
||||
/>
|
||||
|
||||
<FlatList
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
Text,
|
||||
FlatList,
|
||||
ActivityIndicator,
|
||||
} from "react-native";
|
||||
import { useLibrary, useTheme } from "../../Hooks/reduxHooks";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import { Searchbar } from "../../Components/Searchbar";
|
||||
import NovelCover from "../../Components/NovelCover";
|
||||
import NovelList from "../../Components/NovelList";
|
||||
import EmptyView from "../../Components/EmptyView";
|
||||
import GlobalSearchNovelList from "./components/GlobalSearchNovelList";
|
||||
|
||||
const GlobalSearch = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [searchResults, setSearchResults] = useState("");
|
||||
const { sources, pinned } = useSelector((state) => state.sourceReducer);
|
||||
|
||||
const library = useLibrary();
|
||||
|
||||
const pinnedSources = sources.filter(
|
||||
(source) => pinned.indexOf(source.sourceId) !== -1
|
||||
);
|
||||
|
||||
const clearSearchbar = () => {
|
||||
setSearchText("");
|
||||
};
|
||||
|
||||
const onChangeText = (text) => setSearchText(text);
|
||||
|
||||
const onSubmitEditing = () => {
|
||||
setSearchResults("");
|
||||
|
||||
const getSearchUrl = (sourceId) => {
|
||||
if (sourceId === 1) {
|
||||
return `https://lnreader-extensions.vercel.app/api/1/search/?s=${searchText}&?o=rating`;
|
||||
} else {
|
||||
return `https://lnreader-extensions.vercel.app/api/${sourceId}/search/?s=${searchText}`;
|
||||
}
|
||||
};
|
||||
|
||||
pinnedSources.map((source, index) =>
|
||||
setTimeout(() => {
|
||||
setLoading(true);
|
||||
fetch(getSearchUrl(source.sourceId))
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: source.sourceId,
|
||||
sourceName: source.sourceName,
|
||||
sourceLanguage: source.sourceLanguage,
|
||||
novels: json,
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
});
|
||||
}, 3000 * index)
|
||||
);
|
||||
};
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<>
|
||||
<View style={{ padding: 8, paddingVertical: 16 }}>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
{item.sourceName}
|
||||
</Text>
|
||||
<Text style={{ color: theme.textColorSecondary, fontSize: 12 }}>
|
||||
{item.sourceLanguage}
|
||||
</Text>
|
||||
</View>
|
||||
<GlobalSearchNovelList
|
||||
data={item.novels}
|
||||
theme={theme}
|
||||
library={library}
|
||||
navigation={navigation}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<Searchbar
|
||||
theme={theme}
|
||||
placeholder="Global Search"
|
||||
left="arrow-left"
|
||||
onPressLeft={() => navigation.goBack()}
|
||||
searchText={searchText}
|
||||
onChangeText={onChangeText}
|
||||
onSubmitEditing={onSubmitEditing}
|
||||
clearSearchbar={clearSearchbar}
|
||||
/>
|
||||
|
||||
<FlatList
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
padding: 4,
|
||||
}}
|
||||
data={searchResults}
|
||||
keyExtractor={(item) => item.sourceId.toString()}
|
||||
renderItem={renderItem}
|
||||
extraData={pinned}
|
||||
ListEmptyComponent={
|
||||
!loading && (
|
||||
<EmptyView
|
||||
icon="__φ(..)"
|
||||
description="Search a novel in your pinned sources"
|
||||
/>
|
||||
)
|
||||
}
|
||||
ListFooterComponent={
|
||||
loading && (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
padding: 16,
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator
|
||||
size="large"
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalSearch;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1 },
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View, Text, ImageBackground } from "react-native";
|
||||
import { TouchableRipple } from "react-native-paper";
|
||||
|
||||
import { useSettings, useTheme } from "../../../Hooks/reduxHooks";
|
||||
|
||||
const GlobalSearchNovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const comfortableTitle = () => (
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.title,
|
||||
{
|
||||
width: 115,
|
||||
color: theme.textColorPrimary,
|
||||
padding: 4,
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<TouchableRipple
|
||||
borderless
|
||||
centered
|
||||
rippleColor={theme.rippleColor}
|
||||
style={styles.opac}
|
||||
onPress={onPress}
|
||||
>
|
||||
<>
|
||||
<ImageBackground
|
||||
source={{ uri: item.novelCover }}
|
||||
style={{ height: 150, width: 115 }}
|
||||
imageStyle={[
|
||||
{ borderRadius: 4 },
|
||||
libraryStatus && { opacity: 0.5 },
|
||||
]}
|
||||
progressiveRenderingEnabled={true}
|
||||
/>
|
||||
{comfortableTitle()}
|
||||
</>
|
||||
</TouchableRipple>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalSearchNovelCover;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
titleContainer: {
|
||||
flex: 1,
|
||||
justifyContent: "flex-end",
|
||||
borderRadius: 4,
|
||||
},
|
||||
title: {
|
||||
fontFamily: "pt-sans-bold",
|
||||
fontSize: 14,
|
||||
padding: 8,
|
||||
},
|
||||
opac: {
|
||||
paddingHorizontal: 5,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 4,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, FlatList, Text } from "react-native";
|
||||
import GlobalSearchNovelCover from "./GlobalSearchNovelCover";
|
||||
|
||||
const GlobalSearchNovelList = ({ data, theme, library, navigation }) => {
|
||||
const checkIFInLibrary = (sourceId, novelUrl) => {
|
||||
return library.some(
|
||||
(obj) => obj.novelUrl === novelUrl && obj.sourceId === sourceId
|
||||
);
|
||||
};
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<GlobalSearchNovelCover
|
||||
item={item}
|
||||
onPress={() =>
|
||||
navigation.navigate("Novel", {
|
||||
...item,
|
||||
sourceId: item.extensionId,
|
||||
})
|
||||
}
|
||||
libraryStatus={checkIFInLibrary(item.extensionId, item.novelUrl)}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
contentContainerStyle={styles.flatListCont}
|
||||
horizontal={true}
|
||||
data={data}
|
||||
keyExtractor={(item) => item.novelUrl}
|
||||
renderItem={renderItem}
|
||||
ListEmptyComponent={
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
padding: 8,
|
||||
paddingVertical: 4,
|
||||
}}
|
||||
>
|
||||
No results found
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalSearchNovelList;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
flatListCont: {
|
||||
flexGrow: 1,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
});
|
||||
@@ -50,8 +50,10 @@ const Novel = ({
|
||||
novelId || (!loading && novel.novelId)
|
||||
);
|
||||
|
||||
let lastRead = useContinueReading(chapters, novel.novelId);
|
||||
console.log(lastRead);
|
||||
let lastRead = useContinueReading(
|
||||
chapters,
|
||||
novelId || (!loading && novel.novelId)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getNovelAction(followed, sourceId, novelUrl, novelId, sort, filter);
|
||||
|
||||
Reference in New Issue
Block a user