mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 07:29:14 +01:00
React Query refactor (#91)
* initial react-query experiments * use queries for item screens send the data we do have over routing to prepopulate (album/playlist) use number for starred because sending Date freaks out react-navigation * add in equiv. song cover art fix * reorg, switch artistview over start mapping song cover art when any are available * refactor useStar to queries fix caching for starred items and album cover art * add hook to reset queries on server change * refactor search to use query * fix song cover art setting * use query for artistInfo * remove last bits of library state * cleanup * use query key factory already fixed one wrong key... * require coverart size * let's try no promise queues on these for now * image cache uses query * perf fix for playlist parsing also use placeholder data so we don't have to deal with staleness * drill that disabled also list controls doesn't need its own songs hook/copy * switch to react-native-blob-util for downloads slightly slower but allows us to use DownloadManager, which backgrounds downloads so they are no longer corrupted when the app suspends * add a fake "top songs" based on artist search then sorted by play count/ratings artistview should load now even if topSongs fails * try not to swap between topSongs/search on refetch set queueContext by song list so the index isn't off if the list changes * add content type validation for file fetching also try to speed up existing file return by limiting fs ops * if the HEAD fails, don't queue the download * clean up params * reimpl clear image cache * precompute contextId prevents wrong "is playing" when any mismatch between queue and list * clear images from all servers use external files dir instead of cache * fix pressable disabled flicker don't retry topsongs on failure try to optimize setqueue and fixcoverart a bit * wait for queries during clear * break out fetchExistingFile from fetchFile allows to tell if file is coming from disk or not only show placeholder/loading spinner if actually fetching image * forgot these wouldn't do anything with objects * remove query cache when switching servers * add content-disposition extension gathering add support for progress hook (needs native support still) * added custom RNBU pkg with progress changes * fully unmount tabs when server changes prevents unwanted requests, gives fresh start on switch fix fixCoverArt not re-rendering in certain cases on search * use serverId from fetch deps * fix lint * update licenses * just use the whole lodash package * make using cache buster optional
This commit is contained in:
+40
-61
@@ -4,14 +4,14 @@ import Header from '@app/components/Header'
|
||||
import ListItem from '@app/components/ListItem'
|
||||
import NothingHere from '@app/components/NothingHere'
|
||||
import TextInput from '@app/components/TextInput'
|
||||
import { useActiveServerRefresh } from '@app/hooks/settings'
|
||||
import { useQuerySearchResults } from '@app/hooks/query'
|
||||
import { useSetQueue } from '@app/hooks/trackplayer'
|
||||
import { Album, Artist, SearchResults, Song } from '@app/models/library'
|
||||
import { useStore, useStoreDeep } from '@app/state/store'
|
||||
import colors from '@app/styles/colors'
|
||||
import font from '@app/styles/font'
|
||||
import { mapById } from '@app/util/state'
|
||||
import { useFocusEffect, useNavigation } from '@react-navigation/native'
|
||||
import debounce from 'lodash.debounce'
|
||||
import equal from 'fast-deep-equal/es6/react'
|
||||
import _ from 'lodash'
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import {
|
||||
ActivityIndicator,
|
||||
@@ -24,44 +24,27 @@ import {
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
|
||||
const SongItem = React.memo<{ item: Song }>(({ item }) => {
|
||||
const setQueue = useStore(store => store.setQueue)
|
||||
const { setQueue, isReady, contextId } = useSetQueue('song', [item])
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
item={item}
|
||||
contextId={item.id}
|
||||
contextId={contextId}
|
||||
queueId={0}
|
||||
showArt={true}
|
||||
showStar={false}
|
||||
onPress={() => setQueue([item], item.title, 'song', item.id, 0)}
|
||||
onPress={() => setQueue({ title: item.title, playTrack: 0 })}
|
||||
disabled={!isReady}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}, equal)
|
||||
|
||||
const ResultsCategory = React.memo<{
|
||||
name: string
|
||||
query: string
|
||||
ids: string[]
|
||||
items: (Artist | Album | Song)[]
|
||||
type: 'artist' | 'album' | 'song'
|
||||
}>(({ name, query, type, ids }) => {
|
||||
const items: (Album | Artist | Song)[] = useStoreDeep(
|
||||
useCallback(
|
||||
store => {
|
||||
switch (type) {
|
||||
case 'album':
|
||||
return mapById(store.library.albums, ids)
|
||||
case 'artist':
|
||||
return mapById(store.library.artists, ids)
|
||||
case 'song':
|
||||
return mapById(store.library.songs, ids)
|
||||
default:
|
||||
return []
|
||||
}
|
||||
},
|
||||
[ids, type],
|
||||
),
|
||||
)
|
||||
|
||||
}>(({ name, query, type, items }) => {
|
||||
const navigation = useNavigation()
|
||||
|
||||
if (items.length === 0) {
|
||||
@@ -88,7 +71,7 @@ const ResultsCategory = React.memo<{
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})
|
||||
}, equal)
|
||||
|
||||
const Results = React.memo<{
|
||||
results: SearchResults
|
||||
@@ -96,17 +79,17 @@ const Results = React.memo<{
|
||||
}>(({ results, query }) => {
|
||||
return (
|
||||
<>
|
||||
<ResultsCategory name="Artists" query={query} type={'artist'} ids={results.artists} />
|
||||
<ResultsCategory name="Albums" query={query} type={'album'} ids={results.albums} />
|
||||
<ResultsCategory name="Songs" query={query} type={'song'} ids={results.songs} />
|
||||
<ResultsCategory name="Artists" query={query} type={'artist'} items={results.artists} />
|
||||
<ResultsCategory name="Albums" query={query} type={'album'} items={results.albums} />
|
||||
<ResultsCategory name="Songs" query={query} type={'song'} items={results.songs} />
|
||||
</>
|
||||
)
|
||||
})
|
||||
}, equal)
|
||||
|
||||
const Search = () => {
|
||||
const fetchSearchResults = useStore(store => store.fetchSearchResults)
|
||||
const [results, setResults] = useState<SearchResults>({ artists: [], albums: [], songs: [] })
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const [query, setQuery] = useState('')
|
||||
const { data, isLoading } = useQuerySearchResults({ query, albumCount: 5, artistCount: 5, songCount: 5 })
|
||||
|
||||
const [text, setText] = useState('')
|
||||
const searchBarRef = useRef<ReactTextInput>(null)
|
||||
const scrollRef = useRef<ScrollView>(null)
|
||||
@@ -116,42 +99,39 @@ const Search = () => {
|
||||
useCallback(() => {
|
||||
const task = InteractionManager.runAfterInteractions(() => {
|
||||
setTimeout(() => {
|
||||
if (text) {
|
||||
return
|
||||
}
|
||||
setText('')
|
||||
setResults({ artists: [], albums: [], songs: [] })
|
||||
setQuery('')
|
||||
searchBarRef.current?.focus()
|
||||
scrollRef.current?.scrollTo({ y: 0, animated: true })
|
||||
}, 50)
|
||||
})
|
||||
return () => task.cancel()
|
||||
}, [searchBarRef, scrollRef]),
|
||||
}, [text]),
|
||||
)
|
||||
|
||||
useActiveServerRefresh(
|
||||
useCallback(() => {
|
||||
setText('')
|
||||
setResults({ artists: [], albums: [], songs: [] })
|
||||
}, []),
|
||||
)
|
||||
|
||||
const debouncedonUpdateSearch = useMemo(
|
||||
const debouncedSetQuery = useMemo(
|
||||
() =>
|
||||
debounce(async (query: string) => {
|
||||
setRefreshing(true)
|
||||
setResults(await fetchSearchResults({ query, albumCount: 5, artistCount: 5, songCount: 5 }))
|
||||
setRefreshing(false)
|
||||
_.debounce((value: string) => {
|
||||
setQuery(value)
|
||||
}, 400),
|
||||
[fetchSearchResults],
|
||||
[],
|
||||
)
|
||||
|
||||
const onChangeText = useCallback(
|
||||
(value: string) => {
|
||||
setText(value)
|
||||
debouncedonUpdateSearch(value)
|
||||
debouncedSetQuery(value)
|
||||
},
|
||||
[setText, debouncedonUpdateSearch],
|
||||
[setText, debouncedSetQuery],
|
||||
)
|
||||
|
||||
const resultsCount = results.albums.length + results.artists.length + results.songs.length
|
||||
const resultsCount =
|
||||
(data ? data.pages.reduce((acc, val) => (acc += val.artists.length), 0) : 0) +
|
||||
(data ? data.pages.reduce((acc, val) => (acc += val.albums.length), 0) : 0) +
|
||||
(data ? data.pages.reduce((acc, val) => (acc += val.songs.length), 0) : 0)
|
||||
|
||||
return (
|
||||
<GradientScrollView ref={scrollRef} style={styles.scroll} contentContainerStyle={{ paddingTop }}>
|
||||
@@ -164,14 +144,13 @@ const Search = () => {
|
||||
value={text}
|
||||
onChangeText={onChangeText}
|
||||
/>
|
||||
<ActivityIndicator
|
||||
animating={refreshing}
|
||||
size="small"
|
||||
color={colors.text.secondary}
|
||||
style={styles.activity}
|
||||
/>
|
||||
<ActivityIndicator animating={isLoading} size="small" color={colors.text.secondary} style={styles.activity} />
|
||||
</View>
|
||||
{resultsCount > 0 ? <Results results={results} query={text} /> : <NothingHere style={styles.noResults} />}
|
||||
{data !== undefined && resultsCount > 0 ? (
|
||||
<Results results={data.pages[0]} query={text} />
|
||||
) : (
|
||||
<NothingHere style={styles.noResults} />
|
||||
)}
|
||||
</View>
|
||||
</GradientScrollView>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user