diff --git a/app/models/music.ts b/app/models/music.ts index 1238c7c..10d5b65 100644 --- a/app/models/music.ts +++ b/app/models/music.ts @@ -21,12 +21,6 @@ export interface Album extends AlbumListItem { year?: number } -export interface SearchResults { - artists: Artist[] - albums: AlbumListItem[] - songs: Song[] -} - export interface PlaylistListItem { itemType: 'playlist' id: string diff --git a/app/screens/Search.tsx b/app/screens/Search.tsx index ab07b0e..26ba23b 100644 --- a/app/screens/Search.tsx +++ b/app/screens/Search.tsx @@ -5,9 +5,8 @@ import ListItem from '@app/components/ListItem' import NothingHere from '@app/components/NothingHere' import TextInput from '@app/components/TextInput' import { useActiveServerRefresh } from '@app/hooks/server' -import { ListableItem, SearchResults, Song } from '@app/models/music' -import { selectMusic } from '@app/state/music' -import { useStore } from '@app/state/store' +import { Album, Artist, mapById, SearchResults, Song } from '@app/state/library' +import { useStore, useStoreDeep } from '@app/state/store' import { selectTrackPlayer } from '@app/state/trackplayer' import colors from '@app/styles/colors' import font from '@app/styles/font' @@ -42,8 +41,27 @@ const SongItem = React.memo<{ item: Song }>(({ item }) => { const ResultsCategory = React.memo<{ name: string query: string - items: ListableItem[] -}>(({ name, query, items }) => { + ids: string[] + type: 'artist' | 'album' | 'song' +}>(({ name, query, type, ids }) => { + const items: (Album | Artist | Song)[] = useStoreDeep( + useCallback( + store => { + switch (type) { + case 'album': + return mapById(store.entities.albums, ids) + case 'artist': + return mapById(store.entities.artists, ids) + case 'song': + return mapById(store.entities.songs, ids) + default: + return [] + } + }, + [ids, type], + ), + ) + const navigation = useNavigation() if (items.length === 0) { @@ -54,8 +72,8 @@ const ResultsCategory = React.memo<{ <>
{name}
{items.map(a => - a.itemType === 'song' ? ( - + type === 'song' ? ( + ) : ( ), @@ -78,15 +96,15 @@ const Results = React.memo<{ }>(({ results, query }) => { return ( <> - - - + + + ) }) const Search = () => { - const fetchSearchResults = useStore(selectMusic.fetchSearchResults) + const fetchSearchResults = useStore(store => store.fetchLibrarySearchResults) const [results, setResults] = useState({ artists: [], albums: [], songs: [] }) const [refreshing, setRefreshing] = useState(false) const [text, setText] = useState('') @@ -118,7 +136,7 @@ const Search = () => { () => debounce(async (query: string) => { setRefreshing(true) - setResults(await fetchSearchResults(query)) + setResults(await fetchSearchResults({ query, albumCount: 5, artistCount: 5, songCount: 5 })) setRefreshing(false) }, 400), [fetchSearchResults], diff --git a/app/screens/SearchResultsView.tsx b/app/screens/SearchResultsView.tsx index da53ff4..d93d480 100644 --- a/app/screens/SearchResultsView.tsx +++ b/app/screens/SearchResultsView.tsx @@ -1,15 +1,15 @@ import GradientFlatList from '@app/components/GradientFlatList' import ListItem from '@app/components/ListItem' import { useFetchPaginatedList } from '@app/hooks/list' -import { AlbumListItem, Artist, Song } from '@app/models/music' -import { selectMusic } from '@app/state/music' -import { useStore } from '@app/state/store' +import { Album, Artist, Song, mapById } from '@app/state/library' +import { useStore, useStoreDeep } from '@app/state/store' import { selectTrackPlayer } from '@app/state/trackplayer' +import { Search3Params } from '@app/subsonic/params' import { useNavigation } from '@react-navigation/native' import React, { useCallback, useEffect } from 'react' import { StyleSheet } from 'react-native' -type SearchListItemType = AlbumListItem | Song | Artist +type SearchListItemType = Album | Song | Artist const ResultsListItem: React.FC<{ item: SearchListItemType }> = ({ item }) => { const setQueue = useStore(selectTrackPlayer.setQueue) @@ -40,27 +40,62 @@ const SearchResultsView: React.FC<{ type: 'album' | 'artist' | 'song' }> = ({ query, type }) => { const navigation = useNavigation() - const fetchSearchResults = useStore(selectMusic.fetchSearchResults) - const { list, refreshing, refresh, fetchNextPage } = useFetchPaginatedList( + const fetchSearchResults = useStore(store => store.fetchLibrarySearchResults) + const { list, refreshing, refresh, fetchNextPage } = useFetchPaginatedList( useCallback( - (size, offset) => - fetchSearchResults(query, type, size, offset).then(results => { - switch (type) { - case 'album': - return results.albums - case 'artist': - return results.artists - case 'song': - return results.songs - default: - return [] - } - }), + async (size, offset) => { + const params: Search3Params = { query } + if (type === 'album') { + params.albumCount = size + params.albumOffset = offset + } else if (type === 'artist') { + params.artistCount = size + params.artistOffset = offset + } else if (type === 'song') { + params.songCount = size + params.songOffset = offset + } else { + params.albumCount = 5 + params.artistCount = 5 + params.songCount = 5 + } + + const results = await fetchSearchResults(params) + + switch (type) { + case 'album': + return results.albums + case 'artist': + return results.artists + case 'song': + return results.songs + default: + return [] + } + }, [fetchSearchResults, query, type], ), 100, ) + const items: SearchListItemType[] = useStoreDeep( + useCallback( + store => { + switch (type) { + case 'album': + return mapById(store.entities.albums, list) + case 'artist': + return mapById(store.entities.artists, list) + case 'song': + return mapById(store.entities.songs, list) + default: + return [] + } + }, + [list, type], + ), + ) + useEffect(() => { navigation.setOptions({ title: `Search: "${query}"`, @@ -70,7 +105,7 @@ const SearchResultsView: React.FC<{ return ( i.toString()} onRefresh={refresh} diff --git a/app/state/music.ts b/app/state/music.ts index ad88302..55fbf4c 100644 --- a/app/state/music.ts +++ b/app/state/music.ts @@ -1,23 +1,10 @@ -import { AlbumListItem, Artist, PlaylistListItem, SearchResults, StarrableItemType } from '@app/models/music' +import { StarrableItemType } from '@app/models/music' import { Store } from '@app/state/store' -import { GetAlbumList2Params, Search3Params, StarParams } from '@app/subsonic/params' +import { StarParams } from '@app/subsonic/params' import produce from 'immer' import { GetState, SetState } from 'zustand' export type MusicSlice = { - // - // lists-style state - // - fetchArtists: (size?: number, offset?: number) => Promise - fetchPlaylists: () => Promise - fetchAlbums: () => Promise - fetchSearchResults: ( - query: string, - type?: 'album' | 'song' | 'artist', - size?: number, - offset?: number, - ) => Promise - // // actions, etc. // @@ -33,11 +20,6 @@ export type MusicSlice = { } export const selectMusic = { - fetchArtists: (store: MusicSlice) => store.fetchArtists, - fetchPlaylists: (store: MusicSlice) => store.fetchPlaylists, - fetchAlbums: (store: MusicSlice) => store.fetchAlbums, - fetchSearchResults: (store: MusicSlice) => store.fetchSearchResults, - starItem: (store: MusicSlice) => store.starItem, } @@ -55,141 +37,6 @@ function reduceStarred( } export const createMusicSlice = (set: SetState, get: GetState): MusicSlice => ({ - fetchArtists: async () => { - const client = get().client - if (!client) { - return [] - } - - try { - const response = await client.getArtists() - const artists = response.data.artists.map(get().mapArtistID3toArtist) - - set( - produce(state => { - state.starredArtists = reduceStarred(state.starredArtists, artists) - }), - ) - - return artists - } catch { - return [] - } - }, - - fetchPlaylists: async () => { - const client = get().client - if (!client) { - return [] - } - - try { - const response = await client.getPlaylists() - return response.data.playlists.map(get().mapPlaylistListItem) - } catch { - return [] - } - }, - - fetchAlbums: async (size = 500, offset = 0) => { - const client = get().client - if (!client) { - return [] - } - - try { - const filter = get().settings.screens.library.albums - - let params: GetAlbumList2Params - switch (filter.type) { - case 'byYear': - params = { - size, - offset, - type: filter.type, - fromYear: filter.fromYear, - toYear: filter.toYear, - } - break - case 'byGenre': - params = { - size, - offset, - type: filter.type, - genre: filter.genre, - } - break - default: - params = { - size, - offset, - type: filter.type, - } - break - } - - const response = await client.getAlbumList2(params) - const albums = response.data.albums.map(get().mapAlbumID3toAlbumListItem) - - set( - produce(state => { - state.starredAlbums = reduceStarred(state.starredAlbums, albums) - }), - ) - - return albums - } catch { - return [] - } - }, - - fetchSearchResults: async (query, type, size, offset) => { - if (query.length < 2) { - return { artists: [], albums: [], songs: [] } - } - - const client = get().client - if (!client) { - return { artists: [], albums: [], songs: [] } - } - - try { - const params: Search3Params = { query } - if (type === 'album') { - params.albumCount = size - params.albumOffset = offset - } else if (type === 'artist') { - params.artistCount = size - params.artistOffset = offset - } else if (type === 'song') { - params.songCount = size - params.songOffset = offset - } else { - params.albumCount = 5 - params.artistCount = 5 - params.songCount = 5 - } - - const response = await client.search3(params) - - const artists = response.data.artists.map(get().mapArtistID3toArtist) - const albums = response.data.albums.map(get().mapAlbumID3toAlbumListItem) - const songs = await get().mapChildrenToSongs(response.data.songs) - - set( - produce(state => { - state.starredSongs = reduceStarred(state.starredSongs, songs) - state.starredArtists = reduceStarred(state.starredArtists, artists) - state.starredAlbums = reduceStarred(state.starredAlbums, albums) - }), - ) - - return { artists, albums, songs } - } catch { - return { artists: [], albums: [], songs: [] } - } - }, - starredSongs: {}, starredAlbums: {}, starredArtists: {}, diff --git a/index.js b/index.js index e54027b..cf705bd 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,12 @@ enableScreens() import { LogBox } from 'react-native' LogBox.ignoreLogs(["The action 'POP_TO_TOP'"]) +LogBox.ignoreLogs([ + '`new NativeEventEmitter()` was called with a non-null argument without the required `addListener` method.', +]) +LogBox.ignoreLogs([ + '`new NativeEventEmitter()` was called with a non-null argument without the required `removeListeners` method.', +]) import { AppRegistry } from 'react-native' import App from '@app/App'