diff --git a/src/assets/icons/collection.svg b/src/assets/icons/collection.svg
new file mode 100644
index 0000000..d017c0c
--- /dev/null
+++ b/src/assets/icons/collection.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/microphone.svg b/src/assets/icons/microphone.svg
new file mode 100644
index 0000000..8446db3
--- /dev/null
+++ b/src/assets/icons/microphone.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/note.svg b/src/assets/icons/note.svg
new file mode 100644
index 0000000..ec9765b
--- /dev/null
+++ b/src/assets/icons/note.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/screens/Search/components/SelectableFilter.tsx b/src/screens/Search/components/SelectableFilter.tsx
new file mode 100644
index 0000000..3a53315
--- /dev/null
+++ b/src/screens/Search/components/SelectableFilter.tsx
@@ -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;
+ text: string;
+ active: boolean;
+ onPress: () => void;
+}
+
+function SelectableFilter({
+ icon: Icon,
+ text,
+ active,
+ onPress,
+}: Props) {
+ const defaultStyles = useDefaultStyles();
+
+ return (
+
+
+
+
+ );
+}
+
+export default SelectableFilter;
\ No newline at end of file
diff --git a/src/screens/Search/index.tsx b/src/screens/Search/index.tsx
index af7dc93..f4e1ad2 100644
--- a/src/screens/Search/index.tsx
+++ b/src/screens/Search/index.tsx
@@ -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 = {
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([]);
const [jellyfinResults, setJellyfinResults] = useState([]);
-
const albums = useTypedSelector(state => state.music.albums.entities);
-
const fuse = useRef>();
- const searchElement = useRef(null);
// Prepare helpers
const navigation = useNavigation();
@@ -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(() => (
-
-
-
- {isLoading && }
-
+
+
+
+
+
+ {isLoading && }
+
+
+ {/*
+
+
+
+
+
+
+
+
+ */}
+
), [searchTerm, setSearchTerm, defaultStyles, isLoading]);
- // const FooterComponent = React.useMemo(() => (
- //
- // {(searchTerm.length && !jellyfinResults.length && !fuseResults.length && !isLoading)
- // ? {t('no-results')}
- // : null}
- //
- // ), [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 (
{
const album = albums[trackAlbum || id];
@@ -254,7 +290,9 @@ export default function Search() {
return (
id={album.Id} onPress={selectAlbum}>
-
+
+
+
{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() {
{t('no-results')}
) : null}
+ {HeaderComponent}
);
}
\ No newline at end of file