diff --git a/src/screens/Music/index.tsx b/src/screens/Music/index.tsx index 4f4b85e..551f2e3 100644 --- a/src/screens/Music/index.tsx +++ b/src/screens/Music/index.tsx @@ -1,18 +1,19 @@ import React from 'react'; import { createStackNavigator } from '@react-navigation/stack'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; -import { MusicStackParams } from './types'; -import Albums from './stacks/Albums'; -import Album from './stacks/Album'; -import RecentAlbums from './stacks/RecentAlbums'; import { THEME_COLOR } from 'CONSTANTS'; import { t } from '@localisation'; import useDefaultStyles from 'components/Colors'; -import Playlists from './stacks/Playlists'; -import Playlist from './stacks/Playlist'; +import { StackParams } from 'screens/types'; import NowPlaying from './overlays/NowPlaying'; -const Stack = createStackNavigator(); +import RecentAlbums from './stacks/RecentAlbums'; +import Albums from './stacks/Albums'; +import Album from './stacks/Album'; +import Playlists from './stacks/Playlists'; +import Playlist from './stacks/Playlist'; + +const Stack = createStackNavigator(); function MusicStack() { const defaultStyles = useDefaultStyles(); diff --git a/src/screens/Music/overlays/NowPlaying/index.tsx b/src/screens/Music/overlays/NowPlaying/index.tsx index 9d2fe90..aba67f3 100644 --- a/src/screens/Music/overlays/NowPlaying/index.tsx +++ b/src/screens/Music/overlays/NowPlaying/index.tsx @@ -15,7 +15,7 @@ import useDefaultStyles, { ColoredBlurView } from 'components/Colors'; import { useNavigation } from '@react-navigation/native'; import { calculateProgressTranslation } from 'components/Progresstrack'; import { THEME_COLOR } from 'CONSTANTS'; -import { MusicNavigationProp } from 'screens/Music/types'; +import { NavigationProp } from 'screens/types'; import { ShadowWrapper } from 'components/Shadow'; import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; @@ -115,7 +115,7 @@ function NowPlaying() { const previousBuffered = usePrevious(buffered); const previousPosition = usePrevious(position); - const navigation = useNavigation(); + const navigation = useNavigation(); const bufferAnimation = useRef(new Animated.Value(0)); const progressAnimation = useRef(new Animated.Value(0)); diff --git a/src/screens/Music/stacks/Albums.tsx b/src/screens/Music/stacks/Albums.tsx index c5bdb68..64cac99 100644 --- a/src/screens/Music/stacks/Albums.tsx +++ b/src/screens/Music/stacks/Albums.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useEffect, useRef, ReactText, useMemo } from 'react'; import { useGetImage } from 'utility/JellyfinApi'; -import { MusicNavigationProp } from '../types'; import { SafeAreaView, SectionList, View } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { differenceInDays } from 'date-fns'; @@ -17,6 +16,7 @@ import useDefaultStyles, { ColoredBlurView } from 'components/Colors'; import { Album } from 'store/music/types'; import { Text } from 'components/Typography'; import { ShadowWrapper } from 'components/Shadow'; +import { NavigationProp } from 'screens/types'; const HeadingHeight = 50; @@ -87,7 +87,7 @@ const Albums: React.FC = () => { // Initialise helpers const dispatch = useAppDispatch(); - const navigation = useNavigation(); + const navigation = useNavigation(); const getImage = useGetImage(); const listRef = useRef>(null); diff --git a/src/screens/Music/stacks/Playlists.tsx b/src/screens/Music/stacks/Playlists.tsx index 1c3df4b..14ec249 100644 --- a/src/screens/Music/stacks/Playlists.tsx +++ b/src/screens/Music/stacks/Playlists.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useEffect, useRef, ReactText } from 'react'; import { useGetImage } from 'utility/JellyfinApi'; -import { MusicNavigationProp } from '../types'; import { Text, View, FlatList, ListRenderItem } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { differenceInDays } from 'date-fns'; @@ -11,6 +10,7 @@ import TouchableHandler from 'components/TouchableHandler'; import AlbumImage, { AlbumItem } from './components/AlbumImage'; import { EntityId } from '@reduxjs/toolkit'; import useDefaultStyles from 'components/Colors'; +import { NavigationProp } from 'screens/types'; interface GeneratedAlbumItemProps { id: ReactText; @@ -41,7 +41,7 @@ const Playlists: React.FC = () => { // Initialise helpers const dispatch = useAppDispatch(); - const navigation = useNavigation(); + const navigation = useNavigation(); const getImage = useGetImage(); const listRef = useRef>(null); diff --git a/src/screens/Music/stacks/RecentAlbums.tsx b/src/screens/Music/stacks/RecentAlbums.tsx index a960904..d25580b 100644 --- a/src/screens/Music/stacks/RecentAlbums.tsx +++ b/src/screens/Music/stacks/RecentAlbums.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useEffect } from 'react'; import { useGetImage } from 'utility/JellyfinApi'; -import { MusicNavigationProp } from '../types'; import { Text, SafeAreaView, FlatList, StyleSheet } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { useAppDispatch, useTypedSelector } from 'store'; @@ -17,6 +16,7 @@ import { Album } from 'store/music/types'; import Divider from 'components/Divider'; import styled from 'styled-components/native'; import { ShadowWrapper } from 'components/Shadow'; +import { NavigationProp } from 'screens/types'; const styles = StyleSheet.create({ columnWrapper: { @@ -31,7 +31,7 @@ const HeaderContainer = styled.View` `; const NavigationHeader: React.FC = () => { - const navigation = useNavigation(); + const navigation = useNavigation(); const handleAllAlbumsClick = useCallback(() => { navigation.navigate('Albums'); }, [navigation]); const handlePlaylistsClick = useCallback(() => { navigation.navigate('Playlists'); }, [navigation]); @@ -59,7 +59,7 @@ const RecentAlbums: React.FC = () => { // Initialise helpers const dispatch = useAppDispatch(); - const navigation = useNavigation(); + const navigation = useNavigation(); const getImage = useGetImage(); // Set callbacks diff --git a/src/screens/Music/stacks/components/TrackListView.tsx b/src/screens/Music/stacks/components/TrackListView.tsx index 424e362..6f88f79 100644 --- a/src/screens/Music/stacks/components/TrackListView.tsx +++ b/src/screens/Music/stacks/components/TrackListView.tsx @@ -14,7 +14,7 @@ import useDefaultStyles from 'components/Colors'; import usePlayTracks from 'utility/usePlayTracks'; import { EntityId } from '@reduxjs/toolkit'; import { WrappableButtonRow, WrappableButton } from 'components/WrappableButtonRow'; -import { MusicNavigationProp } from 'screens/Music/types'; +import { NavigationProp } from 'screens/types'; import DownloadIcon from 'components/DownloadIcon'; import CloudDownArrow from 'assets/icons/cloud-down-arrow.svg'; import Trash from 'assets/icons/trash.svg'; @@ -89,7 +89,7 @@ const TrackListView: React.FC = ({ const getImage = useGetImage(); const playTracks = usePlayTracks(); const { track: currentTrack } = useCurrentTrack(); - const navigation = useNavigation(); + const navigation = useNavigation(); const dispatch = useAppDispatch(); // Setup callbacks @@ -101,7 +101,7 @@ const TrackListView: React.FC = ({ await TrackPlayer.play(); }, [playTracks, trackIds]); const longPressTrack = useCallback((index: number) => { - navigation.navigate('TrackPopupMenu', { trackId: trackIds[index] }); + navigation.navigate('TrackPopupMenu', { trackId: trackIds[index].toString() }); }, [navigation, trackIds]); const downloadAllTracks = useCallback(() => { trackIds.forEach((trackId) => dispatch(queueTrackForDownload(trackId))); diff --git a/src/screens/Search/index.tsx b/src/screens/Search/index.tsx index 8cf89b0..a9bfbf3 100644 --- a/src/screens/Search/index.tsx +++ b/src/screens/Search/index.tsx @@ -1,331 +1,27 @@ -import React, { useState, useEffect, useRef, useCallback } from 'react'; -import Input from 'components/Input'; -import { ActivityIndicator, Animated, SafeAreaView, View } from 'react-native'; -import styled from 'styled-components/native'; -import { useAppDispatch, useTypedSelector } from 'store'; -import Fuse from 'fuse.js'; -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'; -import { useGetImage } from 'utility/JellyfinApi'; -import FastImage from 'react-native-fast-image'; +import React from 'react'; +import { createStackNavigator } from '@react-navigation/stack'; +import { THEME_COLOR } from 'CONSTANTS'; import { t } from '@localisation'; -import useDefaultStyles, { ColoredBlurView } from 'components/Colors'; -import { searchAndFetchAlbums } from 'store/music/actions'; -import { debounce } from 'lodash'; -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 { useKeyboardHeight } from 'utility/useKeyboardHeight'; -import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; -// 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'; +import useDefaultStyles from 'components/Colors'; +import { StackParams } from 'screens/types'; +import Search from './stacks/Search'; +import Album from 'screens/Music/stacks/Album'; -const Container = styled(View)` - padding: 4px 24px 0 24px; - margin-bottom: 0px; - padding-bottom: 0px; - border-top-width: 0.5px; -`; +const Stack = createStackNavigator(); -const FullSizeContainer = styled.View` - flex: 1; -`; - -const Loading = styled.View` - position: absolute; - right: 12px; - top: 0; - height: 100%; - flex: 1; - justify-content: center; -`; - -const AlbumImage = styled(FastImage)` - border-radius: 4px; - width: 32px; - height: 32px; - margin-right: 10px; -`; - -const HalfOpacity = styled.Text` - opacity: 0.5; - margin-top: 2px; - font-size: 12px; - flex: 1 1 auto; -`; - -const SearchResult = styled.View` - flex-direction: row; - align-items: center; - padding: 8px 32px; - height: 54px; -`; - -const fuseOptions: Fuse.IFuseOptions = { - keys: ['Name', 'AlbumArtist', 'AlbumArtists', 'Artists'], - threshold: 0.1, - includeScore: true, - fieldNormWeight: 1, -}; - -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() { +function SearchStack() { const defaultStyles = useDefaultStyles(); - const tabBarHeight = useBottomTabBarHeight(); - - // 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>(); - - // Prepare helpers - const navigation = useNavigation(); - const keyboardHeight = useKeyboardHeight(); - const getImage = useGetImage(); - const dispatch = useAppDispatch(); - - /** - * Since it is impractical to have a global fuse variable, we need to - * instantiate it for thsi function. With this effect, we generate a new - * Fuse instance every time the albums change. This can of course be done - * more intelligently by removing and adding the changed albums, but this is - * an open todo. - */ - useEffect(() => { - fuse.current = new Fuse(Object.values(albums) as Album[], fuseOptions); - setFuseReady(true); - }, [albums, setFuseReady]); - - /** - * This function retrieves search results from Jellyfin. It is a seperate - * callback, so that we can make sure it is properly debounced and doesn't - * cause execessive jank in the interface. - */ - // eslint-disable-next-line react-hooks/exhaustive-deps - const fetchJellyfinResults = useCallback(debounce(async (searchTerm: string, currentResults: CombinedResults) => { - // First, query the Jellyfin API - const { payload } = await dispatch(searchAndFetchAlbums({ term: searchTerm })); - - // Convert the current results to album ids - const albumIds = currentResults.map(item => item.id); - - // Parse the result in correct typescript form - const results = (payload as { results: (Album | AlbumTrack)[] }).results; - - // Filter any results that are already displayed - const items = results.filter(item => ( - !(item.Type === 'MusicAlbum' && albumIds.includes(item.Id)) - // Then convert the results to proper result form - )).map((item) => ({ - type: item.Type, - id: item.Id, - album: item.Type === 'Audio' - ? item.AlbumId - : undefined, - name: item.Type === 'Audio' - ? item.Name - : undefined, - })); - - // Lastly, we'll merge the two and assign them to the state - setJellyfinResults([...items] as CombinedResults); - - // Loading is now complete - setLoading(false); - }, 50), [dispatch, setJellyfinResults]); - - /** - * Whenever the search term changes, we gather results from Fuse and assign - * them to state - */ - useEffect(() => { - if (!searchTerm) { - 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; - } - - // 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, - })); - - // Assign the preliminary results - setFuseResults(albums); - setLoading(true); - try { - // Wrap the call in a try/catch block so that we catch any - // network issues in search and just use local search if the - // network is unavailable - fetchJellyfinResults(searchTerm, albums); - } catch { - // Reset the loading indicator if the network fails - setLoading(false); - } - }; - - retrieveResults(); - }, [searchTerm, setFuseResults, setLoading, fuse, fetchJellyfinResults]); - - // Handlers - const selectAlbum = useCallback((id: string) => - navigation.navigate('Album', { id, album: albums[id] as Album }), [navigation, albums] - ); - - const HeaderComponent = React.useMemo(() => ( - - - - - } - testID="search-input" - /> - {isLoading && } - - - {/* - - - - - - - - - */} - - - ), [searchTerm, setSearchTerm, defaultStyles, isLoading, keyboardHeight, tabBarHeight]); - - // 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) { - return null; - } return ( - - { - const album = albums[trackAlbum || id]; - - // GUARD: If the album cannot be found in the store, we - // cannot display it. - if (!album) { - return null; - } - - return ( - id={album.Id} onPress={selectAlbum} testID={`search-result-${album.Id}`}> - - - - - - - {trackName || album.Name} - - {(album.AlbumArtist || album.Name) && ( - - {type === 'AlbumArtist' - ? `${t('album')} • ${album.AlbumArtist}` - : `${t('track')} • ${album.AlbumArtist} — ${album.Name}` - } - - )} - - - - - - - - - - ); - }} - keyExtractor={(item) => item.id} - extraData={[searchTerm, albums]} - /> - {(searchTerm.length && !jellyfinResults.length && !fuseResults.length && !isLoading) ? ( - - {t('no-results')} - - ) : null} - {HeaderComponent} - + + + + ); -} \ No newline at end of file +} + +export default SearchStack; \ No newline at end of file diff --git a/src/screens/Search/components/SelectableFilter.tsx b/src/screens/Search/stacks/Search/components/SelectableFilter.tsx similarity index 100% rename from src/screens/Search/components/SelectableFilter.tsx rename to src/screens/Search/stacks/Search/components/SelectableFilter.tsx diff --git a/src/screens/Search/stacks/Search/index.tsx b/src/screens/Search/stacks/Search/index.tsx new file mode 100644 index 0000000..2723d0a --- /dev/null +++ b/src/screens/Search/stacks/Search/index.tsx @@ -0,0 +1,331 @@ +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import Input from 'components/Input'; +import { ActivityIndicator, Animated, SafeAreaView, View } from 'react-native'; +import styled from 'styled-components/native'; +import { useAppDispatch, useTypedSelector } from 'store'; +import Fuse from 'fuse.js'; +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'; +import { useGetImage } from 'utility/JellyfinApi'; +import FastImage from 'react-native-fast-image'; +import { t } from '@localisation'; +import useDefaultStyles, { ColoredBlurView } from 'components/Colors'; +import { searchAndFetchAlbums } from 'store/music/actions'; +import { debounce } from 'lodash'; +import { Text } from 'components/Typography'; +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 { useKeyboardHeight } from 'utility/useKeyboardHeight'; +import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; +import { NavigationProp } from 'screens/types'; +// 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: 4px 24px 0 24px; + margin-bottom: 0px; + padding-bottom: 0px; + border-top-width: 0.5px; +`; + +const FullSizeContainer = styled.View` + flex: 1; +`; + +const Loading = styled.View` + position: absolute; + right: 12px; + top: 0; + height: 100%; + flex: 1; + justify-content: center; +`; + +const AlbumImage = styled(FastImage)` + border-radius: 4px; + width: 32px; + height: 32px; + margin-right: 10px; +`; + +const HalfOpacity = styled.Text` + opacity: 0.5; + margin-top: 2px; + font-size: 12px; + flex: 1 1 auto; +`; + +const SearchResult = styled.View` + flex-direction: row; + align-items: center; + padding: 8px 32px; + height: 54px; +`; + +const fuseOptions: Fuse.IFuseOptions = { + keys: ['Name', 'AlbumArtist', 'AlbumArtists', 'Artists'], + threshold: 0.1, + includeScore: true, + fieldNormWeight: 1, +}; + +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(); + const tabBarHeight = useBottomTabBarHeight(); + + // 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>(); + + // Prepare helpers + const navigation = useNavigation(); + const keyboardHeight = useKeyboardHeight(); + const getImage = useGetImage(); + const dispatch = useAppDispatch(); + + /** + * Since it is impractical to have a global fuse variable, we need to + * instantiate it for thsi function. With this effect, we generate a new + * Fuse instance every time the albums change. This can of course be done + * more intelligently by removing and adding the changed albums, but this is + * an open todo. + */ + useEffect(() => { + fuse.current = new Fuse(Object.values(albums) as Album[], fuseOptions); + setFuseReady(true); + }, [albums, setFuseReady]); + + /** + * This function retrieves search results from Jellyfin. It is a seperate + * callback, so that we can make sure it is properly debounced and doesn't + * cause execessive jank in the interface. + */ + // eslint-disable-next-line react-hooks/exhaustive-deps + const fetchJellyfinResults = useCallback(debounce(async (searchTerm: string, currentResults: CombinedResults) => { + // First, query the Jellyfin API + const { payload } = await dispatch(searchAndFetchAlbums({ term: searchTerm })); + + // Convert the current results to album ids + const albumIds = currentResults.map(item => item.id); + + // Parse the result in correct typescript form + const results = (payload as { results: (Album | AlbumTrack)[] }).results; + + // Filter any results that are already displayed + const items = results.filter(item => ( + !(item.Type === 'MusicAlbum' && albumIds.includes(item.Id)) + // Then convert the results to proper result form + )).map((item) => ({ + type: item.Type, + id: item.Id, + album: item.Type === 'Audio' + ? item.AlbumId + : undefined, + name: item.Type === 'Audio' + ? item.Name + : undefined, + })); + + // Lastly, we'll merge the two and assign them to the state + setJellyfinResults([...items] as CombinedResults); + + // Loading is now complete + setLoading(false); + }, 50), [dispatch, setJellyfinResults]); + + /** + * Whenever the search term changes, we gather results from Fuse and assign + * them to state + */ + useEffect(() => { + if (!searchTerm) { + 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; + } + + // 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, + })); + + // Assign the preliminary results + setFuseResults(albums); + setLoading(true); + try { + // Wrap the call in a try/catch block so that we catch any + // network issues in search and just use local search if the + // network is unavailable + fetchJellyfinResults(searchTerm, albums); + } catch { + // Reset the loading indicator if the network fails + setLoading(false); + } + }; + + retrieveResults(); + }, [searchTerm, setFuseResults, setLoading, fuse, fetchJellyfinResults]); + + // Handlers + const selectAlbum = useCallback((id: string) => { + navigation.navigate('Album', { id, album: albums[id] as Album }); + }, [navigation, albums]); + + const HeaderComponent = React.useMemo(() => ( + + + + + } + testID="search-input" + /> + {isLoading && } + + + {/* + + + + + + + + + */} + + + ), [searchTerm, setSearchTerm, defaultStyles, isLoading, keyboardHeight, tabBarHeight]); + + // 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) { + return null; + } + + return ( + + { + const album = albums[trackAlbum || id]; + + // GUARD: If the album cannot be found in the store, we + // cannot display it. + if (!album) { + return null; + } + + return ( + id={album.Id} onPress={selectAlbum} testID={`search-result-${album.Id}`}> + + + + + + + {trackName || album.Name} + + {(album.AlbumArtist || album.Name) && ( + + {type === 'AlbumArtist' + ? `${t('album')} • ${album.AlbumArtist}` + : `${t('track')} • ${album.AlbumArtist} — ${album.Name}` + } + + )} + + + + + + + + + + ); + }} + keyExtractor={(item) => item.id} + extraData={[searchTerm, albums]} + /> + {(searchTerm.length && !jellyfinResults.length && !fuseResults.length && !isLoading) ? ( + + {t('no-results')} + + ) : null} + {HeaderComponent} + + ); +} \ No newline at end of file diff --git a/src/screens/Music/types.ts b/src/screens/Search/types.ts similarity index 100% rename from src/screens/Music/types.ts rename to src/screens/Search/types.ts diff --git a/src/screens/index.tsx b/src/screens/index.tsx index 04fa446..017c46e 100644 --- a/src/screens/index.tsx +++ b/src/screens/index.tsx @@ -5,7 +5,7 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { CompositeNavigationProp } from '@react-navigation/native'; import { THEME_COLOR } from 'CONSTANTS'; -import Search from './Search'; +import SearchStack from './Search'; import Music from './Music'; import Settings from './Settings'; import Downloads from './Downloads'; @@ -18,15 +18,15 @@ import NotesIcon from 'assets/icons/notes.svg'; import GearIcon from 'assets/icons/gear.svg'; import DownloadsIcon from 'assets/icons/arrow-down-to-line.svg'; import { useTypedSelector } from 'store'; -import { ModalStackParams } from './types'; import { t } from '@localisation'; import ErrorReportingAlert from 'utility/ErrorReportingAlert'; import ErrorReportingPopup from './modals/ErrorReportingPopup'; import Player from './modals/Player'; import { StyleSheet } from 'react-native'; import { ColoredBlurView } from 'components/Colors'; +import { StackParams } from './types'; -const Stack = createNativeStackNavigator(); +const Stack = createNativeStackNavigator(); const Tab = createBottomTabNavigator(); type Screens = { @@ -50,9 +50,9 @@ function Screens() { screenOptions={({ route }) => ({ tabBarIcon: function TabBarIcon({ color, size }) { switch (route.name) { - case 'Search': + case 'SearchTab': return ; - case 'Music': + case 'MusicTab': return ; case 'Settings': return ; @@ -72,8 +72,8 @@ function Screens() { ) })} > - - + + diff --git a/src/screens/types.ts b/src/screens/types.ts index 32d83f1..3991a2b 100644 --- a/src/screens/types.ts +++ b/src/screens/types.ts @@ -1,9 +1,16 @@ import { StackNavigationProp } from '@react-navigation/stack'; +import { Album } from 'store/music/types'; -export interface ModalStackParams { +export type StackParams = { [key: string]: Record | undefined; + Albums: undefined; + Album: { id: string, album: Album }; + Playlists: undefined; + Playlist: { id: string }; + RecentAlbums: undefined; + Search: undefined; SetJellyfinServer: undefined; TrackPopupMenu: { trackId: string }; -} +}; -export type ModalNavigationProp = StackNavigationProp; +export type NavigationProp = StackNavigationProp;