Add track results to search queries
This commit is contained in:
@@ -2,9 +2,9 @@ import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import Input from 'components/Input';
|
||||
import { Text, View } from 'react-native';
|
||||
import styled from 'styled-components/native';
|
||||
import { useTypedSelector } from 'store';
|
||||
import { useAppDispatch, useTypedSelector } from 'store';
|
||||
import Fuse from 'fuse.js';
|
||||
import { Album } from 'store/music/types';
|
||||
import { Album, AlbumTrack } from 'store/music/types';
|
||||
import { FlatList } from 'react-native-gesture-handler';
|
||||
import TouchableHandler from 'components/TouchableHandler';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
@@ -13,6 +13,7 @@ import { NavigationProp } from '../types';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import { t } from '@localisation';
|
||||
import useDefaultStyles from 'components/Colors';
|
||||
import { searchAndFetchAlbums } from 'store/music/actions';
|
||||
|
||||
const Container = styled.View`
|
||||
padding: 0 20px;
|
||||
@@ -46,18 +47,37 @@ const fuseOptions = {
|
||||
includeScore: true,
|
||||
};
|
||||
|
||||
type AudioResult = {
|
||||
type: 'Audio',
|
||||
id: string;
|
||||
album: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type AlbumResult = {
|
||||
type: 'AlbumArtist',
|
||||
id: string;
|
||||
album: undefined;
|
||||
name: undefined;
|
||||
}
|
||||
|
||||
type CombinedResults = (AudioResult | AlbumResult)[];
|
||||
|
||||
export default function Search() {
|
||||
const defaultStyles = useDefaultStyles();
|
||||
|
||||
// Prepare state
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const albums = useTypedSelector(state => state.music.albums.entities);
|
||||
const [results, setResults] = useState<Fuse.FuseResult<Album>[]>([]);
|
||||
const fuse = useRef<Fuse<Album, typeof fuseOptions>>();
|
||||
const [results, setResults] = useState<CombinedResults>([]);
|
||||
// const [isLoading, setLoading] = useState(false);
|
||||
const fuse = useRef<Fuse<Album>>();
|
||||
|
||||
// Prepare helpers
|
||||
const navigation = useNavigation<NavigationProp>();
|
||||
const getImage = useGetImage();
|
||||
const credentials = useTypedSelector(state => state.settings.jellyfin);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
/**
|
||||
* Since it is impractical to have a global fuse variable, we need to
|
||||
@@ -75,14 +95,52 @@ export default function Search() {
|
||||
* them to state
|
||||
*/
|
||||
useEffect(() => {
|
||||
// GUARD: In some extraordinary cases, Fuse might not be presented since
|
||||
// it is assigned via refs. In this case, we can't handle any searching.
|
||||
if (!fuse.current) {
|
||||
return;
|
||||
}
|
||||
const retrieveResults = async () => {
|
||||
// GUARD: In some extraordinary cases, Fuse might not be presented since
|
||||
// it is assigned via refs. In this case, we can't handle any searching.
|
||||
if (!fuse.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
setResults(fuse.current.search(searchTerm));
|
||||
}, [searchTerm, setResults, fuse]);
|
||||
// First set the immediate results from fuse
|
||||
const fuseResults = fuse.current.search(searchTerm);
|
||||
const albums: AlbumResult[] = fuseResults
|
||||
.map(({ item }) => ({
|
||||
id: item.Id,
|
||||
type: 'AlbumArtist',
|
||||
album: undefined,
|
||||
name: undefined,
|
||||
}));
|
||||
const albumIds = fuseResults.map(({ item }) => item.Id);
|
||||
|
||||
// Assign the preliminary results
|
||||
setResults(albums);
|
||||
|
||||
// Then query the Jellyfin API
|
||||
const { payload } = await dispatch(searchAndFetchAlbums({ term: searchTerm }));
|
||||
|
||||
const items = (payload as
|
||||
{ results: (Album | AlbumTrack)[] }
|
||||
).results.filter(item => (
|
||||
!(item.Type === 'MusicAlbum' && albumIds.includes(item.Id))
|
||||
)).map((item) => ({
|
||||
type: item.Type,
|
||||
id: item.Id,
|
||||
album: item.Type === 'Audio'
|
||||
? item.AlbumId
|
||||
: undefined,
|
||||
name: item.Type === 'Audio'
|
||||
? item.Name
|
||||
: undefined,
|
||||
}));
|
||||
|
||||
// Then add those to the results
|
||||
// console.log(results, items);
|
||||
setResults([...albums, ...items] as CombinedResults);
|
||||
};
|
||||
|
||||
retrieveResults();
|
||||
}, [searchTerm, setResults, fuse, credentials, dispatch]);
|
||||
|
||||
// Handlers
|
||||
const selectAlbum = useCallback((id: string) =>
|
||||
@@ -92,7 +150,9 @@ export default function Search() {
|
||||
const HeaderComponent = React.useMemo(() => (
|
||||
<Container>
|
||||
<Input value={searchTerm} onChangeText={setSearchTerm} style={defaultStyles.input} placeholder={t('search') + '...'} />
|
||||
{(searchTerm.length && !results.length) ? <Text style={{ textAlign: 'center' }}>{t('no-results')}</Text> : null}
|
||||
{(searchTerm.length && !results.length)
|
||||
? <Text style={{ textAlign: 'center' }}>{t('no-results')}</Text>
|
||||
: null}
|
||||
</Container>
|
||||
), [searchTerm, results, setSearchTerm, defaultStyles]);
|
||||
|
||||
@@ -103,24 +163,37 @@ export default function Search() {
|
||||
}
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={results}
|
||||
renderItem={({ item: { item: album } }) =>(
|
||||
<TouchableHandler id={album.Id} onPress={selectAlbum}>
|
||||
<SearchResult style={defaultStyles.border}>
|
||||
<AlbumImage source={{ uri: getImage(album.Id) }} />
|
||||
<View>
|
||||
<Text numberOfLines={1} ellipsizeMode="tail" style={defaultStyles.text}>
|
||||
{album.Name} - {album.AlbumArtist}
|
||||
</Text>
|
||||
<HalfOpacity style={defaultStyles.text}>{t('album')}</HalfOpacity>
|
||||
</View>
|
||||
</SearchResult>
|
||||
</TouchableHandler>
|
||||
)}
|
||||
keyExtractor={(item) => item.refIndex.toString()}
|
||||
ListHeaderComponent={HeaderComponent}
|
||||
extraData={searchTerm}
|
||||
/>
|
||||
<>
|
||||
<FlatList
|
||||
data={results}
|
||||
renderItem={({ item: { id, type, album: trackAlbum, name: trackName } }: { item: AlbumResult | AudioResult }) => {
|
||||
const album = albums[trackAlbum || id];
|
||||
|
||||
if (!album) {
|
||||
console.log('Couldnt find ', trackAlbum, id);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableHandler id={album.Id} onPress={selectAlbum}>
|
||||
<SearchResult style={defaultStyles.border}>
|
||||
<AlbumImage source={{ uri: getImage(album.Id) }} />
|
||||
<View>
|
||||
<Text numberOfLines={1} ellipsizeMode="tail" style={defaultStyles.text}>
|
||||
{trackName || album.Name} - {album.AlbumArtist}
|
||||
</Text>
|
||||
<HalfOpacity style={defaultStyles.text}>
|
||||
{type === 'AlbumArtist' ? t('album'): t('track')}
|
||||
</HalfOpacity>
|
||||
</View>
|
||||
</SearchResult>
|
||||
</TouchableHandler>
|
||||
);
|
||||
}}
|
||||
keyExtractor={(item) => item.id}
|
||||
ListHeaderComponent={HeaderComponent}
|
||||
extraData={[searchTerm, albums]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user