mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 00:59:28 +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 colors from '@app/styles/colors'
|
||||
import font from '@app/styles/font'
|
||||
@ -37,7 +37,7 @@ const TitleText = React.memo<{
|
||||
})
|
||||
|
||||
const ListItem: React.FC<{
|
||||
item: Song | AlbumListItem | Artist | PlaylistListItem
|
||||
item: ListableItem
|
||||
onPress?: (event: GestureResponderEvent) => void
|
||||
showArt?: boolean
|
||||
showStar?: boolean
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
import font from '@app/styles/font'
|
||||
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'
|
||||
|
||||
const NothingHere = React.memo<{
|
||||
height?: number
|
||||
width?: number
|
||||
}>(({ height, width }) => {
|
||||
style?: ViewStyle
|
||||
}>(({ height, width, style }) => {
|
||||
height = height || 200
|
||||
width = width || 200
|
||||
|
||||
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} />
|
||||
<Text style={[styles.text, { fontSize: width / 8 }]}>Nothing here...</Text>
|
||||
</View>
|
||||
|
||||
@ -67,6 +67,8 @@ export interface Song {
|
||||
coverArt?: string
|
||||
}
|
||||
|
||||
export type ListableItem = Song | AlbumListItem | Artist | PlaylistListItem
|
||||
|
||||
export type DownloadedSong = {
|
||||
id: string
|
||||
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 PressableOpacity from '@app/components/PressableOpacity'
|
||||
import font from '@app/styles/font'
|
||||
import colors from '@app/styles/colors'
|
||||
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 = {
|
||||
regular: number
|
||||
@ -38,7 +40,7 @@ const BottomTabButton = React.memo<{
|
||||
name: string
|
||||
isFocused: boolean
|
||||
img: TabButtonImage
|
||||
navigation: any
|
||||
navigation: NavigationHelpers<ParamListBase, BottomTabNavigationEventMap>
|
||||
}>(({ routeKey, label, name, isFocused, img, navigation }) => {
|
||||
const onPress = () => {
|
||||
const event = navigation.emit({
|
||||
@ -53,7 +55,6 @@ const BottomTabButton = React.memo<{
|
||||
}
|
||||
|
||||
return (
|
||||
// <PressableOpacity onPress={onPress} style={styles.button} ripple={true} rippleColor="rgba(100,100,100,0.18)">
|
||||
<PressableOpacity onPress={onPress} style={styles.button}>
|
||||
<FastImage
|
||||
source={isFocused ? img.fill : img.regular}
|
||||
|
||||
@ -1,47 +1,92 @@
|
||||
import GradientScrollView from '@app/components/GradientScrollView'
|
||||
import Header from '@app/components/Header'
|
||||
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 font from '@app/styles/font'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import React, { useState } from 'react'
|
||||
import { StatusBar, StyleSheet, View, TextInput } from 'react-native'
|
||||
import debounce from 'lodash.debounce'
|
||||
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 [text, setText] = useState('')
|
||||
const updateSearch = useUpdateSearchResults()
|
||||
const updating = useAtomValue(searchResultsUpdatingAtom)
|
||||
const results = useAtomValue(searchResultsAtom)
|
||||
|
||||
const onSubmitEditing = () => {
|
||||
console.log(text)
|
||||
updateSearch(text)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const debouncedonUpdateSearch = useMemo(() => debounce(updateSearch, 400), [])
|
||||
|
||||
const onChangeText = useCallback(
|
||||
(value: string) => {
|
||||
setText(value)
|
||||
debouncedonUpdateSearch(value)
|
||||
},
|
||||
[setText, debouncedonUpdateSearch],
|
||||
)
|
||||
|
||||
const resultsCount = results.albums.length + results.artists.length + results.songs.length
|
||||
|
||||
return (
|
||||
<GradientScrollView style={styles.scroll} contentContainerStyle={styles.scrollContentContainer}>
|
||||
<View style={styles.content}>
|
||||
<TextInput
|
||||
style={styles.textInput}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="grey"
|
||||
selectionColor={colors.text.secondary}
|
||||
value={text}
|
||||
onChangeText={setText}
|
||||
onSubmitEditing={onSubmitEditing}
|
||||
/>
|
||||
<Header>Artists</Header>
|
||||
{results.artists.map(a => (
|
||||
<ListItem key={a.id} item={a} showArt={true} showStar={false} />
|
||||
))}
|
||||
<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 style={styles.inputBar}>
|
||||
<TextInput
|
||||
style={styles.textInput}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="grey"
|
||||
selectionColor={colors.text.secondary}
|
||||
value={text}
|
||||
onChangeText={onChangeText}
|
||||
/>
|
||||
<ActivityIndicator animating={updating} size="small" color={colors.text.secondary} style={styles.activity} />
|
||||
</View>
|
||||
{resultsCount > 0 ? <Results results={results} /> : <NothingHere style={styles.noResults} />}
|
||||
</View>
|
||||
</GradientScrollView>
|
||||
)
|
||||
@ -56,14 +101,28 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
content: {
|
||||
paddingHorizontal: 20,
|
||||
alignItems: 'stretch',
|
||||
},
|
||||
inputBar: {
|
||||
justifyContent: 'center',
|
||||
},
|
||||
activity: {
|
||||
position: 'absolute',
|
||||
right: 16,
|
||||
bottom: 15,
|
||||
},
|
||||
textInput: {
|
||||
width: '100%',
|
||||
backgroundColor: '#515151',
|
||||
fontFamily: font.regular,
|
||||
fontSize: 18,
|
||||
color: colors.text.primary,
|
||||
marginTop: 20,
|
||||
paddingHorizontal: 12,
|
||||
paddingRight: 46,
|
||||
},
|
||||
noResults: {
|
||||
width: '100%',
|
||||
},
|
||||
itemText: {
|
||||
color: colors.text.primary,
|
||||
|
||||
@ -114,7 +114,7 @@ export const useUpdateSearchResults = () => {
|
||||
}
|
||||
|
||||
return async (query: string) => {
|
||||
if (updating) {
|
||||
if (updating || query.length < 2) {
|
||||
return
|
||||
}
|
||||
setUpdating(true)
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
"@react-navigation/native": "^5.9.4",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"jotai": "^1.1.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"md5": "^2.3.0",
|
||||
"react": "17.0.1",
|
||||
"react-native": "0.64.1",
|
||||
@ -41,6 +42,7 @@
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@react-native-community/eslint-config": "^2.0.0",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/lodash.debounce": "^4.0.6",
|
||||
"@types/md5": "^2.3.0",
|
||||
"@types/react-native": "^0.64.5",
|
||||
"@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"
|
||||
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":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.3.0.tgz#3b6a623091160f4dc75be3173e25f2110dc3fa1f"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user