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

@@ -0,0 +1,3 @@
<svg width="8" height="10" viewBox="0 0 8 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.37245 10H6.29172C6.67206 10 6.96987 9.88698 7.18515 9.66093C7.40044 9.43488 7.50808 9.09581 7.50808 8.64371V3.84285C7.50808 3.39074 7.39416 3.05257 7.16632 2.82831C6.93847 2.60406 6.59491 2.49193 6.13564 2.49193H1.37245C0.916756 2.49193 0.574092 2.60406 0.344454 2.82831C0.114818 3.05257 0 3.39074 0 3.84285V8.64371C0 9.09581 0.114818 9.43488 0.344454 9.66093C0.574092 9.88698 0.916756 10 1.37245 10ZM0.963407 1.83531H6.54467C6.50521 1.61644 6.42806 1.44959 6.31324 1.33477C6.19842 1.21995 6.02081 1.16254 5.78041 1.16254H1.72767C1.48727 1.16254 1.30966 1.21995 1.19484 1.33477C1.08002 1.44959 1.00288 1.61644 0.963407 1.83531ZM1.72228 0.613563H5.7858C5.77144 0.409042 5.70596 0.255651 5.58934 0.153391C5.47273 0.0511304 5.3032 0 5.08074 0H2.42734C2.20488 0 2.03535 0.0511304 1.91874 0.153391C1.80212 0.255651 1.73663 0.409042 1.72228 0.613563Z" />
</svg>

After

Width:  |  Height:  |  Size: 962 B

View File

@@ -0,0 +1,3 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.03813 10C5.17148 10 5.28504 9.95416 5.3788 9.86248C5.47256 9.77081 5.51944 9.65621 5.51944 9.51869V5.90574L6.96337 4.56808C7.54261 4.63475 8.08226 4.42014 8.58233 3.92424L5.85698 1.19265C5.61112 1.43851 5.43506 1.7 5.32879 1.97712C5.22253 2.25424 5.18606 2.5324 5.2194 2.81161L0.662578 7.70597C0.570901 7.81015 0.518811 7.92891 0.506309 8.06226C0.493814 8.19561 0.543822 8.31854 0.656331 8.43105L0.050007 9.23115C0.0166691 9.27699 0 9.33012 0 9.39055C0 9.45097 0.0250033 9.50827 0.0750098 9.56245L0.218772 9.70621C0.268783 9.75205 0.324001 9.77706 0.384425 9.78122C0.444848 9.78539 0.500061 9.76664 0.550065 9.72497L1.35016 9.11864C1.45852 9.23115 1.58041 9.28116 1.71584 9.26866C1.85127 9.25616 1.969 9.20198 2.06901 9.10614L4.55057 6.80585V9.51869C4.55057 9.65621 4.59745 9.77081 4.69122 9.86248C4.78498 9.95416 4.90061 10 5.03813 10ZM1.25641 8.14351L5.52569 3.6242C5.5632 3.68671 5.60591 3.74713 5.65383 3.80547C5.70175 3.86381 5.7528 3.92007 5.80698 3.97425C5.85698 4.02842 5.91115 4.07946 5.9695 4.12739C6.02784 4.17531 6.0841 4.21802 6.13827 4.25552L1.6377 8.52482L1.25641 8.14351ZM6.39455 0.648827L9.11989 3.38042C9.40326 3.10122 9.59495 2.79909 9.69497 2.47405C9.79498 2.14901 9.80227 1.82397 9.71684 1.49893C9.63142 1.17389 9.4491 0.875935 9.1699 0.60507C8.89487 0.33004 8.59691 0.14877 8.27604 0.0612579C7.95516 -0.0262539 7.6322 -0.0200025 7.30716 0.0800122C6.98212 0.180018 6.67792 0.369624 6.39455 0.648827Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,3 @@
<svg width="7" height="10" viewBox="0 0 7 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.11802 2.2789V0.406546C6.11802 0.268775 6.07001 0.161274 5.974 0.084044C5.87798 0.00681376 5.76317 -0.0171898 5.62958 0.0120333L3.04962 0.575614C2.70312 0.650758 2.52987 0.83236 2.52987 1.12042V6.63102C2.55909 6.8648 2.46725 7.00048 2.25434 7.03805L1.47158 7.20087C0.97062 7.30941 0.600115 7.48996 0.360063 7.74253C0.120021 7.9951 0 8.31133 0 8.69123C0 9.0753 0.13359 9.38945 0.400769 9.63367C0.667949 9.87789 1.01027 10 1.42774 10C1.66988 10 1.93393 9.93425 2.2199 9.80275C2.50586 9.67124 2.75113 9.45938 2.95569 9.16715C3.16025 8.87492 3.26253 8.48667 3.26253 8.00241V3.46868C3.26253 3.34344 3.27819 3.26204 3.3095 3.22446C3.3408 3.18689 3.41282 3.15767 3.52554 3.13679L5.83623 2.6233C5.9239 2.60661 5.99278 2.56695 6.04288 2.50433C6.09298 2.44171 6.11802 2.36657 6.11802 2.2789Z" />
</svg>

After

Width:  |  Height:  |  Size: 898 B

View File

@@ -0,0 +1,55 @@
import useDefaultStyles from 'components/Colors';
import { Text } from 'components/Typography';
import { THEME_COLOR } from 'CONSTANTS';
import React from 'react';
import { SvgProps } from 'react-native-svg';
import styled, { css } from 'styled-components/native';
const Container = styled.TouchableOpacity<{ active?: boolean }>`
border-radius: 80px;
padding: 6px 12px;
margin-right: 2px;
display: flex;
flex-direction: row;
align-items: center;
`;
const Label = styled(Text)<{ active?: boolean }>`
margin-left: 6px;
opacity: 0.5;
${(props) => props.active && css`
opacity: 1;
font-weight: 500;
color: ${THEME_COLOR};
`}
`;
interface Props {
icon: React.FC<SvgProps>;
text: string;
active: boolean;
onPress: () => void;
}
function SelectableFilter({
icon: Icon,
text,
active,
onPress,
}: Props) {
const defaultStyles = useDefaultStyles();
return (
<Container
style={[defaultStyles.filter, active ? defaultStyles.activeBackground : undefined]}
active={active}
onPress={onPress}
>
<Icon width={14} height={14} fill={active ? THEME_COLOR : defaultStyles.textHalfOpacity.color} />
<Label active={active}>{text}</Label>
</Container>
);
}
export default SelectableFilter;

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>
);
}