mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 00:59:28 +01:00
* start of music store refactor moving stuff into a state cache better separate it from view logic * added paginated list/album list * reworked fetchAlbumList to remove ui state refactored home screen to use new method i broke playing songs somehow, JS thread goes into a loop * don't reset parts manually, do it all at once * fixed perf issue related to too many rerenders rerenders were caused by strict equality check on object/array picks switched artistInfo to new store updated zustand and fixed deprecation warnings * update typescript and use workspace tsc version for vscode * remove old artistInfo * switched to new playlist w/songs removed more unused stuff * remove unused + (slightly) rework search * refactor star * use only original/large imges for covers/artist fix view artist from context menu add loading indicators to song list and artist views (show info we have right away) * set starred/unstar assuming it works and correct state on error * reorg, remove old music slice files * added back fix for song cover art * sort artists by localCompare name * update licenses * fix now playing background grey bar * update react-native-gesture-handler for node-fetch security alert * fix another gradient height grey bar issue * update licenses again * remove thumbnail cache * rename to remove "Library" from methods * Revert "remove thumbnail cache" This reverts commit e0db4931f11bbf4cd8e73102d06505c6ae85f4a6. * use ids for lists, pull state later * Revert "use only original/large imges for covers/artist" This reverts commit c9aea9065ce6ebe3c8b09c10dd74d4de153d76fd. * deep equal ListItem props for now this needs a bigger refactor * use immer as middleware * refactor api client to use string method hoping to use this for requestKey/deduping next * use thumbnails in list items * Revert "refactor api client to use string method" This reverts commit 234326135b7af96cb91b941e7ca515f45c632556. * rename/cleanup * store servers by id * get rid of settings selectors * renames for clarity remove unused estimateContentLength setting * remove trackplayer selectors * fix migration for library filter settings * fixed shuffle order reporting wrong track/queue * removed the other selectors * don't actually need es6/react for our state * fix slow artist sort on star localeCompare is too slow for large lists
219 lines
5.7 KiB
TypeScript
219 lines
5.7 KiB
TypeScript
import Button from '@app/components/Button'
|
|
import GradientScrollView from '@app/components/GradientScrollView'
|
|
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 { Song, Album, Artist, SearchResults } 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 React, { useCallback, useMemo, useRef, useState } from 'react'
|
|
import {
|
|
ActivityIndicator,
|
|
InteractionManager,
|
|
ScrollView,
|
|
StatusBar,
|
|
StyleSheet,
|
|
TextInput as ReactTextInput,
|
|
View,
|
|
} from 'react-native'
|
|
|
|
const SongItem = React.memo<{ item: Song }>(({ item }) => {
|
|
const setQueue = useStore(store => store.setQueue)
|
|
|
|
return (
|
|
<ListItem
|
|
item={item}
|
|
contextId={item.id}
|
|
queueId={0}
|
|
showArt={true}
|
|
showStar={false}
|
|
onPress={() => setQueue([item], item.title, 'song', item.id, 0)}
|
|
/>
|
|
)
|
|
})
|
|
|
|
const ResultsCategory = React.memo<{
|
|
name: string
|
|
query: string
|
|
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.library.albums, ids)
|
|
case 'artist':
|
|
return mapById(store.library.artists, ids)
|
|
case 'song':
|
|
return mapById(store.library.songs, ids)
|
|
default:
|
|
return []
|
|
}
|
|
},
|
|
[ids, type],
|
|
),
|
|
)
|
|
|
|
const navigation = useNavigation()
|
|
|
|
if (items.length === 0) {
|
|
return <></>
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Header>{name}</Header>
|
|
{items.map(a =>
|
|
type === 'song' ? (
|
|
<SongItem key={a.id} item={a as Song} />
|
|
) : (
|
|
<ListItem key={a.id} item={a} showArt={true} showStar={false} />
|
|
),
|
|
)}
|
|
{items.length === 5 && (
|
|
<Button
|
|
title="More..."
|
|
buttonStyle="hollow"
|
|
style={styles.more}
|
|
onPress={() => navigation.navigate('results', { query, type: items[0].itemType })}
|
|
/>
|
|
)}
|
|
</>
|
|
)
|
|
})
|
|
|
|
const Results = React.memo<{
|
|
results: SearchResults
|
|
query: string
|
|
}>(({ 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} />
|
|
</>
|
|
)
|
|
})
|
|
|
|
const Search = () => {
|
|
const fetchSearchResults = useStore(store => store.fetchSearchResults)
|
|
const [results, setResults] = useState<SearchResults>({ artists: [], albums: [], songs: [] })
|
|
const [refreshing, setRefreshing] = useState(false)
|
|
const [text, setText] = useState('')
|
|
const searchBarRef = useRef<ReactTextInput>(null)
|
|
const scrollRef = useRef<ScrollView>(null)
|
|
|
|
useFocusEffect(
|
|
useCallback(() => {
|
|
const task = InteractionManager.runAfterInteractions(() => {
|
|
setTimeout(() => {
|
|
setText('')
|
|
setResults({ artists: [], albums: [], songs: [] })
|
|
searchBarRef.current?.focus()
|
|
scrollRef.current?.scrollTo({ y: 0, animated: true })
|
|
}, 50)
|
|
})
|
|
return () => task.cancel()
|
|
}, [searchBarRef, scrollRef]),
|
|
)
|
|
|
|
useActiveServerRefresh(
|
|
useCallback(() => {
|
|
setText('')
|
|
setResults({ artists: [], albums: [], songs: [] })
|
|
}, []),
|
|
)
|
|
|
|
const debouncedonUpdateSearch = useMemo(
|
|
() =>
|
|
debounce(async (query: string) => {
|
|
setRefreshing(true)
|
|
setResults(await fetchSearchResults({ query, albumCount: 5, artistCount: 5, songCount: 5 }))
|
|
setRefreshing(false)
|
|
}, 400),
|
|
[fetchSearchResults],
|
|
)
|
|
|
|
const onChangeText = useCallback(
|
|
(value: string) => {
|
|
setText(value)
|
|
debouncedonUpdateSearch(value)
|
|
},
|
|
[setText, debouncedonUpdateSearch],
|
|
)
|
|
|
|
const resultsCount = results.albums.length + results.artists.length + results.songs.length
|
|
|
|
return (
|
|
<GradientScrollView ref={scrollRef} style={styles.scroll} contentContainerStyle={styles.scrollContentContainer}>
|
|
<View style={styles.content}>
|
|
<View style={styles.inputBar}>
|
|
<TextInput
|
|
ref={searchBarRef}
|
|
style={styles.textInput}
|
|
placeholder="Search"
|
|
value={text}
|
|
onChangeText={onChangeText}
|
|
/>
|
|
<ActivityIndicator
|
|
animating={refreshing}
|
|
size="small"
|
|
color={colors.text.secondary}
|
|
style={styles.activity}
|
|
/>
|
|
</View>
|
|
{resultsCount > 0 ? <Results results={results} query={text} /> : <NothingHere style={styles.noResults} />}
|
|
</View>
|
|
</GradientScrollView>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
scroll: {
|
|
flex: 1,
|
|
},
|
|
scrollContentContainer: {
|
|
paddingTop: StatusBar.currentHeight,
|
|
},
|
|
content: {
|
|
paddingHorizontal: 20,
|
|
paddingBottom: 20,
|
|
alignItems: 'stretch',
|
|
},
|
|
inputBar: {
|
|
justifyContent: 'center',
|
|
},
|
|
activity: {
|
|
position: 'absolute',
|
|
right: 16,
|
|
bottom: 15,
|
|
},
|
|
noResults: {
|
|
width: '100%',
|
|
},
|
|
itemText: {
|
|
color: colors.text.primary,
|
|
fontFamily: font.regular,
|
|
fontSize: 14,
|
|
},
|
|
more: {
|
|
marginTop: 5,
|
|
marginBottom: 10,
|
|
},
|
|
textInput: {
|
|
marginTop: 20,
|
|
paddingHorizontal: 12,
|
|
paddingRight: 46,
|
|
},
|
|
})
|
|
|
|
export default Search
|