Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Faster & less laggy global search #1255

Merged
merged 3 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ module.exports = {
trailingComma: 'all',
arrowParens: 'avoid',
quoteProps: 'preserve',
endOfLine: 'auto', // stop prettier from getting mad on windows
};
8 changes: 5 additions & 3 deletions src/hooks/common/useSearch.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { useIsFocused } from '@react-navigation/native';
import { useCallback, useEffect, useState } from 'react';

const useSearch = (defaultSearchText?: string) => {
const useSearch = (defaultSearchText?: string, clearSearchOnUnfocus = true) => {
const isFocused = useIsFocused();

const [searchText, setSearchText] = useState<string>(defaultSearchText || '');

const clearSearchbar = useCallback(() => setSearchText(''), []);

useEffect(() => {
if (!isFocused) {
clearSearchbar();
if (clearSearchOnUnfocus) {
if (!isFocused) {
clearSearchbar();
}
}
}, [isFocused, clearSearchbar]);

Expand Down
4 changes: 4 additions & 0 deletions src/hooks/persisted/useSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface AppSettings {
showUpdatesTab: boolean;
showLabelsInNav: boolean;
useFabForContinueReading: boolean;
disableLoadingAnimations: boolean;

/**
* Library settings
Expand Down Expand Up @@ -56,6 +57,7 @@ export interface AppSettings {
export interface BrowseSettings {
showMyAnimeList: boolean;
showAniList: boolean;
globalSearchConcurrency?: number;
}

export interface LibrarySettings {
Expand Down Expand Up @@ -125,6 +127,7 @@ const initialAppSettings: AppSettings = {
showUpdatesTab: true,
showLabelsInNav: true,
useFabForContinueReading: false,
disableLoadingAnimations: false,

/**
* Library settings
Expand Down Expand Up @@ -153,6 +156,7 @@ const initialAppSettings: AppSettings = {
const initialBrowseSettings: BrowseSettings = {
showMyAnimeList: true,
showAniList: true,
globalSearchConcurrency: 1,
};

export const initialChapterGeneralSettings: ChapterGeneralSettings = {
Expand Down
2 changes: 1 addition & 1 deletion src/navigators/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import MigrateNovel from '../screens/browse/migration/MigrationNovels';
import MalTopNovels from '../screens/browse/discover/MalTopNovels';
import AniListTopNovels from '../screens/browse/discover/AniListTopNovels';
import NewUpdateDialog from '../components/NewUpdateDialog';
import BrowseSettings from '../screens/browse/BrowseSettings';
import BrowseSettings from '../screens/browse/settings/BrowseSettings';
import WebviewScreen from '@screens/WebviewScreen/WebviewScreen';
import { RootStackParamList } from './types';
import Color from 'color';
Expand Down
3 changes: 3 additions & 0 deletions src/screens/Categories/components/CategorySkeletonLoading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createShimmerPlaceholder } from 'react-native-shimmer-placeholder';
import { LinearGradient } from 'expo-linear-gradient';
import { ThemeColors } from '@theme/types';
import getLoadingColors from '@utils/getLoadingColors';
import { useAppSettings } from '@hooks/persisted/index';

interface Props {
width: number;
Expand All @@ -12,6 +13,7 @@ interface Props {
}

const CategorySkeletonLoading: React.FC<Props> = ({ height, width, theme }) => {
const { disableLoadingAnimations } = useAppSettings();
const ShimmerPlaceHolder = createShimmerPlaceholder(LinearGradient);

const [highlightColor, backgroundColor] = getLoadingColors(theme);
Expand All @@ -24,6 +26,7 @@ const CategorySkeletonLoading: React.FC<Props> = ({ height, width, theme }) => {
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={height}
width={width}
stopAutoRun={disableLoadingAnimations}
/>
</View>
);
Expand Down
1 change: 1 addition & 0 deletions src/screens/GlobalSearchScreen/GlobalSearchScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const GlobalSearchScreen = (props: Props) => {
const theme = useTheme();
const { searchText, setSearchText, clearSearchbar } = useSearch(
props?.route?.params?.searchText,
false,
);
const onChangeText = (text: string) => setSearchText(text);
const onSubmitEditing = () => globalSearch(searchText);
Expand Down
219 changes: 114 additions & 105 deletions src/screens/GlobalSearchScreen/components/GlobalSearchResultsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import { GlobalSearchResult } from '../hooks/useGlobalSearch';
import GlobalSearchNovelItem from './GlobalSearchNovelItem';
import { useLibraryNovels } from '@screens/library/hooks/useLibrary';
import { LibraryNovelInfo } from '@database/types';
import GlobalSearchSkeletonLoading from '@screens/browse/loadingAnimation/GlobalSearchSkeletonLoading';
import { switchNovelToLibrary } from '@database/queries/NovelQueries';
import GlobalSearchSkeletonLoading from '@screens/browse/loadingAnimation/GlobalSearchSkeletonLoading';
import { interpolateColor } from 'react-native-reanimated';

interface GlobalSearchResultsListProps {
searchResults: GlobalSearchResult[];
Expand All @@ -25,130 +26,138 @@ const GlobalSearchResultsList: React.FC<GlobalSearchResultsListProps> = ({
searchResults,
ListEmptyComponent,
}) => {
const theme = useTheme();
const navigation = useNavigation<StackNavigationProp<any>>();
const keyExtractor = useCallback(
(item: GlobalSearchResult) => item.plugin.id,
[],
);

return (
<FlatList<GlobalSearchResult>
keyExtractor={keyExtractor}
data={searchResults}
contentContainerStyle={styles.resultList}
renderItem={({ item }) => <GlobalSearchSourceResults item={item} />}
ListEmptyComponent={ListEmptyComponent}
/>
);
};

const GlobalSearchSourceResults: React.FC<{ item: GlobalSearchResult }> = ({
item,
}) => {
const theme = useTheme();
const navigation = useNavigation<StackNavigationProp<any>>();
const { library, setLibrary } = useLibraryNovels();

const novelInLibrary = (pluginId: string, novelPath: string) =>
library?.some(
novel => novel.pluginId === pluginId && novel.path === novelPath,
);

const errorColor = useMemo(() => (theme.isDark ? '#B3261E' : '#F2B8B5'), []);
const errorColor = theme.isDark ? '#B3261E' : '#F2B8B5';
const noResultsColor = interpolateColor(
0.8,
[0, 1],
['transparent', theme.onSurfaceVariant],
);

const navigateToNovel = useCallback(
(item: { name: string; path: string; pluginId: string }) =>
navigation.push('Novel', item),
[],
);

return (
<FlatList<GlobalSearchResult>
keyExtractor={keyExtractor}
data={searchResults}
contentContainerStyle={styles.resultList}
renderItem={({ item }) => (
<>
<View>
<Pressable
android_ripple={{
color: color(theme.primary).alpha(0.12).string(),
}}
style={styles.sourceHeader}
onPress={() =>
navigation.navigate('SourceScreen', {
pluginId: item.plugin.id,
pluginName: item.plugin.name,
site: item.plugin.site,
})
}
>
<View>
<Text style={[styles.sourceName, { color: theme.onSurface }]}>
{item.plugin.name}
</Text>
<Text
style={[styles.language, { color: theme.onSurfaceVariant }]}
>
{item.plugin.lang}
</Text>
</View>
<MaterialCommunityIcons
name="arrow-right"
size={24}
color={theme.onSurface}
/>
</Pressable>
{item.isLoading ? (
<GlobalSearchSkeletonLoading theme={theme} />
) : item.error ? (
<Text style={[styles.error, { color: errorColor }]}>
{item.error}
return useMemo(
() => (
<>
<View>
<Pressable
android_ripple={{
color: color(theme.primary).alpha(0.12).string(),
}}
style={styles.sourceHeader}
onPress={() =>
navigation.navigate('SourceScreen', {
pluginId: item.plugin.id,
pluginName: item.plugin.name,
site: item.plugin.site,
})
}
>
<View>
<Text style={[styles.sourceName, { color: theme.onSurface }]}>
{item.plugin.name}
</Text>
) : (
<FlatList
horizontal
contentContainerStyle={styles.novelsContainer}
keyExtractor={novelItem =>
item.plugin.id + '_' + novelItem.path
}
data={item.novels}
ListEmptyComponent={
<Text
style={[
styles.listEmpty,
{ color: theme.onSurfaceVariant },
]}
>
{getString('sourceScreen.noResultsFound')}
</Text>
}
renderItem={({ item: novelItem }) => {
const inLibrary = novelInLibrary(
item.plugin.id,
novelItem.path,
);
<Text
style={[styles.language, { color: theme.onSurfaceVariant }]}
>
{item.plugin.lang}
</Text>
</View>
<MaterialCommunityIcons
name="arrow-right"
size={24}
color={theme.onSurface}
/>
</Pressable>
{item.isLoading ? (
<GlobalSearchSkeletonLoading theme={theme} />
) : item.error ? (
<Text style={[styles.error, { color: errorColor }]}>
{item.error}
</Text>
) : (
<FlatList
horizontal
contentContainerStyle={styles.novelsContainer}
keyExtractor={novelItem => item.plugin.id + '_' + novelItem.path}
data={item.novels}
ListEmptyComponent={
<Text style={[styles.listEmpty, { color: noResultsColor }]}>
{getString('sourceScreen.noResultsFound')}
</Text>
}
renderItem={({ item: novelItem }) => {
const inLibrary = novelInLibrary(
item.plugin.id,
novelItem.path,
);

return (
<GlobalSearchNovelItem
novel={novelItem}
pluginId={item.plugin.id}
inLibrary={inLibrary}
navigateToNovel={navigateToNovel}
theme={theme}
onLongPress={() => {
setLibrary(prevValues => {
if (inLibrary) {
return [
...prevValues.filter(
novel => novel.path !== novelItem.path,
),
];
} else {
return [
...prevValues,
{
path: novelItem.path,
} as LibraryNovelInfo,
];
}
});
switchNovelToLibrary(novelItem.path, item.plugin.id);
}}
/>
);
}}
/>
)}
</View>
</>
)}
ListEmptyComponent={ListEmptyComponent}
/>
return (
<GlobalSearchNovelItem
novel={novelItem}
pluginId={item.plugin.id}
inLibrary={inLibrary}
navigateToNovel={navigateToNovel}
theme={theme}
onLongPress={() => {
setLibrary(prevValues => {
if (inLibrary) {
return [
...prevValues.filter(
novel => novel.path !== novelItem.path,
),
];
} else {
return [
...prevValues,
{
path: novelItem.path,
} as LibraryNovelInfo,
];
}
});
switchNovelToLibrary(novelItem.path, item.plugin.id);
}}
/>
);
}}
/>
)}
</View>
</>
),
[item.isLoading],
);
};

Expand Down
Loading
Loading