mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-28 17:19:27 +01:00
serach debouncing, hiding result categories
This commit is contained in:
parent
3615ec9ab6
commit
c12ff2c08c
@ -1,4 +1,4 @@
|
|||||||
import { AlbumListItem, Artist, PlaylistListItem, Song } from '@app/models/music'
|
import { ListableItem } from '@app/models/music'
|
||||||
import { currentTrackAtom } from '@app/state/trackplayer'
|
import { currentTrackAtom } from '@app/state/trackplayer'
|
||||||
import colors from '@app/styles/colors'
|
import colors from '@app/styles/colors'
|
||||||
import font from '@app/styles/font'
|
import font from '@app/styles/font'
|
||||||
@ -37,7 +37,7 @@ const TitleText = React.memo<{
|
|||||||
})
|
})
|
||||||
|
|
||||||
const ListItem: React.FC<{
|
const ListItem: React.FC<{
|
||||||
item: Song | AlbumListItem | Artist | PlaylistListItem
|
item: ListableItem
|
||||||
onPress?: (event: GestureResponderEvent) => void
|
onPress?: (event: GestureResponderEvent) => void
|
||||||
showArt?: boolean
|
showArt?: boolean
|
||||||
showStar?: boolean
|
showStar?: boolean
|
||||||
|
|||||||
@ -1,17 +1,18 @@
|
|||||||
import font from '@app/styles/font'
|
import font from '@app/styles/font'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Text, View, StyleSheet } from 'react-native'
|
import { Text, View, StyleSheet, ViewStyle } from 'react-native'
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
|
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
|
||||||
|
|
||||||
const NothingHere = React.memo<{
|
const NothingHere = React.memo<{
|
||||||
height?: number
|
height?: number
|
||||||
width?: number
|
width?: number
|
||||||
}>(({ height, width }) => {
|
style?: ViewStyle
|
||||||
|
}>(({ height, width, style }) => {
|
||||||
height = height || 200
|
height = height || 200
|
||||||
width = width || 200
|
width = width || 200
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { height, width }]}>
|
<View style={[styles.container, { height, width }, style]}>
|
||||||
<Icon name="music-rest-quarter" color={styles.text.color} size={width / 2} />
|
<Icon name="music-rest-quarter" color={styles.text.color} size={width / 2} />
|
||||||
<Text style={[styles.text, { fontSize: width / 8 }]}>Nothing here...</Text>
|
<Text style={[styles.text, { fontSize: width / 8 }]}>Nothing here...</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@ -67,6 +67,8 @@ export interface Song {
|
|||||||
coverArt?: string
|
coverArt?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ListableItem = Song | AlbumListItem | Artist | PlaylistListItem
|
||||||
|
|
||||||
export type DownloadedSong = {
|
export type DownloadedSong = {
|
||||||
id: string
|
id: string
|
||||||
type: 'song'
|
type: 'song'
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
import React from 'react'
|
|
||||||
import { Text, View, StyleSheet } from 'react-native'
|
|
||||||
import { BottomTabBarProps } from '@react-navigation/bottom-tabs'
|
|
||||||
import colors from '@app/styles/colors'
|
|
||||||
import FastImage from 'react-native-fast-image'
|
|
||||||
import NowPlayingBar from '@app/components/NowPlayingBar'
|
import NowPlayingBar from '@app/components/NowPlayingBar'
|
||||||
import PressableOpacity from '@app/components/PressableOpacity'
|
import PressableOpacity from '@app/components/PressableOpacity'
|
||||||
import font from '@app/styles/font'
|
import colors from '@app/styles/colors'
|
||||||
import dimensions from '@app/styles/dimensions'
|
import dimensions from '@app/styles/dimensions'
|
||||||
|
import font from '@app/styles/font'
|
||||||
|
import { BottomTabBarProps } from '@react-navigation/bottom-tabs'
|
||||||
|
import { BottomTabNavigationEventMap } from '@react-navigation/bottom-tabs/lib/typescript/src/types'
|
||||||
|
import { NavigationHelpers, ParamListBase } from '@react-navigation/native'
|
||||||
|
import React from 'react'
|
||||||
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
|
import FastImage from 'react-native-fast-image'
|
||||||
|
|
||||||
type TabButtonImage = {
|
type TabButtonImage = {
|
||||||
regular: number
|
regular: number
|
||||||
@ -38,7 +40,7 @@ const BottomTabButton = React.memo<{
|
|||||||
name: string
|
name: string
|
||||||
isFocused: boolean
|
isFocused: boolean
|
||||||
img: TabButtonImage
|
img: TabButtonImage
|
||||||
navigation: any
|
navigation: NavigationHelpers<ParamListBase, BottomTabNavigationEventMap>
|
||||||
}>(({ routeKey, label, name, isFocused, img, navigation }) => {
|
}>(({ routeKey, label, name, isFocused, img, navigation }) => {
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
const event = navigation.emit({
|
const event = navigation.emit({
|
||||||
@ -53,7 +55,6 @@ const BottomTabButton = React.memo<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// <PressableOpacity onPress={onPress} style={styles.button} ripple={true} rippleColor="rgba(100,100,100,0.18)">
|
|
||||||
<PressableOpacity onPress={onPress} style={styles.button}>
|
<PressableOpacity onPress={onPress} style={styles.button}>
|
||||||
<FastImage
|
<FastImage
|
||||||
source={isFocused ? img.fill : img.regular}
|
source={isFocused ? img.fill : img.regular}
|
||||||
|
|||||||
@ -1,47 +1,92 @@
|
|||||||
import GradientScrollView from '@app/components/GradientScrollView'
|
import GradientScrollView from '@app/components/GradientScrollView'
|
||||||
import Header from '@app/components/Header'
|
import Header from '@app/components/Header'
|
||||||
import ListItem from '@app/components/ListItem'
|
import ListItem from '@app/components/ListItem'
|
||||||
import { searchResultsAtom, useUpdateSearchResults } from '@app/state/music'
|
import NothingHere from '@app/components/NothingHere'
|
||||||
|
import { ListableItem, SearchResults } from '@app/models/music'
|
||||||
|
import { searchResultsAtom, searchResultsUpdatingAtom, useUpdateSearchResults } from '@app/state/music'
|
||||||
import colors from '@app/styles/colors'
|
import colors from '@app/styles/colors'
|
||||||
import font from '@app/styles/font'
|
import font from '@app/styles/font'
|
||||||
import { useAtomValue } from 'jotai/utils'
|
import { useAtomValue } from 'jotai/utils'
|
||||||
import React, { useState } from 'react'
|
import debounce from 'lodash.debounce'
|
||||||
import { StatusBar, StyleSheet, View, TextInput } from 'react-native'
|
import React, { useCallback, useMemo, useState } from 'react'
|
||||||
|
import { ActivityIndicator, StatusBar, StyleSheet, TextInput, View } from 'react-native'
|
||||||
|
|
||||||
|
function getSubtitle(item: ListableItem) {
|
||||||
|
switch (item.itemType) {
|
||||||
|
case 'playlist':
|
||||||
|
return item.comment
|
||||||
|
case 'album':
|
||||||
|
case 'song':
|
||||||
|
return item.artist
|
||||||
|
default:
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResultsCategory = React.memo<{
|
||||||
|
name: string
|
||||||
|
items: ListableItem[]
|
||||||
|
}>(({ name, items }) => {
|
||||||
|
if (items.length === 0) {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header>{name}</Header>
|
||||||
|
{items.map(a => (
|
||||||
|
<ListItem key={a.id} item={a} showArt={true} showStar={false} subtitle={getSubtitle(a)} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const Results = React.memo<{
|
||||||
|
results: SearchResults
|
||||||
|
}>(({ results }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ResultsCategory name="Artists" items={results.artists} />
|
||||||
|
<ResultsCategory name="Albums" items={results.albums} />
|
||||||
|
<ResultsCategory name="Songs" items={results.songs} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const Search = () => {
|
const Search = () => {
|
||||||
const [text, setText] = useState('')
|
const [text, setText] = useState('')
|
||||||
const updateSearch = useUpdateSearchResults()
|
const updateSearch = useUpdateSearchResults()
|
||||||
|
const updating = useAtomValue(searchResultsUpdatingAtom)
|
||||||
const results = useAtomValue(searchResultsAtom)
|
const results = useAtomValue(searchResultsAtom)
|
||||||
|
|
||||||
const onSubmitEditing = () => {
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
console.log(text)
|
const debouncedonUpdateSearch = useMemo(() => debounce(updateSearch, 400), [])
|
||||||
updateSearch(text)
|
|
||||||
}
|
const onChangeText = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
setText(value)
|
||||||
|
debouncedonUpdateSearch(value)
|
||||||
|
},
|
||||||
|
[setText, debouncedonUpdateSearch],
|
||||||
|
)
|
||||||
|
|
||||||
|
const resultsCount = results.albums.length + results.artists.length + results.songs.length
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GradientScrollView style={styles.scroll} contentContainerStyle={styles.scrollContentContainer}>
|
<GradientScrollView style={styles.scroll} contentContainerStyle={styles.scrollContentContainer}>
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
<TextInput
|
<View style={styles.inputBar}>
|
||||||
style={styles.textInput}
|
<TextInput
|
||||||
placeholder="Search"
|
style={styles.textInput}
|
||||||
placeholderTextColor="grey"
|
placeholder="Search"
|
||||||
selectionColor={colors.text.secondary}
|
placeholderTextColor="grey"
|
||||||
value={text}
|
selectionColor={colors.text.secondary}
|
||||||
onChangeText={setText}
|
value={text}
|
||||||
onSubmitEditing={onSubmitEditing}
|
onChangeText={onChangeText}
|
||||||
/>
|
/>
|
||||||
<Header>Artists</Header>
|
<ActivityIndicator animating={updating} size="small" color={colors.text.secondary} style={styles.activity} />
|
||||||
{results.artists.map(a => (
|
</View>
|
||||||
<ListItem key={a.id} item={a} showArt={true} showStar={false} />
|
{resultsCount > 0 ? <Results results={results} /> : <NothingHere style={styles.noResults} />}
|
||||||
))}
|
|
||||||
<Header>Albums</Header>
|
|
||||||
{results.albums.map(a => (
|
|
||||||
<ListItem key={a.id} item={a} showArt={true} showStar={false} subtitle={a.artist} />
|
|
||||||
))}
|
|
||||||
<Header>Songs</Header>
|
|
||||||
{results.songs.map(a => (
|
|
||||||
<ListItem key={a.id} item={a} showArt={true} showStar={false} subtitle={a.artist} />
|
|
||||||
))}
|
|
||||||
</View>
|
</View>
|
||||||
</GradientScrollView>
|
</GradientScrollView>
|
||||||
)
|
)
|
||||||
@ -56,14 +101,28 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
|
alignItems: 'stretch',
|
||||||
|
},
|
||||||
|
inputBar: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
activity: {
|
||||||
|
position: 'absolute',
|
||||||
|
right: 16,
|
||||||
|
bottom: 15,
|
||||||
},
|
},
|
||||||
textInput: {
|
textInput: {
|
||||||
|
width: '100%',
|
||||||
backgroundColor: '#515151',
|
backgroundColor: '#515151',
|
||||||
fontFamily: font.regular,
|
fontFamily: font.regular,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
color: colors.text.primary,
|
color: colors.text.primary,
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
|
paddingRight: 46,
|
||||||
|
},
|
||||||
|
noResults: {
|
||||||
|
width: '100%',
|
||||||
},
|
},
|
||||||
itemText: {
|
itemText: {
|
||||||
color: colors.text.primary,
|
color: colors.text.primary,
|
||||||
|
|||||||
@ -114,7 +114,7 @@ export const useUpdateSearchResults = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return async (query: string) => {
|
return async (query: string) => {
|
||||||
if (updating) {
|
if (updating || query.length < 2) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setUpdating(true)
|
setUpdating(true)
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
"@react-navigation/native": "^5.9.4",
|
"@react-navigation/native": "^5.9.4",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"jotai": "^1.1.0",
|
"jotai": "^1.1.0",
|
||||||
|
"lodash.debounce": "^4.0.8",
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
"react": "17.0.1",
|
"react": "17.0.1",
|
||||||
"react-native": "0.64.1",
|
"react-native": "0.64.1",
|
||||||
@ -41,6 +42,7 @@
|
|||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"@react-native-community/eslint-config": "^2.0.0",
|
"@react-native-community/eslint-config": "^2.0.0",
|
||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "^26.0.23",
|
||||||
|
"@types/lodash.debounce": "^4.0.6",
|
||||||
"@types/md5": "^2.3.0",
|
"@types/md5": "^2.3.0",
|
||||||
"@types/react-native": "^0.64.5",
|
"@types/react-native": "^0.64.5",
|
||||||
"@types/react-native-vector-icons": "^6.4.7",
|
"@types/react-native-vector-icons": "^6.4.7",
|
||||||
|
|||||||
12
yarn.lock
12
yarn.lock
@ -1293,6 +1293,18 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
|
||||||
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
|
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
|
||||||
|
|
||||||
|
"@types/lodash.debounce@^4.0.6":
|
||||||
|
version "4.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.6.tgz#c5a2326cd3efc46566c47e4c0aa248dc0ee57d60"
|
||||||
|
integrity sha512-4WTmnnhCfDvvuLMaF3KV4Qfki93KebocUF45msxhYyjMttZDQYzHkO639ohhk8+oco2cluAFL3t5+Jn4mleylQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/lodash" "*"
|
||||||
|
|
||||||
|
"@types/lodash@*":
|
||||||
|
version "4.14.171"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.171.tgz#f01b3a5fe3499e34b622c362a46a609fdb23573b"
|
||||||
|
integrity sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg==
|
||||||
|
|
||||||
"@types/md5@^2.3.0":
|
"@types/md5@^2.3.0":
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.3.0.tgz#3b6a623091160f4dc75be3173e25f2110dc3fa1f"
|
resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.3.0.tgz#3b6a623091160f4dc75be3173e25f2110dc3fa1f"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user