Add stubs for filters in search

This commit is contained in:
Lei Nelissen
2022-05-16 22:17:00 +02:00
parent ccc0c211fb
commit a719e309ad
5 changed files with 143 additions and 42 deletions

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef, useCallback } from 'react';
import Input from 'components/Input';
import { ActivityIndicator, SafeAreaView, TextInput, View } from 'react-native';
import { ActivityIndicator, SafeAreaView, View } from 'react-native';
import styled from 'styled-components/native';
import { useTypedSelector } from 'store';
import Fuse from 'fuse.js';
@@ -9,7 +9,6 @@ import { FlatList } from 'react-native-gesture-handler';
import TouchableHandler from 'components/TouchableHandler';
import { useNavigation } from '@react-navigation/native';
import { useGetImage } from 'utility/JellyfinApi';
import { MusicNavigationProp } from '../types';
import FastImage from 'react-native-fast-image';
import { t } from '@localisation';
import useDefaultStyles from 'components/Colors';
@@ -17,23 +16,33 @@ import { searchAndFetchAlbums } from 'store/music/actions';
import { debounce } from 'lodash';
import { useDispatch } from 'react-redux';
import { Text } from 'components/Typography';
import { MusicNavigationProp } from 'screens/Music/types';
import DownloadIcon from 'components/DownloadIcon';
import ChevronRight from 'assets/icons/chevron-right.svg';
import SearchIcon from 'assets/icons/magnifying-glass.svg';
import { ShadowWrapper } from 'components/Shadow';
// import MicrophoneIcon from 'assets/icons/microphone.svg';
// import AlbumIcon from 'assets/icons/collection.svg';
// import TrackIcon from 'assets/icons/note.svg';
// import PlaylistIcon from 'assets/icons/note-list.svg';
// import StreamIcon from 'assets/icons/cloud.svg';
// import LocalIcon from 'assets/icons/internal-drive.svg';
// import SelectableFilter from './components/SelectableFilter';
const Container = styled.View`
padding: 0 32px;
position: relative;
padding: 4px 32px 0 32px;
margin-bottom: 0px;
padding-bottom: 0px;
border-top-width: 1px;
`;
const FullSizeContainer = styled(Container)`
const FullSizeContainer = styled.View`
flex: 1;
`;
const Loading = styled.View`
position: absolute;
right: 32px;
right: 12px;
top: 0;
height: 100%;
flex: 1;
@@ -61,10 +70,18 @@ const SearchResult = styled.View`
height: 54px;
`;
const fuseOptions = {
const SearchIndicator = styled(SearchIcon)`
position: absolute;
left: 16px;
top: 26px;
`;
const fuseOptions: Fuse.IFuseOptions<Album> = {
keys: ['Name', 'AlbumArtist', 'AlbumArtists', 'Artists'],
threshold: 0.1,
includeScore: true,
fieldNormWeight: 1,
};
type AudioResult = {
@@ -86,17 +103,14 @@ type CombinedResults = (AudioResult | AlbumResult)[];
export default function Search() {
const defaultStyles = useDefaultStyles();
// Prepare state
// Prepare state for fuse and albums
const [fuseIsReady, setFuseReady] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [isLoading, setLoading] = useState(false);
const [fuseResults, setFuseResults] = useState<CombinedResults>([]);
const [jellyfinResults, setJellyfinResults] = useState<CombinedResults>([]);
const albums = useTypedSelector(state => state.music.albums.entities);
const fuse = useRef<Fuse<Album>>();
const searchElement = useRef<TextInput>(null);
// Prepare helpers
const navigation = useNavigation<MusicNavigationProp>();
@@ -197,40 +211,62 @@ export default function Search() {
retrieveResults();
}, [searchTerm, setFuseResults, setLoading, fuse, fetchJellyfinResults]);
// Automatically focus on the text input on mount
useEffect(() => {
// Give the timeout a slight delay so the component has a chance to actually
// render the text input field.
setTimeout(() => searchElement.current?.focus(), 10);
}, []);
// Handlers
const selectAlbum = useCallback((id: string) =>
navigation.navigate('Album', { id, album: albums[id] as Album }), [navigation, albums]
);
const HeaderComponent = React.useMemo(() => (
<Container>
<Input
ref={searchElement}
value={searchTerm}
onChangeText={setSearchTerm}
style={defaultStyles.input}
placeholder={t('search') + '...'}
/>
<SearchIcon width={14} height={14} fill={defaultStyles.textHalfOpacity.color} style={{ position: 'absolute', left: 48, top: 26}} />
{isLoading && <Loading><ActivityIndicator /></Loading>}
</Container>
<View>
<Container style={defaultStyles.border}>
<View>
<Input
value={searchTerm}
onChangeText={setSearchTerm}
style={[defaultStyles.input, { marginBottom: 12 }]}
placeholder={t('search') + '...'}
/>
<SearchIndicator width={14} height={14} fill={defaultStyles.textHalfOpacity.color} />
{isLoading && <Loading><ActivityIndicator /></Loading>}
</View>
</Container>
{/* <ScrollView horizontal showsHorizontalScrollIndicator={false}>
<View style={{ paddingHorizontal: 32, paddingBottom: 12, flex: 1, flexDirection: 'row' }}>
<SelectableFilter
text="Artists"
icon={MicrophoneIcon}
active
/>
<SelectableFilter
text="Albums"
icon={AlbumIcon}
active={false}
/>
<SelectableFilter
text="Tracks"
icon={TrackIcon}
active={false}
/>
<SelectableFilter
text="Playlist"
icon={PlaylistIcon}
active={false}
/>
<SelectableFilter
text="Streaming"
icon={StreamIcon}
active={false}
/>
<SelectableFilter
text="Local Playback"
icon={LocalIcon}
active={false}
/>
</View>
</ScrollView> */}
</View>
), [searchTerm, setSearchTerm, defaultStyles, isLoading]);
// const FooterComponent = React.useMemo(() => (
// <FullSizeContainer>
// {(searchTerm.length && !jellyfinResults.length && !fuseResults.length && !isLoading)
// ? <Text style={{ textAlign: 'center', opacity: 0.5 }}>{t('no-results')}</Text>
// : null}
// </FullSizeContainer>
// ), [searchTerm, jellyfinResults, fuseResults, isLoading]);
// GUARD: We cannot search for stuff unless Fuse is loaded with results.
// Therefore we delay rendering to when we are certain it's there.
if (!fuseIsReady) {
@@ -240,7 +276,7 @@ export default function Search() {
return (
<SafeAreaView style={{ flex: 1 }}>
<FlatList
style={{ flex: 1 }}
style={{ flex: 2 }}
data={[...jellyfinResults, ...fuseResults]}
renderItem={({ item: { id, type, album: trackAlbum, name: trackName } }: { item: AlbumResult | AudioResult }) => {
const album = albums[trackAlbum || id];
@@ -254,7 +290,9 @@ export default function Search() {
return (
<TouchableHandler<string> id={album.Id} onPress={selectAlbum}>
<SearchResult>
<AlbumImage source={{ uri: getImage(album.Id) }} />
<ShadowWrapper>
<AlbumImage source={{ uri: getImage(album.Id) }} style={defaultStyles.imageBackground} />
</ShadowWrapper>
<View style={{ flex: 1 }}>
<Text numberOfLines={1}>
{trackName || album.Name}
@@ -277,8 +315,6 @@ export default function Search() {
);
}}
keyExtractor={(item) => item.id}
ListHeaderComponent={HeaderComponent}
// ListFooterComponent={FooterComponent}
extraData={[searchTerm, albums]}
/>
{(searchTerm.length && !jellyfinResults.length && !fuseResults.length && !isLoading) ? (
@@ -286,6 +322,7 @@ export default function Search() {
<Text style={{ textAlign: 'center', opacity: 0.5, fontSize: 18 }}>{t('no-results')}</Text>
</FullSizeContainer>
) : null}
{HeaderComponent}
</SafeAreaView>
);
}