Merge branch 'main' of github.com:Jellify-Music/App into feature/nitro-player

This commit is contained in:
Violet Caulfield
2026-02-23 18:18:05 -06:00
20 changed files with 143 additions and 156 deletions

View File

@@ -24,6 +24,7 @@ import { registerAutoService } from './src/services/carplay'
import QueryPersistenceConfig from './src/configs/query-persistence.config'
import registerTrackPlayer from './src/services/player'
import configureDownloadManager from './src/services/downloads'
import { ReducedMotionConfig, ReduceMotion } from 'react-native-reanimated'
LogBox.ignoreAllLogs()
@@ -88,6 +89,7 @@ function Container(): React.JSX.Element {
theme={getJellifyNavTheme(colorPreset, resolvedMode)}
>
<GestureHandlerRootView>
<ReducedMotionConfig mode={ReduceMotion.System} />
<TamaguiProvider config={jellifyConfig}>
<Jellify />
</TamaguiProvider>

View File

@@ -88,8 +88,8 @@ android {
applicationId "com.cosmonautical.jellify"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 176
versionName "1.0.17"
versionCode 177
versionName "1.0.18"
}
signingConfigs {

View File

@@ -41,7 +41,7 @@
"react-native-nitro-ota": "0.11.0",
"react-native-nitro-player": "0.5.6",
"react-native-pager-view": "8.0.0",
"react-native-reanimated": "4.2.1",
"react-native-reanimated": "4.1.6",
"react-native-safe-area-context": "5.6.2",
"react-native-screens": "4.23.0",
"react-native-sortables": "1.9.4",
@@ -1914,7 +1914,7 @@
"react-native-pager-view": ["react-native-pager-view@8.0.0", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-oAwlWT1lhTkIs9HhODnjNNl/owxzn9DP1MbP+az6OTUdgbmzA16Up83sBH8NRKwrH8rNm7iuWnX1qMqiiWOLhg=="],
"react-native-reanimated": ["react-native-reanimated@4.2.1", "", { "dependencies": { "react-native-is-edge-to-edge": "1.2.1", "semver": "7.7.3" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-worklets": ">=0.7.0" } }, "sha512-/NcHnZMyOvsD/wYXug/YqSKw90P9edN0kEPL5lP4PFf1aQ4F1V7MKe/E0tvfkXKIajy3Qocp5EiEnlcrK/+BZg=="],
"react-native-reanimated": ["react-native-reanimated@4.1.6", "", { "dependencies": { "react-native-is-edge-to-edge": "^1.2.1", "semver": "7.7.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", "react-native": "*", "react-native-worklets": ">=0.5.0" } }, "sha512-F+ZJBYiok/6Jzp1re75F/9aLzkgoQCOh4yxrnwATa8392RvM3kx+fiXXFvwcgE59v48lMwd9q0nzF1oJLXpfxQ=="],
"react-native-safe-area-context": ["react-native-safe-area-context@5.6.2", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg=="],

View File

@@ -543,7 +543,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 284;
CURRENT_PROJECT_VERSION = 285;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG;
ENABLE_BITCODE = NO;
@@ -554,7 +554,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.17;
MARKETING_VERSION = 1.0.18;
NEW_SETTING = "";
OTHER_LDFLAGS = (
"$(inherited)",
@@ -585,7 +585,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 284;
CURRENT_PROJECT_VERSION = 285;
DEVELOPMENT_TEAM = WAH9CZ8BPG;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
@@ -595,7 +595,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.17;
MARKETING_VERSION = 1.0.18;
NEW_SETTING = "";
OTHER_LDFLAGS = (
"$(inherited)",
@@ -833,7 +833,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 284;
CURRENT_PROJECT_VERSION = 285;
DEVELOPMENT_TEAM = WAH9CZ8BPG;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
@@ -844,7 +844,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.17;
MARKETING_VERSION = 1.0.18;
NEW_SETTING = "";
OTHER_LDFLAGS = (
"$(inherited)",

View File

@@ -2198,7 +2198,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- RNReanimated (4.2.1):
- RNReanimated (4.1.6):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@@ -2220,10 +2220,10 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- RNReanimated/reanimated (= 4.2.1)
- RNReanimated/reanimated (= 4.1.6)
- RNWorklets
- Yoga
- RNReanimated/reanimated (4.2.1):
- RNReanimated/reanimated (4.1.6):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@@ -2245,10 +2245,10 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- RNReanimated/reanimated/apple (= 4.2.1)
- RNReanimated/reanimated/apple (= 4.1.6)
- RNWorklets
- Yoga
- RNReanimated/reanimated/apple (4.2.1):
- RNReanimated/reanimated/apple (4.1.6):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@@ -2829,7 +2829,7 @@ SPEC CHECKSUMS:
RNDeviceInfo: 36d7f232bfe7c9b5c494cb7793230424ed32c388
RNGestureHandler: 07de6f059e0ee5744ae9a56feb07ee345338cc31
RNReactNativeHapticFeedback: d7bc1bcfe68bb139c9919ea81d11d7c6e866e870
RNReanimated: 5a8239f0bd8835225707968c1357b19b447d8681
RNReanimated: caefad1cdf778187bc12b747ff958ee7c6d2e580
RNScreens: 14243fa0d9842ffa7f8bb2d00b6c3cfd3ca817e8
RNSentry: c6ec62a7d10299ac5d8e0916202913da77bb9fc7
RNWorklets: b6bad5e2ccfdddc4751bca114acf1680dc3231d4

View File

@@ -1,6 +1,6 @@
{
"name": "jellify",
"version": "1.0.17",
"version": "1.0.18",
"private": true,
"scripts": {
"init-android": "bun i",
@@ -74,7 +74,7 @@
"react-native-nitro-ota": "0.11.0",
"react-native-nitro-player": "0.5.6",
"react-native-pager-view": "8.0.0",
"react-native-reanimated": "4.2.1",
"react-native-reanimated": "4.1.6",
"react-native-safe-area-context": "5.6.2",
"react-native-screens": "4.23.0",
"react-native-sortables": "1.9.4",

View File

@@ -0,0 +1,13 @@
diff --git a/node_modules/react-native-reanimated/compatibility.json b/node_modules/react-native-reanimated/compatibility.json
index f1ba9cb..ffab7fe 100644
--- a/node_modules/react-native-reanimated/compatibility.json
+++ b/node_modules/react-native-reanimated/compatibility.json
@@ -4,7 +4,7 @@
"react-native-worklets": ["nightly"]
},
"4.1.x": {
- "react-native": ["0.78", "0.79", "0.80", "0.81", "0.82"],
+ "react-native": ["0.78", "0.79", "0.80", "0.81", "0.82", "0.83", "0.84"],
"react-native-worklets": ["0.5.x", "0.6.x", "0.7.x"]
},
"4.0.x": {

View File

@@ -1,13 +0,0 @@
diff --git a/node_modules/react-native-reanimated/compatibility.json b/node_modules/react-native-reanimated/compatibility.json
index e278e81..97cfddf 100644
--- a/node_modules/react-native-reanimated/compatibility.json
+++ b/node_modules/react-native-reanimated/compatibility.json
@@ -5,7 +5,7 @@
"react-native-worklets": ["nightly"]
},
"4.2.x": {
- "react-native": ["0.80", "0.81", "0.82", "0.83"],
+ "react-native": ["0.80", "0.81", "0.82", "0.83", "0.84"],
"react-native-worklets": ["0.7.x"]
},
"4.1.x": {

View File

@@ -0,0 +1,19 @@
import { QueryKeys } from '../../../enums/query-keys'
import { useQuery } from '@tanstack/react-query'
import { fetchSearchResults } from './utils'
import { ONE_MINUTE } from '../../../constants/query-client'
import { useJellifyLibrary } from '../../../stores'
const useSearchResults = (searchString: string | undefined) => {
const [library] = useJellifyLibrary()
return useQuery({
queryKey: [QueryKeys.Search, library?.musicLibraryId, searchString],
queryFn: () => fetchSearchResults(library?.musicLibraryId, searchString),
staleTime: ONE_MINUTE * 10, // Cache results for 10 minutes
gcTime: ONE_MINUTE * 15, // Garbage collect after 15 minutes
enabled: !!library?.musicLibraryId && !!searchString, // Only run if we have a library ID and a search string
})
}
export default useSearchResults

View File

@@ -1,9 +1,8 @@
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api'
import { isEmpty, isUndefined, trim } from 'lodash'
import QueryConfig from '../../configs/query.config'
import { Api } from '@jellyfin/sdk'
import { JellifyUser } from '../../types/JellifyUser'
import QueryConfig from '../../../../configs/query.config'
import { getApi, getUser } from '../../../../stores'
/**
* Performs a search for items against the Jellyfin server, trimming whitespace
* around the search term for the best possible results.
@@ -11,12 +10,13 @@ import { JellifyUser } from '../../types/JellifyUser'
* @returns A promise of a BaseItemDto array, be it empty or not
*/
export async function fetchSearchResults(
api: Api | undefined,
user: JellifyUser | undefined,
libraryId: string | undefined,
searchString: string | undefined,
): Promise<BaseItemDto[]> {
return new Promise((resolve, reject) => {
const api = getApi()
const user = getUser()
if (isEmpty(searchString)) resolve([])
if (isUndefined(api)) return reject('Client instance not set')

View File

@@ -7,9 +7,9 @@ import { useNavigation } from '@react-navigation/native'
import DiscoverStackParamList from '../../../screens/Discover/types'
import navigationRef from '../../../../navigation'
import { useRecentlyAddedAlbums } from '../../../api/queries/album'
import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated'
import AnimatedRow from '../../Global/helpers/animated-row'
export default function RecentlyAdded(): React.JSX.Element | null {
export default function RecentlyAdded(): React.JSX.Element {
const recentlyAddedAlbumsInfinityQuery = useRecentlyAddedAlbums()
const navigation = useNavigation<NativeStackNavigationProp<DiscoverStackParamList>>()
@@ -18,15 +18,7 @@ export default function RecentlyAdded(): React.JSX.Element | null {
recentlyAddedAlbumsInfinityQuery.data && recentlyAddedAlbumsInfinityQuery.data.length > 0
return recentlyAddedExists ? (
<Animated.View
entering={FadeIn.easing(Easing.in(Easing.ease))}
exiting={FadeOut.easing(Easing.out(Easing.ease))}
layout={LinearTransition.springify()}
testID='discover-recently-added'
style={{
flex: 1,
}}
>
<AnimatedRow testID='discover-recently-added'>
<XStack
alignItems='center'
onPress={() => {
@@ -64,6 +56,8 @@ export default function RecentlyAdded(): React.JSX.Element | null {
/>
)}
/>
</Animated.View>
) : null
</AnimatedRow>
) : (
<></>
)
}

View File

@@ -9,9 +9,9 @@ import DiscoverStackParamList from '../../../screens/Discover/types'
import navigationRef from '../../../../navigation'
import { useJellifyServer } from '../../../stores'
import { usePublicPlaylists } from '../../../api/queries/playlist'
import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated'
import AnimatedRow from '../../Global/helpers/animated-row'
export default function PublicPlaylists(): React.JSX.Element | null {
export default function PublicPlaylists(): React.JSX.Element {
const {
data: playlists,
fetchNextPage,
@@ -29,15 +29,7 @@ export default function PublicPlaylists(): React.JSX.Element | null {
const publicPlaylistsExist = playlists && playlists.length > 0
return publicPlaylistsExist ? (
<Animated.View
entering={FadeIn.easing(Easing.in(Easing.ease))}
exiting={FadeOut.easing(Easing.out(Easing.ease))}
layout={LinearTransition.springify()}
testID='discover-public-playlists'
style={{
flex: 1,
}}
>
<AnimatedRow testID='discover-public-playlists'>
<XStack
alignItems='center'
onPress={() => {
@@ -80,6 +72,8 @@ export default function PublicPlaylists(): React.JSX.Element | null {
/>
)}
/>
</Animated.View>
) : null
</AnimatedRow>
) : (
<></>
)
}

View File

@@ -1,6 +1,5 @@
import navigationRef from '../../../../navigation'
import { formatArtistNames } from '../../../utils/formatting/artist-names'
import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated'
import ItemCard from '../../Global/components/item-card'
import HorizontalCardList from '../../Global/components/horizontal-list'
import { XStack } from 'tamagui'
@@ -10,6 +9,7 @@ import { useNavigation } from '@react-navigation/native'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import DiscoverStackParamList from '../../../screens/Discover/types'
import { useDiscoverAlbums } from '../../../api/queries/suggestions'
import AnimatedRow from '../../Global/helpers/animated-row'
export default function SuggestedAlbums() {
const suggestedAlbumsInfiniteQuery = useDiscoverAlbums()
@@ -20,15 +20,7 @@ export default function SuggestedAlbums() {
suggestedAlbumsInfiniteQuery.data && suggestedAlbumsInfiniteQuery.data.length > 0
return suggestedAlbumsExist ? (
<Animated.View
entering={FadeIn.easing(Easing.in(Easing.ease))}
exiting={FadeOut.easing(Easing.out(Easing.ease))}
layout={LinearTransition.springify()}
testID='discover-suggested-albums'
style={{
flex: 1,
}}
>
<AnimatedRow testID='discover-suggested-albums'>
<XStack
alignItems='center'
onPress={() => {
@@ -64,6 +56,8 @@ export default function SuggestedAlbums() {
/>
)}
/>
</Animated.View>
) : null
</AnimatedRow>
) : (
<></>
)
}

View File

@@ -7,10 +7,10 @@ import { useNavigation } from '@react-navigation/native'
import DiscoverStackParamList from '../../../screens/Discover/types'
import navigationRef from '../../../../navigation'
import { pickFirstGenre } from '../../../utils/formatting/genres'
import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated'
import { useDiscoverArtists } from '../../../api/queries/suggestions'
import AnimatedRow from '../../Global/helpers/animated-row'
export default function SuggestedArtists(): React.JSX.Element | null {
export default function SuggestedArtists(): React.JSX.Element {
const suggestedArtistsInfiniteQuery = useDiscoverArtists()
const navigation = useNavigation<NativeStackNavigationProp<DiscoverStackParamList>>()
@@ -19,15 +19,7 @@ export default function SuggestedArtists(): React.JSX.Element | null {
suggestedArtistsInfiniteQuery.data && suggestedArtistsInfiniteQuery.data.length > 0
return suggestedArtistsExist ? (
<Animated.View
entering={FadeIn.easing(Easing.in(Easing.ease))}
exiting={FadeOut.easing(Easing.out(Easing.ease))}
layout={LinearTransition.springify()}
testID='discover-suggested-artists'
style={{
flex: 1,
}}
>
<AnimatedRow testID='discover-suggested-artists'>
<XStack
alignItems='center'
onPress={() => {
@@ -63,6 +55,8 @@ export default function SuggestedArtists(): React.JSX.Element | null {
/>
)}
/>
</Animated.View>
) : null
</AnimatedRow>
) : (
<></>
)
}

View File

@@ -0,0 +1,28 @@
import Animated, {
FadeIn,
ReduceMotion,
FadeOut,
LinearTransition,
Easing,
} from 'react-native-reanimated'
interface AnimatedRowProps {
children: React.ReactNode
testID?: string
}
export default function AnimatedRow({ children, testID }: AnimatedRowProps) {
return (
<Animated.View
testID={testID}
entering={FadeIn.easing(Easing.in(Easing.ease)).reduceMotion(ReduceMotion.System)}
exiting={FadeOut.easing(Easing.out(Easing.ease)).reduceMotion(ReduceMotion.System)}
layout={LinearTransition.springify().reduceMotion(ReduceMotion.System)}
style={{
flex: 1,
}}
>
{children}
</Animated.View>
)
}

View File

@@ -11,7 +11,7 @@ import { RootStackParamList } from '../../../screens/types'
import { useFrequentlyPlayedArtists } from '../../../api/queries/frequents'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'
import { pickFirstGenre } from '../../../utils/formatting/genres'
import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated'
import AnimatedRow from '../../Global/helpers/animated-row'
export default function FrequentArtists(): React.JSX.Element {
const navigation = useNavigation<NativeStackNavigationProp<HomeStackParamList>>()
@@ -41,14 +41,7 @@ export default function FrequentArtists(): React.JSX.Element {
)
return frequentArtistsInfiniteQuery.data ? (
<Animated.View
entering={FadeIn.easing(Easing.in(Easing.ease))}
exiting={FadeOut.easing(Easing.out(Easing.ease))}
layout={LinearTransition.springify()}
style={{
flex: 1,
}}
>
<AnimatedRow testID='home-frequent-artists'>
<XStack
alignItems='center'
onPress={() => {
@@ -63,7 +56,7 @@ export default function FrequentArtists(): React.JSX.Element {
data={frequentArtistsInfiniteQuery.data.slice(0, horizontalItems) ?? []}
renderItem={renderItem}
/>
</Animated.View>
</AnimatedRow>
) : (
<></>
)

View File

@@ -10,7 +10,7 @@ import HomeStackParamList from '../../../screens/Home/types'
import { useNavigation } from '@react-navigation/native'
import { RootStackParamList } from '../../../screens/types'
import { useFrequentlyPlayedTracks } from '../../../api/queries/frequents'
import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated'
import AnimatedRow from '../../Global/helpers/animated-row'
export default function FrequentlyPlayedTracks(): React.JSX.Element {
const tracksInfiniteQuery = useFrequentlyPlayedTracks()
@@ -23,14 +23,7 @@ export default function FrequentlyPlayedTracks(): React.JSX.Element {
const { horizontalItems } = useDisplayContext()
return tracksInfiniteQuery.data ? (
<Animated.View
entering={FadeIn.easing(Easing.in(Easing.ease))}
exiting={FadeOut.easing(Easing.out(Easing.ease))}
layout={LinearTransition.springify()}
style={{
flex: 1,
}}
>
<AnimatedRow testID='home-frequent-tracks'>
<XStack
alignItems='center'
onPress={() => {
@@ -74,7 +67,7 @@ export default function FrequentlyPlayedTracks(): React.JSX.Element {
/>
)}
/>
</Animated.View>
</AnimatedRow>
) : (
<></>
)

View File

@@ -11,7 +11,7 @@ import HomeStackParamList from '../../../screens/Home/types'
import { useRecentArtists } from '../../../api/queries/recents'
import { pickFirstGenre } from '../../../utils/formatting/genres'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto'
import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated'
import AnimatedRow from '../../Global/helpers/animated-row'
export default function RecentArtists(): React.JSX.Element {
const recentArtistsInfiniteQuery = useRecentArtists()
@@ -47,14 +47,7 @@ export default function RecentArtists(): React.JSX.Element {
)
return recentArtistsInfiniteQuery.data ? (
<Animated.View
entering={FadeIn.easing(Easing.in(Easing.ease))}
exiting={FadeOut.easing(Easing.out(Easing.ease))}
layout={LinearTransition.springify()}
style={{
flex: 1,
}}
>
<AnimatedRow testID='home-recent-artists'>
<XStack alignItems='center' onPress={handleHeaderPress}>
<H5 marginLeft={'$2'}>Recent Artists</H5>
<Icon name='arrow-right' />
@@ -64,7 +57,7 @@ export default function RecentArtists(): React.JSX.Element {
data={recentArtistsInfiniteQuery.data.slice(0, horizontalItems)}
renderItem={renderItem}
/>
</Animated.View>
</AnimatedRow>
) : (
<></>
)

View File

@@ -11,8 +11,8 @@ import { useDisplayContext } from '../../../providers/Display/display-provider'
import { useNavigation } from '@react-navigation/native'
import HomeStackParamList from '../../../screens/Home/types'
import { useRecentlyPlayedTracks } from '../../../api/queries/recents'
import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated'
import { BaseItemDto, BaseItemKind } from '@jellyfin/sdk/lib/generated-client'
import AnimatedRow from '../../Global/helpers/animated-row'
export default function RecentlyPlayed(): React.JSX.Element {
const navigation = useNavigation<NativeStackNavigationProp<HomeStackParamList>>()
@@ -43,14 +43,7 @@ export default function RecentlyPlayed(): React.JSX.Element {
}
return tracksInfiniteQuery.data ? (
<Animated.View
entering={FadeIn.easing(Easing.in(Easing.ease))}
exiting={FadeOut.easing(Easing.out(Easing.ease))}
layout={LinearTransition.springify()}
style={{
flex: 1,
}}
>
<AnimatedRow testID='home-recently-played'>
<XStack
alignItems='center'
onPress={() => {
@@ -87,7 +80,7 @@ export default function RecentlyPlayed(): React.JSX.Element {
/>
)}
/>
</Animated.View>
</AnimatedRow>
) : (
<></>
)

View File

@@ -1,11 +1,8 @@
import React, { useState } from 'react'
import React, { useEffect, useState } from 'react'
import Input from '../Global/helpers/input'
import { H5, Text } from '../Global/helpers/text'
import ItemRow from '../Global/components/item-row'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { QueryKeys } from '../../enums/query-keys'
import { fetchSearchResults } from '../../api/queries/search'
import { useQuery } from '@tanstack/react-query'
import { getToken, H3, Spinner, YStack } from 'tamagui'
import Suggestions from './suggestions'
import { isEmpty } from 'lodash'
@@ -13,7 +10,6 @@ import HorizontalCardList from '../Global/components/horizontal-list'
import ItemCard from '../Global/components/item-card'
import SearchParamList from '../../screens/Search/types'
import { closeAllSwipeableRows } from '../Global/components/swipeable-row-registry'
import { getApi, getUser, useJellifyLibrary } from '../../stores'
import { FlashList } from '@shopify/flash-list'
import navigationRef from '../../../navigation'
import { StackActions } from '@react-navigation/native'
@@ -22,41 +18,35 @@ import Track from '../Global/components/Track'
import { pickRandomItemFromArray } from '../../utils/parsing/random'
import { SEARCH_PLACEHOLDERS } from '../../configs/placeholder.config'
import { formatArtistName } from '../../utils/formatting/artist-names'
import useSearchResults from '../../api/queries/search'
export default function Search({
navigation,
}: {
navigation: NativeStackNavigationProp<SearchParamList, 'SearchScreen'>
}): React.JSX.Element {
const api = getApi()
const user = getUser()
const [library] = useJellifyLibrary()
/**
* Raw text input value from the user, updates immediately as they type
*/
const [inputValue, setInputValue] = useState<string | undefined>(undefined)
/**
* Debounced search string that updates 500ms after the user stops typing, used to trigger the search query
* which is keyed off of this value for caching.
*/
const [searchString, setSearchString] = useState<string | undefined>(undefined)
const {
data: items,
refetch,
isFetching: fetchingResults,
} = useQuery({
queryKey: [QueryKeys.Search, library?.musicLibraryId, searchString],
queryFn: () => fetchSearchResults(api, user, library?.musicLibraryId, searchString),
})
useEffect(() => {
const timeout = setTimeout(() => {
setSearchString(inputValue || undefined)
}, 500)
return () => clearTimeout(timeout)
}, [inputValue])
const search = () => {
let timeout: ReturnType<typeof setTimeout>
return () => {
clearTimeout(timeout)
timeout = setTimeout(() => {
refetch()
}, 1000)
}
}
const { data: items, isFetching: fetchingResults } = useSearchResults(searchString)
const handleSearchStringUpdate = (value: string | undefined) => {
setSearchString(value)
search()
setInputValue(value || undefined)
}
const handleScrollBeginDrag = () => {
@@ -90,7 +80,7 @@ export default function Search({
<Input
placeholder={placeholder}
onChangeText={handleSearchStringUpdate}
value={searchString}
value={inputValue}
testID='search-input'
clearButtonMode='always'
/>