(1) Automatically focus on text field in search

(2) Restyle no results message
This commit is contained in:
Lei Nelissen
2022-01-01 14:27:08 +01:00
parent 9b41a0e62f
commit 4460bdf7f9

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef, useCallback } from 'react'; import React, { useState, useEffect, useRef, useCallback, Ref } from 'react';
import Input from 'components/Input'; import Input from 'components/Input';
import { ActivityIndicator, Text, View } from 'react-native'; import { ActivityIndicator, Text, TextInput, View } from 'react-native';
import styled from 'styled-components/native'; import styled from 'styled-components/native';
import { useAppDispatch, useTypedSelector } from 'store'; import { useAppDispatch, useTypedSelector } from 'store';
import Fuse from 'fuse.js'; import Fuse from 'fuse.js';
@@ -21,6 +21,10 @@ const Container = styled.View`
position: relative; position: relative;
`; `;
const FullSizeContainer = styled(Container)`
flex: 1;
`;
const Loading = styled.View` const Loading = styled.View`
position: absolute; position: absolute;
right: 32px; right: 32px;
@@ -80,12 +84,14 @@ export default function Search() {
// Prepare state // Prepare state
const [fuseIsReady, setFuseReady] = useState(false); const [fuseIsReady, setFuseReady] = useState(false);
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const albums = useTypedSelector(state => state.music.albums.entities); const [isLoading, setLoading] = useState(false);
const [fuseResults, setFuseResults] = useState<CombinedResults>([]); const [fuseResults, setFuseResults] = useState<CombinedResults>([]);
const [jellyfinResults, setJellyfinResults] = useState<CombinedResults>([]); const [jellyfinResults, setJellyfinResults] = useState<CombinedResults>([]);
const [isLoading, setLoading] = useState(false); const albums = useTypedSelector(state => state.music.albums.entities);
const fuse = useRef<Fuse<Album>>(); const fuse = useRef<Fuse<Album>>();
const searchElement = useRef<TextInput>(null);
// Prepare helpers // Prepare helpers
const navigation = useNavigation<NavigationProp>(); const navigation = useNavigation<NavigationProp>();
@@ -185,6 +191,13 @@ export default function Search() {
retrieveResults(); retrieveResults();
}, [searchTerm, setFuseResults, setLoading, fuse, fetchJellyfinResults]); }, [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 // Handlers
const selectAlbum = useCallback((id: string) => const selectAlbum = useCallback((id: string) =>
navigation.navigate('Album', { id, album: albums[id] as Album }), [navigation, albums] navigation.navigate('Album', { id, album: albums[id] as Album }), [navigation, albums]
@@ -192,18 +205,25 @@ export default function Search() {
const HeaderComponent = React.useMemo(() => ( const HeaderComponent = React.useMemo(() => (
<Container> <Container>
<Input value={searchTerm} onChangeText={setSearchTerm} style={defaultStyles.input} placeholder={t('search') + '...'} /> <Input
// @ts-expect-error Ref typing shenanigans
ref={searchElement}
value={searchTerm}
onChangeText={setSearchTerm}
style={defaultStyles.input}
placeholder={t('search') + '...'}
/>
{isLoading && <Loading><ActivityIndicator /></Loading>} {isLoading && <Loading><ActivityIndicator /></Loading>}
</Container> </Container>
), [searchTerm, setSearchTerm, defaultStyles, isLoading]); ), [searchTerm, setSearchTerm, defaultStyles, isLoading]);
const FooterComponent = React.useMemo(() => ( // const FooterComponent = React.useMemo(() => (
<Container> // <FullSizeContainer>
{(searchTerm.length && !jellyfinResults.length && !fuseResults.length && !isLoading) // {(searchTerm.length && !jellyfinResults.length && !fuseResults.length && !isLoading)
? <Text style={{ textAlign: 'center' }}>{t('no-results')}</Text> // ? <Text style={{ textAlign: 'center', opacity: 0.5 }}>{t('no-results')}</Text>
: null} // : null}
</Container> // </FullSizeContainer>
), [searchTerm, jellyfinResults, fuseResults, isLoading]); // ), [searchTerm, jellyfinResults, fuseResults, isLoading]);
// GUARD: We cannot search for stuff unless Fuse is loaded with results. // GUARD: We cannot search for stuff unless Fuse is loaded with results.
// Therefore we delay rendering to when we are certain it's there. // Therefore we delay rendering to when we are certain it's there.
@@ -243,9 +263,14 @@ export default function Search() {
}} }}
keyExtractor={(item) => item.id} keyExtractor={(item) => item.id}
ListHeaderComponent={HeaderComponent} ListHeaderComponent={HeaderComponent}
ListFooterComponent={FooterComponent} // ListFooterComponent={FooterComponent}
extraData={[searchTerm, albums]} extraData={[searchTerm, albums]}
/> />
<FullSizeContainer>
{(searchTerm.length && !jellyfinResults.length && !fuseResults.length && !isLoading)
? <Text style={{ textAlign: 'center', opacity: 0.5, fontSize: 18 }}>{t('no-results')}</Text>
: null}
</FullSizeContainer>
</> </>
); );
} }