mirror of
https://github.com/Jellify-Music/App.git
synced 2026-04-16 14:33:31 -05:00
Nitro Fetch Axios Adapter, Artist Page Additions (#866)
* artist page tweaks, incorporate nitro fetch axios adapter for networking requests * fix pr otas * queue and playlist fixes * media info query staletime adjustments
This commit is contained in:
@@ -7,7 +7,7 @@ import { fetchMediaInfo } from './utils'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'
|
||||
import MediaInfoQueryKey from './keys'
|
||||
import { useApi } from '../../../stores'
|
||||
import { ONE_DAY } from '../../../constants/query-client'
|
||||
import { ONE_DAY, ONE_HOUR } from '../../../constants/query-client'
|
||||
|
||||
/**
|
||||
* A React hook that will retrieve the latest media info
|
||||
@@ -32,8 +32,8 @@ const useStreamedMediaInfo = (itemId: string | null | undefined) => {
|
||||
queryKey: MediaInfoQueryKey({ api, deviceProfile, itemId }),
|
||||
queryFn: () => fetchMediaInfo(api, deviceProfile, itemId),
|
||||
enabled: Boolean(api && deviceProfile && itemId),
|
||||
staleTime: ONE_DAY, // Only refetch when the user's device profile changes
|
||||
gcTime: ONE_DAY,
|
||||
staleTime: Infinity, // Only refetch when the user's device profile changes
|
||||
gcTime: Infinity,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ export const useDownloadedMediaInfo = (itemId: string | null | undefined) => {
|
||||
queryKey: MediaInfoQueryKey({ api, deviceProfile, itemId }),
|
||||
queryFn: () => fetchMediaInfo(api, deviceProfile, itemId),
|
||||
enabled: Boolean(api && deviceProfile && itemId),
|
||||
staleTime: ONE_DAY, // Only refetch when the user's device profile changes
|
||||
gcTime: ONE_DAY,
|
||||
staleTime: ONE_HOUR * 6, // Only refetch when the user's device profile changes
|
||||
gcTime: ONE_HOUR * 6,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useNavigation } from '@react-navigation/native'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { FlashList } from '@shopify/flash-list'
|
||||
import { YStack, H5 } from 'tamagui'
|
||||
import { ItemCard } from '../Global/components/item-card'
|
||||
import ItemCard from '../Global/components/item-card'
|
||||
|
||||
export default function AlbumTrackListFooter({ album }: { album: BaseItemDto }): React.JSX.Element {
|
||||
const navigation =
|
||||
|
||||
@@ -18,7 +18,7 @@ import useAddToPendingDownloads, { useIsDownloading } from '../../stores/network
|
||||
import { useIsDownloaded } from '../../api/queries/download'
|
||||
import AlbumTrackListFooter from './footer'
|
||||
import AlbumTrackListHeader from './header'
|
||||
import Animated, { FadeInUp, FadeOutDown, LinearTransition } from 'react-native-reanimated'
|
||||
import Animated, { FadeIn, FadeOutDown, LinearTransition } from 'react-native-reanimated'
|
||||
import { useStorageContext } from '../../providers/Storage'
|
||||
|
||||
/**
|
||||
@@ -69,7 +69,7 @@ export function Album({ album }: { album: BaseItemDto }): React.JSX.Element {
|
||||
{albumTrackList &&
|
||||
(isDownloaded ? (
|
||||
<Animated.View
|
||||
entering={FadeInUp.springify()}
|
||||
entering={FadeIn.springify()}
|
||||
exiting={FadeOutDown.springify()}
|
||||
layout={LinearTransition.springify()}
|
||||
>
|
||||
@@ -83,7 +83,7 @@ export function Album({ album }: { album: BaseItemDto }): React.JSX.Element {
|
||||
<Spinner justifyContent='center' color={'$neutral'} />
|
||||
) : (
|
||||
<Animated.View
|
||||
entering={FadeInUp.springify()}
|
||||
entering={FadeIn.springify()}
|
||||
exiting={FadeOutDown.springify()}
|
||||
layout={LinearTransition.springify()}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import React from 'react'
|
||||
import { useArtistContext } from '../../providers/Artist'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { BaseStackParamList } from '@/src/screens/types'
|
||||
@@ -16,43 +16,42 @@ export default function ArtistOverviewTab({
|
||||
}): React.JSX.Element {
|
||||
const { featuredOn, artist, albums } = useArtistContext()
|
||||
|
||||
const sections: SectionListData<BaseItemDto>[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
title: 'Albums',
|
||||
data: albums?.filter(({ ChildCount }) => (ChildCount ?? 0) > 6) ?? [],
|
||||
},
|
||||
{
|
||||
title: 'EPs',
|
||||
data:
|
||||
albums?.filter(
|
||||
({ ChildCount }) => (ChildCount ?? 0) <= 6 && (ChildCount ?? 0) >= 3,
|
||||
) ?? [],
|
||||
},
|
||||
{
|
||||
title: 'Singles',
|
||||
data: albums?.filter(({ ChildCount }) => (ChildCount ?? 0) === 1) ?? [],
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
data: albums?.filter(({ ChildCount }) => typeof ChildCount !== 'number') ?? [],
|
||||
},
|
||||
{
|
||||
title: 'Featured On',
|
||||
data: featuredOn ?? [],
|
||||
},
|
||||
]
|
||||
}, [artist, albums?.map(({ Id }) => Id)])
|
||||
const sections: SectionListData<BaseItemDto>[] = [
|
||||
{
|
||||
title: 'Albums',
|
||||
data: albums?.filter(({ ChildCount }) => (ChildCount ?? 0) > 6) ?? [],
|
||||
},
|
||||
{
|
||||
title: 'EPs',
|
||||
data:
|
||||
albums?.filter(
|
||||
({ ChildCount }) => (ChildCount ?? 0) <= 6 && (ChildCount ?? 0) >= 3,
|
||||
) ?? [],
|
||||
},
|
||||
{
|
||||
title: 'Singles',
|
||||
data: albums?.filter(({ ChildCount }) => (ChildCount ?? 0) === 1) ?? [],
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
data: albums?.filter(({ ChildCount }) => typeof ChildCount !== 'number') ?? [],
|
||||
},
|
||||
{
|
||||
title: 'Featured On',
|
||||
data: featuredOn ?? [],
|
||||
},
|
||||
]
|
||||
|
||||
const renderSectionHeader = useCallback(
|
||||
({ section }: { section: SectionListData<BaseItemDto, DefaultSectionT> }) =>
|
||||
section.data.length > 0 ? (
|
||||
<Text padding={'$3'} fontSize={'$6'} bold backgroundColor={'$background'}>
|
||||
{section.title}
|
||||
</Text>
|
||||
) : null,
|
||||
[],
|
||||
)
|
||||
const renderSectionHeader = ({
|
||||
section,
|
||||
}: {
|
||||
section: SectionListData<BaseItemDto, DefaultSectionT>
|
||||
}) =>
|
||||
section.data.length > 0 ? (
|
||||
<Text padding={'$2'} fontSize={'$6'} bold backgroundColor={'$background'}>
|
||||
{section.title}
|
||||
</Text>
|
||||
) : null
|
||||
|
||||
return (
|
||||
<SectionList
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import { MaterialTopTabBarProps } from '@react-navigation/material-top-tabs'
|
||||
import React from 'react'
|
||||
import { Square, XStack, YStack } from 'tamagui'
|
||||
import Icon from '../Global/components/icon'
|
||||
import { Text } from '../Global/helpers/text'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import useHapticFeedback from '../../hooks/use-haptic-feedback'
|
||||
import { ItemSortBy, SortOrder } from '@jellyfin/sdk/lib/generated-client'
|
||||
import { MaterialTopTabBar } from '@react-navigation/material-top-tabs'
|
||||
|
||||
interface ArtistTabBarProps extends MaterialTopTabBarProps {
|
||||
isFavorites: boolean
|
||||
setIsFavorites: (isFavorites: boolean) => void
|
||||
sortBy: ItemSortBy
|
||||
setSortBy: (sortBy: ItemSortBy) => void
|
||||
sortOrder: SortOrder
|
||||
setSortOrder: (sortOrder: SortOrder) => void
|
||||
}
|
||||
|
||||
export default function ArtistTabBar({
|
||||
isFavorites,
|
||||
setIsFavorites,
|
||||
sortBy,
|
||||
setSortBy,
|
||||
sortOrder,
|
||||
setSortOrder,
|
||||
...props
|
||||
}: ArtistTabBarProps) {
|
||||
const trigger = useHapticFeedback()
|
||||
const insets = useSafeAreaInsets()
|
||||
|
||||
return (
|
||||
<YStack>
|
||||
<MaterialTopTabBar {...props} />
|
||||
|
||||
{props.state.routes[props.state.index].name === 'Tracks' && (
|
||||
<XStack
|
||||
borderColor={'$borderColor'}
|
||||
alignContent={'flex-start'}
|
||||
justifyContent='flex-start'
|
||||
paddingHorizontal={'$1'}
|
||||
paddingVertical={'$2'}
|
||||
gap={'$2'}
|
||||
maxWidth={'80%'}
|
||||
>
|
||||
<XStack
|
||||
onPress={() => {
|
||||
trigger('impactLight')
|
||||
setIsFavorites(!isFavorites)
|
||||
}}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
>
|
||||
<Icon
|
||||
name={isFavorites ? 'heart' : 'heart-outline'}
|
||||
color={isFavorites ? '$primary' : '$borderColor'}
|
||||
/>
|
||||
|
||||
<Text color={isFavorites ? '$primary' : '$borderColor'}>
|
||||
{isFavorites ? 'Favorites' : 'All'}
|
||||
</Text>
|
||||
</XStack>
|
||||
|
||||
<XStack
|
||||
onPress={() => {
|
||||
trigger('impactLight')
|
||||
if (sortBy === ItemSortBy.DateCreated) {
|
||||
setSortBy(ItemSortBy.SortName)
|
||||
setSortOrder(SortOrder.Ascending)
|
||||
} else {
|
||||
setSortBy(ItemSortBy.DateCreated)
|
||||
setSortOrder(SortOrder.Descending)
|
||||
}
|
||||
}}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
>
|
||||
<Icon
|
||||
name={
|
||||
sortBy === ItemSortBy.DateCreated
|
||||
? 'calendar'
|
||||
: 'sort-alphabetical-ascending'
|
||||
}
|
||||
color={'$borderColor'}
|
||||
/>{' '}
|
||||
<Text color={'$borderColor'}>
|
||||
{sortBy === ItemSortBy.DateCreated ? 'Date Added' : 'A-Z'}
|
||||
</Text>
|
||||
</XStack>
|
||||
</XStack>
|
||||
)}
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ImageType } from '@jellyfin/sdk/lib/generated-client'
|
||||
import { XStack, YStack } from 'tamagui'
|
||||
import { Text, XStack, YStack } from 'tamagui'
|
||||
import ItemImage from '../Global/components/image'
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import { H5 } from '../Global/helpers/text'
|
||||
@@ -16,6 +16,8 @@ import { QueuingType } from '../../enums/queuing-type'
|
||||
import { useNetworkStatus } from '../../stores/network'
|
||||
import useStreamingDeviceProfile from '../../stores/device-profile'
|
||||
import { useApi } from '../../stores'
|
||||
import Icon from '../Global/components/icon'
|
||||
import useTracks from '../../api/queries/track'
|
||||
|
||||
export default function ArtistHeader(): React.JSX.Element {
|
||||
const { width } = useSafeAreaFrame()
|
||||
@@ -62,6 +64,8 @@ export default function ArtistHeader(): React.JSX.Element {
|
||||
}
|
||||
}
|
||||
|
||||
const [trackPageParams, tracksInfiniteQuery] = useTracks(artist.Id)
|
||||
|
||||
return (
|
||||
<YStack flex={1}>
|
||||
<ItemImage
|
||||
@@ -73,7 +77,7 @@ export default function ArtistHeader(): React.JSX.Element {
|
||||
imageOptions={{ maxWidth: width * 2, maxHeight: 640 }}
|
||||
/>
|
||||
|
||||
<YStack alignItems='center' paddingHorizontal={'$3'}>
|
||||
<YStack paddingHorizontal={'$2'}>
|
||||
<XStack alignItems='flex-end' justifyContent='flex-start' flex={1}>
|
||||
<XStack alignItems='center' flex={1} justifyContent='space-between'>
|
||||
<H5 flexGrow={1} fontWeight={'bold'}>
|
||||
@@ -90,10 +94,31 @@ export default function ArtistHeader(): React.JSX.Element {
|
||||
</XStack>
|
||||
|
||||
<XStack alignItems='center' justifyContent='flex-end' gap={'$3'} flex={1}>
|
||||
{/* <Icon name='shuffle' onPress={() => playArtist(true)} /> */}
|
||||
<IconButton circular name='play' onPress={playArtist} />
|
||||
<Icon
|
||||
small
|
||||
color='$primary'
|
||||
name='shuffle'
|
||||
onPress={() => playArtist(true)}
|
||||
/>
|
||||
<IconButton circular name='play' onPress={() => playArtist(false)} />
|
||||
</XStack>
|
||||
</XStack>
|
||||
|
||||
<XStack
|
||||
alignItems='center'
|
||||
flex={1}
|
||||
justifyContent='flex-start'
|
||||
marginVertical={'$2'}
|
||||
onPress={() =>
|
||||
navigation.push('Tracks', {
|
||||
tracksInfiniteQuery,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Text fontWeight={'bold'} fontSize={'$4'}>{`View Tracks`}</Text>
|
||||
|
||||
<Icon name='chevron-right' small />
|
||||
</XStack>
|
||||
</YStack>
|
||||
</YStack>
|
||||
)
|
||||
|
||||
@@ -1,71 +1,12 @@
|
||||
import React, { useState } from 'react'
|
||||
import React from 'react'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { BaseStackParamList } from '@/src/screens/types'
|
||||
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'
|
||||
import ArtistOverviewTab from './OverviewTab'
|
||||
import ArtistTracksTab from './TracksTab'
|
||||
import ArtistTabBar from './TabBar'
|
||||
import { ItemSortBy, SortOrder } from '@jellyfin/sdk/lib/generated-client'
|
||||
import { getTokenValue, useTheme } from 'tamagui'
|
||||
|
||||
const Tab = createMaterialTopTabNavigator()
|
||||
|
||||
export default function ArtistNavigation({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<BaseStackParamList>
|
||||
}): React.JSX.Element {
|
||||
const [isFavorites, setIsFavorites] = useState(false)
|
||||
const [sortBy, setSortBy] = useState<ItemSortBy>(ItemSortBy.SortName)
|
||||
const [sortOrder, setSortOrder] = useState<SortOrder>(SortOrder.Ascending)
|
||||
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<Tab.Navigator
|
||||
tabBar={(props) => (
|
||||
<ArtistTabBar
|
||||
{...props}
|
||||
isFavorites={isFavorites}
|
||||
setIsFavorites={setIsFavorites}
|
||||
sortBy={sortBy}
|
||||
setSortBy={setSortBy}
|
||||
sortOrder={sortOrder}
|
||||
setSortOrder={setSortOrder}
|
||||
/>
|
||||
)}
|
||||
screenOptions={{
|
||||
swipeEnabled: false,
|
||||
tabBarIndicatorStyle: {
|
||||
borderColor: theme.background.val,
|
||||
borderBottomWidth: getTokenValue('$2'),
|
||||
},
|
||||
tabBarActiveTintColor: theme.background.val,
|
||||
tabBarInactiveTintColor: theme.background50.val,
|
||||
tabBarStyle: {
|
||||
backgroundColor: theme.primary.val,
|
||||
},
|
||||
tabBarLabelStyle: {
|
||||
fontSize: 16,
|
||||
fontFamily: 'Figtree-Bold',
|
||||
},
|
||||
tabBarPressOpacity: 0.5,
|
||||
lazy: true, // Enable lazy loading to prevent all tabs from mounting simultaneously
|
||||
}}
|
||||
>
|
||||
<Tab.Screen name='Overview'>
|
||||
{() => <ArtistOverviewTab navigation={navigation} />}
|
||||
</Tab.Screen>
|
||||
<Tab.Screen name='Tracks'>
|
||||
{() => (
|
||||
<ArtistTracksTab
|
||||
navigation={navigation}
|
||||
isFavorites={isFavorites}
|
||||
sortBy={sortBy}
|
||||
sortOrder={sortOrder}
|
||||
/>
|
||||
)}
|
||||
</Tab.Screen>
|
||||
</Tab.Navigator>
|
||||
)
|
||||
return <ArtistOverviewTab navigation={navigation} />
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function SimilarArtists(): React.JSX.Element {
|
||||
return (
|
||||
<YStack flex={1}>
|
||||
<Text
|
||||
margin={'$3'}
|
||||
margin={'$2'}
|
||||
fontSize={'$6'}
|
||||
bold
|
||||
>{`Similar to ${artist.Name ?? 'Unknown Artist'}`}</Text>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import HorizontalCardList from '../../../components/Global/components/horizontal-list'
|
||||
import { ItemCard } from '../../../components/Global/components/item-card'
|
||||
import ItemCard from '../../../components/Global/components/item-card'
|
||||
import { H5, View, XStack } from 'tamagui'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
|
||||
@@ -2,7 +2,7 @@ import { H5, XStack } from 'tamagui'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import HorizontalCardList from '../../Global/components/horizontal-list'
|
||||
import { ItemCard } from '../../Global/components/item-card'
|
||||
import ItemCard from '../../Global/components/item-card'
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import DiscoverStackParamList from '../../../screens/Discover/types'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { H5, View, XStack } from 'tamagui'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import HorizontalCardList from '../../Global/components/horizontal-list'
|
||||
import { ItemCard } from '../../Global/components/item-card'
|
||||
import ItemCard from '../../Global/components/item-card'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import DiscoverStackParamList from '../../../screens/Discover/types'
|
||||
|
||||
@@ -57,8 +57,6 @@ export default function Icon({
|
||||
onPress={onPress}
|
||||
onPressIn={onPressIn}
|
||||
hitSlop={getTokenValue('$2.5')}
|
||||
width={size + getToken('$0.5')}
|
||||
height={size + getToken('$0.5')}
|
||||
flex={flex}
|
||||
>
|
||||
<MaterialDesignIcon
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { memo, useCallback, useEffect, useMemo } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import { CardProps as TamaguiCardProps } from 'tamagui'
|
||||
import { Card as TamaguiCard, View, YStack } from 'tamagui'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
@@ -23,7 +23,7 @@ interface CardProps extends TamaguiCardProps {
|
||||
*
|
||||
* @param props
|
||||
*/
|
||||
function ItemCardComponent({
|
||||
export default function ItemCard({
|
||||
caption,
|
||||
subCaption,
|
||||
item,
|
||||
@@ -41,22 +41,16 @@ function ItemCardComponent({
|
||||
if (item.Type === 'Audio') warmContext(item)
|
||||
}, [item.Id, item.Type, warmContext])
|
||||
|
||||
const hoverStyle = useMemo(() => (onPress ? { scale: 0.925 } : undefined), [onPress])
|
||||
const hoverStyle = onPress ? { scale: 0.925 } : undefined
|
||||
|
||||
const pressStyle = useMemo(() => (onPress ? { scale: 0.875 } : undefined), [onPress])
|
||||
const pressStyle = onPress ? { scale: 0.875 } : undefined
|
||||
|
||||
const handlePressIn = useCallback(
|
||||
() => (item.Type !== 'Audio' ? warmContext(item) : undefined),
|
||||
[item.Id, warmContext],
|
||||
)
|
||||
const handlePressIn = () => (item.Type !== 'Audio' ? warmContext(item) : undefined)
|
||||
|
||||
const background = useMemo(
|
||||
() => (
|
||||
<TamaguiCard.Background>
|
||||
<ItemImage item={item} circular={!squared} />
|
||||
</TamaguiCard.Background>
|
||||
),
|
||||
[item.Id, squared],
|
||||
const background = (
|
||||
<TamaguiCard.Background>
|
||||
<ItemImage item={item} circular={!squared} />
|
||||
</TamaguiCard.Background>
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -88,62 +82,41 @@ function ItemCardComponent({
|
||||
)
|
||||
}
|
||||
|
||||
const ItemCardComponentCaption = memo(
|
||||
function ItemCardComponentCaption({
|
||||
size,
|
||||
captionAlign = 'center',
|
||||
caption,
|
||||
subCaption,
|
||||
}: {
|
||||
size: string | number
|
||||
captionAlign: 'center' | 'left' | 'right'
|
||||
caption?: string | null | undefined
|
||||
subCaption?: string | null | undefined
|
||||
}): React.JSX.Element | null {
|
||||
if (!caption) return null
|
||||
function ItemCardComponentCaption({
|
||||
size,
|
||||
captionAlign = 'center',
|
||||
caption,
|
||||
subCaption,
|
||||
}: {
|
||||
size: string | number
|
||||
captionAlign: 'center' | 'left' | 'right'
|
||||
caption?: string | null | undefined
|
||||
subCaption?: string | null | undefined
|
||||
}): React.JSX.Element | null {
|
||||
if (!caption) return null
|
||||
|
||||
return (
|
||||
<YStack maxWidth={size}>
|
||||
return (
|
||||
<YStack maxWidth={size}>
|
||||
<Text
|
||||
bold
|
||||
lineBreakStrategyIOS='standard'
|
||||
width={size}
|
||||
numberOfLines={1}
|
||||
textAlign={captionAlign}
|
||||
>
|
||||
{caption}
|
||||
</Text>
|
||||
|
||||
{subCaption && (
|
||||
<Text
|
||||
bold
|
||||
lineBreakStrategyIOS='standard'
|
||||
width={size}
|
||||
numberOfLines={1}
|
||||
textAlign={captionAlign}
|
||||
>
|
||||
{caption}
|
||||
{subCaption}
|
||||
</Text>
|
||||
|
||||
{subCaption && (
|
||||
<Text
|
||||
lineBreakStrategyIOS='standard'
|
||||
width={size}
|
||||
numberOfLines={1}
|
||||
textAlign={captionAlign}
|
||||
>
|
||||
{subCaption}
|
||||
</Text>
|
||||
)}
|
||||
</YStack>
|
||||
)
|
||||
},
|
||||
(prevProps, nextProps) =>
|
||||
prevProps.size === nextProps.size &&
|
||||
prevProps.captionAlign === nextProps.captionAlign &&
|
||||
prevProps.caption === nextProps.caption &&
|
||||
prevProps.subCaption === nextProps.subCaption,
|
||||
)
|
||||
|
||||
export const ItemCard = React.memo(
|
||||
ItemCardComponent,
|
||||
(a, b) =>
|
||||
a.item.Id === b.item.Id &&
|
||||
a.item.Type === b.item.Type &&
|
||||
a.caption === b.caption &&
|
||||
a.subCaption === b.subCaption &&
|
||||
a.squared === b.squared &&
|
||||
a.size === b.size &&
|
||||
a.testId === b.testId &&
|
||||
!!a.onPress === !!b.onPress &&
|
||||
a.captionAlign === b.captionAlign,
|
||||
)
|
||||
)}
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import HorizontalCardList from '../../../components/Global/components/horizontal-list'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import React, { useCallback } from 'react'
|
||||
import { ItemCard } from '../../../components/Global/components/item-card'
|
||||
import ItemCard from '../../../components/Global/components/item-card'
|
||||
import { H5, XStack } from 'tamagui'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { useDisplayContext } from '../../../providers/Display/display-provider'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { H5, XStack } from 'tamagui'
|
||||
import HorizontalCardList from '../../../components/Global/components/horizontal-list'
|
||||
import { ItemCard } from '../../../components/Global/components/item-card'
|
||||
import ItemCard from '../../../components/Global/components/item-card'
|
||||
import { QueuingType } from '../../../enums/queuing-type'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { useLoadNewQueue } from '../../../providers/Player/hooks/mutations'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import { H5, View, XStack } from 'tamagui'
|
||||
import { RootStackParamList } from '../../../screens/types'
|
||||
import { ItemCard } from '../../Global/components/item-card'
|
||||
import ItemCard from '../../Global/components/item-card'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import HorizontalCardList from '../../../components/Global/components/horizontal-list'
|
||||
import Icon from '../../Global/components/icon'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { H5, XStack } from 'tamagui'
|
||||
import { ItemCard } from '../../Global/components/item-card'
|
||||
import ItemCard from '../../Global/components/item-card'
|
||||
import { RootStackParamList } from '../../../screens/types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { QueuingType } from '../../../enums/queuing-type'
|
||||
|
||||
@@ -2,8 +2,8 @@ import Icon from '../Global/components/icon'
|
||||
import Track from '../Global/components/track'
|
||||
import { RootStackParamList } from '../../screens/types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { ScrollView, XStack } from 'tamagui'
|
||||
import { useLayoutEffect, useCallback, useState } from 'react'
|
||||
import { ScrollView, Text, XStack } from 'tamagui'
|
||||
import { useLayoutEffect, useState } from 'react'
|
||||
import JellifyTrack from '../../types/JellifyTrack'
|
||||
import {
|
||||
useRemoveFromQueue,
|
||||
@@ -13,8 +13,9 @@ import {
|
||||
} from '../../providers/Player/hooks/mutations'
|
||||
import { usePlayerQueueStore, useQueueRef } from '../../stores/player/queue'
|
||||
import Sortable from 'react-native-sortables'
|
||||
import { RenderItemInfo } from 'react-native-sortables/dist/typescript/types'
|
||||
import { OrderChangeParams, RenderItemInfo } from 'react-native-sortables/dist/typescript/types'
|
||||
import { useReducedHapticsSetting } from '../../stores/settings/app'
|
||||
import uuid from 'react-native-uuid'
|
||||
|
||||
export default function Queue({
|
||||
navigation,
|
||||
@@ -35,51 +36,62 @@ export default function Queue({
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerRight: () => {
|
||||
return <Icon name='notification-clear-all' onPress={removeUpcomingTracks} />
|
||||
return (
|
||||
<XStack>
|
||||
<Text>Clear Upcoming</Text>
|
||||
<Icon
|
||||
name='notification-clear-all'
|
||||
onPress={async () => {
|
||||
await removeUpcomingTracks()
|
||||
setQueue(usePlayerQueueStore.getState().queue)
|
||||
}}
|
||||
/>
|
||||
</XStack>
|
||||
)
|
||||
},
|
||||
})
|
||||
}, [navigation, removeUpcomingTracks])
|
||||
|
||||
const keyExtractor = useCallback((item: JellifyTrack) => `${item.item.Id}`, [])
|
||||
const keyExtractor = (item: JellifyTrack) => item.item.Id ?? uuid.v4()
|
||||
|
||||
// Memoize renderItem function for better performance
|
||||
const renderItem = useCallback(
|
||||
({ item: queueItem, index }: RenderItemInfo<JellifyTrack>) => (
|
||||
<XStack alignItems='center' key={`${index}-${queueItem.item.Id}`}>
|
||||
<Sortable.Handle style={{ display: 'flex', flexShrink: 1 }}>
|
||||
<Icon name='drag' />
|
||||
</Sortable.Handle>
|
||||
const renderItem = ({ item: queueItem, index }: RenderItemInfo<JellifyTrack>) => (
|
||||
<XStack alignItems='center'>
|
||||
<Sortable.Handle style={{ display: 'flex', flexShrink: 1 }}>
|
||||
<Icon name='drag' />
|
||||
</Sortable.Handle>
|
||||
|
||||
<Sortable.Touchable
|
||||
onTap={() => skip(index)}
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<Track
|
||||
queue={queueRef ?? 'Recently Played'}
|
||||
track={queueItem.item}
|
||||
index={index}
|
||||
showArtwork
|
||||
testID={`queue-item-${index}`}
|
||||
isNested
|
||||
editing
|
||||
/>
|
||||
</Sortable.Touchable>
|
||||
<Sortable.Touchable
|
||||
onTap={() => skip(index)}
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<Track
|
||||
queue={queueRef ?? 'Recently Played'}
|
||||
track={queueItem.item}
|
||||
index={index}
|
||||
showArtwork
|
||||
testID={`queue-item-${index}`}
|
||||
isNested
|
||||
editing
|
||||
/>
|
||||
</Sortable.Touchable>
|
||||
|
||||
<Sortable.Touchable
|
||||
onTap={async () => {
|
||||
setQueue(queue.filter(({ item }) => item.Id !== queueItem.item.Id))
|
||||
await removeFromQueue(index)
|
||||
}}
|
||||
>
|
||||
<Icon name='close' color='$warning' />
|
||||
</Sortable.Touchable>
|
||||
</XStack>
|
||||
),
|
||||
[queueRef, skip, removeFromQueue],
|
||||
<Sortable.Touchable
|
||||
onTap={async () => {
|
||||
setQueue(queue.filter(({ item }) => item.Id !== queueItem.item.Id))
|
||||
await removeFromQueue(index)
|
||||
}}
|
||||
>
|
||||
<Icon name='close' color='$warning' />
|
||||
</Sortable.Touchable>
|
||||
</XStack>
|
||||
)
|
||||
|
||||
const handleReorder = async ({ fromIndex, toIndex }: OrderChangeParams) =>
|
||||
await reorderQueue({ fromIndex, toIndex })
|
||||
|
||||
return (
|
||||
<ScrollView flex={1} contentInsetAdjustmentBehavior='automatic'>
|
||||
<Sortable.Grid
|
||||
@@ -87,10 +99,8 @@ export default function Queue({
|
||||
columns={1}
|
||||
keyExtractor={keyExtractor}
|
||||
renderItem={renderItem}
|
||||
onOrderChange={reorderQueue}
|
||||
onDragEnd={({ data }) => {
|
||||
setQueue(data)
|
||||
}}
|
||||
onOrderChange={handleReorder}
|
||||
onDragEnd={({ data }) => setQueue(data)}
|
||||
overDrag='vertical'
|
||||
customHandle
|
||||
hapticsEnabled={!reducedHaptics}
|
||||
|
||||
@@ -151,69 +151,72 @@ export default function Playlist({
|
||||
|
||||
const handleDownload = () => addToDownloadQueue(playlistTracks ?? [])
|
||||
|
||||
const editModeActions = (
|
||||
<Animated.View
|
||||
entering={FadeIn.springify()}
|
||||
exiting={FadeOut.springify()}
|
||||
layout={LinearTransition.springify()}
|
||||
>
|
||||
<XStack gap={'$2'}>
|
||||
<Icon
|
||||
color={'$warning'}
|
||||
name='delete-sweep-outline' // otherwise use "delete-circle"
|
||||
onPress={() => {
|
||||
navigationRef.dispatch(
|
||||
StackActions.push('DeletePlaylist', {
|
||||
playlist,
|
||||
onDelete: navigation.goBack,
|
||||
}),
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
<Icon color='$neutral' name='close-circle-outline' onPress={handleCancel} />
|
||||
</XStack>
|
||||
</Animated.View>
|
||||
)
|
||||
|
||||
const downloadActions = (
|
||||
<XStack gap={'$2'}>
|
||||
{playlistTracks &&
|
||||
(isDownloaded ? (
|
||||
<Animated.View
|
||||
entering={FadeInUp.springify()}
|
||||
exiting={FadeOutDown.springify()}
|
||||
layout={LinearTransition.springify()}
|
||||
>
|
||||
<Icon color='$warning' name='broom' onPress={handleDeleteDownload} />
|
||||
</Animated.View>
|
||||
) : playlistDownloadPending ? (
|
||||
<Spinner justifyContent='center' color={'$neutral'} />
|
||||
) : (
|
||||
<Animated.View
|
||||
entering={FadeInUp.springify()}
|
||||
exiting={FadeOutDown.springify()}
|
||||
layout={LinearTransition.springify()}
|
||||
>
|
||||
<Icon
|
||||
color='$success'
|
||||
name='download-circle-outline'
|
||||
onPress={handleDownload}
|
||||
/>
|
||||
</Animated.View>
|
||||
))}
|
||||
</XStack>
|
||||
)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerRight: () => (
|
||||
<XStack gap={'$2'}>
|
||||
{playlistTracks &&
|
||||
(isDownloaded ? (
|
||||
<Animated.View
|
||||
entering={FadeInUp.springify()}
|
||||
exiting={FadeOutDown.springify()}
|
||||
layout={LinearTransition.springify()}
|
||||
>
|
||||
<Icon
|
||||
color='$warning'
|
||||
name='broom'
|
||||
onPress={handleDeleteDownload}
|
||||
/>
|
||||
</Animated.View>
|
||||
) : playlistDownloadPending ? (
|
||||
<Spinner justifyContent='center' color={'$neutral'} />
|
||||
) : (
|
||||
<Animated.View
|
||||
entering={FadeInUp.springify()}
|
||||
exiting={FadeOutDown.springify()}
|
||||
layout={LinearTransition.springify()}
|
||||
>
|
||||
<Icon
|
||||
color='$success'
|
||||
name='download-circle-outline'
|
||||
onPress={handleDownload}
|
||||
/>
|
||||
</Animated.View>
|
||||
))}
|
||||
{canEdit &&
|
||||
(editing ? (
|
||||
<Animated.View
|
||||
entering={FadeIn.springify()}
|
||||
exiting={FadeOut.springify()}
|
||||
layout={LinearTransition.springify()}
|
||||
>
|
||||
<XStack gap={'$2'}>
|
||||
<Icon
|
||||
color={'$warning'}
|
||||
name='delete-sweep-outline' // otherwise use "delete-circle"
|
||||
onPress={() => {
|
||||
navigationRef.dispatch(
|
||||
StackActions.push('DeletePlaylist', {
|
||||
playlist,
|
||||
onDelete: navigation.goBack,
|
||||
}),
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
<Icon
|
||||
color='$neutral'
|
||||
name='close-circle-outline'
|
||||
onPress={handleCancel}
|
||||
/>
|
||||
</XStack>
|
||||
</Animated.View>
|
||||
) : isUpdating || isPreparingEditMode ? (
|
||||
<Spinner color={isPreparingEditMode ? '$primary' : '$success'} />
|
||||
) : (
|
||||
{playlistTracks && !editing && downloadActions}
|
||||
{canEdit && (
|
||||
<XStack gap={'$2'}>
|
||||
{editing ? (
|
||||
editModeActions
|
||||
) : isUpdating || isPreparingEditMode ? (
|
||||
<Spinner color={isPreparingEditMode ? '$primary' : '$success'} />
|
||||
) : null}
|
||||
<Animated.View
|
||||
entering={FadeIn.springify()}
|
||||
exiting={FadeOut.springify()}
|
||||
@@ -233,8 +236,8 @@ export default function Playlist({
|
||||
}
|
||||
/>
|
||||
</Animated.View>
|
||||
))}
|
||||
)
|
||||
</XStack>
|
||||
)}
|
||||
</XStack>
|
||||
),
|
||||
})
|
||||
|
||||
@@ -7,12 +7,11 @@ import { QueryKeys } from '../../enums/query-keys'
|
||||
import { fetchSearchResults } from '../../api/queries/search'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { FlatList } from 'react-native'
|
||||
import { fetchSearchSuggestions } from '../../api/queries/suggestions/utils/suggestions'
|
||||
import { getToken, H3, Separator, Spinner, YStack } from 'tamagui'
|
||||
import Suggestions from './suggestions'
|
||||
import { isEmpty } from 'lodash'
|
||||
import HorizontalCardList from '../Global/components/horizontal-list'
|
||||
import { ItemCard } from '../Global/components/item-card'
|
||||
import ItemCard from '../Global/components/item-card'
|
||||
import SearchParamList from '../../screens/Search/types'
|
||||
import { closeAllSwipeableRows } from '../Global/components/swipeable-row-registry'
|
||||
import { useApi, useJellifyLibrary, useJellifyUser } from '../../stores'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import ItemRow from '../Global/components/item-row'
|
||||
import { Text } from '../Global/helpers/text'
|
||||
import { H5, Separator, Spinner, YStack } from 'tamagui'
|
||||
import { ItemCard } from '../Global/components/item-card'
|
||||
import ItemCard from '../Global/components/item-card'
|
||||
import HorizontalCardList from '../Global/components/horizontal-list'
|
||||
import { FlashList } from '@shopify/flash-list'
|
||||
import SearchParamList from '../../screens/Search/types'
|
||||
|
||||
@@ -1,4 +1,29 @@
|
||||
import axios from 'axios'
|
||||
import axios, { AxiosAdapter } from 'axios'
|
||||
import { fetch } from 'react-native-nitro-fetch'
|
||||
|
||||
const nitroAxiosAdapter: AxiosAdapter = async (config) => {
|
||||
const response = await fetch(config.url!, {
|
||||
method: config.method?.toUpperCase(),
|
||||
headers: config.headers,
|
||||
body: config.data,
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
const headers: Record<string, string> = {}
|
||||
response.headers.forEach((value, key) => {
|
||||
headers[key] = value
|
||||
})
|
||||
|
||||
return {
|
||||
data,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers,
|
||||
config,
|
||||
request: null,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Axios instance for making HTTP requests.
|
||||
@@ -7,6 +32,7 @@ import axios from 'axios'
|
||||
*/
|
||||
const AXIOS_INSTANCE = axios.create({
|
||||
timeout: 60000,
|
||||
adapter: nitroAxiosAdapter,
|
||||
})
|
||||
|
||||
export default AXIOS_INSTANCE
|
||||
|
||||
@@ -226,9 +226,11 @@ export const useRemoveFromQueue = () => {
|
||||
return async (index: number) => {
|
||||
trigger('impactMedium')
|
||||
TrackPlayer.remove([index])
|
||||
const newQueue = await TrackPlayer.getQueue()
|
||||
|
||||
usePlayerQueueStore.getState().setQueue(newQueue as JellifyTrack[])
|
||||
const prevQueue = usePlayerQueueStore.getState().queue
|
||||
const newQueue = prevQueue.filter((_, i) => i !== index)
|
||||
|
||||
usePlayerQueueStore.getState().setQueue(newQueue)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,9 +246,15 @@ export const useRemoveUpcomingTracks = () => {
|
||||
export const useReorderQueue = () => {
|
||||
return async ({ fromIndex, toIndex }: QueueOrderMutation) => {
|
||||
await TrackPlayer.move(fromIndex, toIndex)
|
||||
const newQueue = await TrackPlayer.getQueue()
|
||||
|
||||
usePlayerQueueStore.getState().setQueue(newQueue as JellifyTrack[])
|
||||
const queue = usePlayerQueueStore.getState().queue
|
||||
|
||||
const itemToMove = queue[fromIndex]
|
||||
const newQueue = [...queue]
|
||||
newQueue.splice(fromIndex, 1)
|
||||
newQueue.splice(toIndex, 0, itemToMove)
|
||||
|
||||
usePlayerQueueStore.getState().setQueue(newQueue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import ArtistNavigation from '../../components/Artist'
|
||||
import { ArtistProvider } from '../../providers/Artist'
|
||||
import { RouteProp } from '@react-navigation/native'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { BaseStackParamList } from '../types'
|
||||
import { ArtistProvider } from '../../providers/Artist'
|
||||
import ArtistNavigation from '../../components/Artist'
|
||||
|
||||
export function ArtistScreen({
|
||||
export default function ArtistScreen({
|
||||
route,
|
||||
navigation,
|
||||
}: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import Index from '../../components/Discover/component'
|
||||
import AlbumScreen from '../Album'
|
||||
import { ArtistScreen } from '../Artist'
|
||||
import ArtistScreen from '../Artist'
|
||||
import { getTokenValue, useTheme } from 'tamagui'
|
||||
import RecentlyAdded from './albums'
|
||||
import PublicPlaylists from './playlists'
|
||||
@@ -10,6 +10,7 @@ import SuggestedArtists from './artists'
|
||||
import DiscoverStackParamList from './types'
|
||||
import InstantMix from '../../components/InstantMix/component'
|
||||
import { getItemName } from '../../utils/text'
|
||||
import TracksScreen from '../Tracks'
|
||||
|
||||
export const DiscoverStack = createNativeStackNavigator<DiscoverStackParamList>()
|
||||
|
||||
@@ -101,6 +102,8 @@ export function Discover(): React.JSX.Element {
|
||||
headerTitle: `${getItemName(route.params.item)} Mix`,
|
||||
})}
|
||||
/>
|
||||
|
||||
<DiscoverStack.Screen name='Tracks' component={TracksScreen} />
|
||||
</DiscoverStack.Navigator>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import _ from 'lodash'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import { PlaylistScreen } from '../Playlist'
|
||||
import { Home as HomeComponent } from '../../components/Home'
|
||||
import { ArtistScreen } from '../Artist'
|
||||
import ArtistScreen from '../Artist'
|
||||
import { getTokenValue, useTheme } from 'tamagui'
|
||||
import HomeArtistsScreen from './artists'
|
||||
import HomeTracksScreen from './tracks'
|
||||
@@ -10,6 +10,7 @@ import AlbumScreen from '../Album'
|
||||
import HomeStackParamList from './types'
|
||||
import InstantMix from '../../components/InstantMix/component'
|
||||
import { getItemName } from '../../utils/text'
|
||||
import TracksScreen from '../Tracks'
|
||||
|
||||
const HomeStack = createNativeStackNavigator<HomeStackParamList>()
|
||||
|
||||
@@ -99,6 +100,8 @@ export default function Home(): React.JSX.Element {
|
||||
headerTitle: `${getItemName(route.params.item)} Mix`,
|
||||
})}
|
||||
/>
|
||||
|
||||
<HomeStack.Screen name='Tracks' component={TracksScreen} />
|
||||
</HomeStack.Group>
|
||||
</HomeStack.Navigator>
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import Library from '../../components/Library/component'
|
||||
import { PlaylistScreen } from '../Playlist'
|
||||
import AddPlaylist from './add-playlist'
|
||||
import { ArtistScreen } from '../Artist'
|
||||
import ArtistScreen from '../Artist'
|
||||
import { useTheme } from 'tamagui'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import AlbumScreen from '../Album'
|
||||
@@ -10,6 +10,7 @@ import LibraryStackParamList from './types'
|
||||
import InstantMix from '../../components/InstantMix/component'
|
||||
import { getItemName } from '../../utils/text'
|
||||
import { Platform } from 'react-native'
|
||||
import TracksScreen from '../Tracks'
|
||||
|
||||
const LibraryStack = createNativeStackNavigator<LibraryStackParamList>()
|
||||
|
||||
@@ -81,6 +82,8 @@ export default function LibraryScreen(): React.JSX.Element {
|
||||
sheetAllowedDetents: Platform.OS === 'ios' ? 'fitToContents' : [0.5],
|
||||
}}
|
||||
/>
|
||||
|
||||
<LibraryStack.Screen name='Tracks' component={TracksScreen} />
|
||||
</LibraryStack.Navigator>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import { ArtistScreen } from '../Artist'
|
||||
import ArtistScreen from '../Artist'
|
||||
import AlbumScreen from '../Album'
|
||||
import { PlaylistScreen } from '../Playlist'
|
||||
import { getTokenValue, useTheme } from 'tamagui'
|
||||
@@ -7,6 +7,7 @@ import Search from '../../components/Search'
|
||||
import SearchParamList from './types'
|
||||
import InstantMix from '../../components/InstantMix/component'
|
||||
import { getItemName } from '../../utils/text'
|
||||
import TracksScreen from '../Tracks'
|
||||
|
||||
const Stack = createNativeStackNavigator<SearchParamList>()
|
||||
|
||||
@@ -68,6 +69,8 @@ export default function SearchStack(): React.JSX.Element {
|
||||
headerTitle: `${getItemName(route.params.item)} Mix`,
|
||||
})}
|
||||
/>
|
||||
|
||||
<Stack.Screen name='Tracks' component={TracksScreen} />
|
||||
</Stack.Navigator>
|
||||
)
|
||||
}
|
||||
|
||||
2
src/screens/types.d.ts
vendored
2
src/screens/types.d.ts
vendored
@@ -36,7 +36,7 @@ export type BaseStackParamList = {
|
||||
}
|
||||
|
||||
Tracks: {
|
||||
tracksInfiniteQuery: UseInfiniteQueryResult<BaseItemDto[], Error>
|
||||
tracksInfiniteQuery: UseInfiniteQueryResult<(string | number | BaseItemDto)[], Error>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user