lots of context prefetching and favorites prefetching improvements

This commit is contained in:
Violet Caulfield
2025-08-20 21:33:04 -05:00
parent f368ba77d6
commit 1d0641f2ae
19 changed files with 165 additions and 266 deletions

View File

@@ -7,7 +7,7 @@ import { TamaguiProvider } from 'tamagui'
import { Platform, useColorScheme } from 'react-native'
import jellifyConfig from './tamagui.config'
import { clientPersister } from './src/constants/storage'
import { queryClient } from './src/constants/query-client'
import { ONE_DAY, queryClient } from './src/constants/query-client'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import TrackPlayer, {
AndroidAudioContentType,
@@ -110,10 +110,9 @@ function Container({ playerIsReady }: { playerIsReady: boolean }): React.JSX.Ele
persister: clientPersister,
/**
* Infinity, since data can remain the
* same forever on the server
* Maximum query data age of one day
*/
maxAge: Infinity,
maxAge: ONE_DAY,
}}
>
<GestureHandlerRootView>

View File

@@ -50,11 +50,6 @@ const QueryConfig = {
width: 1000,
format: ImageFormat.Jpg,
},
staleTime: {
oneDay: 1000 * 60 * 60 * 24, // 1 Day
oneWeek: 1000 * 60 * 60 * 24 * 7, // 7 Days
oneFortnight: 1000 * 60 * 60 * 24 * 7 * 14, // 14 Days
},
}
export default QueryConfig

View File

@@ -168,6 +168,7 @@ export default function Artists({
if (artistsInfiniteQuery.hasNextPage && !artistsInfiniteQuery.isFetching)
artistsInfiniteQuery.fetchNextPage()
}}
removeClippedSubviews
/>
{showAlphabeticalSelector && artistPageParams && (

View File

@@ -1,24 +1,22 @@
import { BaseItemDto, BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models'
import { getToken, getTokenValue, ListItem, ScrollView, View, YGroup, ZStack } from 'tamagui'
import { getToken, ListItem, ScrollView, View, YGroup } from 'tamagui'
import { BaseStackParamList, RootStackParamList } from '../../screens/types'
import { Text } from '../Global/helpers/text'
import FavoriteContextMenuRow from '../Global/components/favorite-context-menu-row'
import { Blurhash } from 'react-native-blurhash'
import { getPrimaryBlurhashFromDto } from '../../utils/blurhash'
import { Platform, useColorScheme } from 'react-native'
import { useColorScheme } from 'react-native'
import { useThemeSettingContext } from '../../providers/Settings'
import LinearGradient from 'react-native-linear-gradient'
import Icon from '../Global/components/icon'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { useQuery } from '@tanstack/react-query'
import { QueryKeys } from '../../enums/query-keys'
import { fetchAlbumDiscs, fetchItem, fetchItems } from '../../api/queries/item'
import { fetchAlbumDiscs, fetchItem } from '../../api/queries/item'
import { useJellifyContext } from '../../providers'
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api'
import { useAddToQueueContext } from '../../providers/Player/queue'
import { AddToQueueMutation } from '../../providers/Player/interfaces'
import { QueuingType } from '../../enums/queuing-type'
import { useCallback, useMemo } from 'react'
import { useCallback, useEffect, useMemo } from 'react'
import navigationRef from '../../../navigation'
import { goToAlbumFromContextSheet, goToArtistFromContextSheet } from './utils/navigation'
import { getItemName } from '../../utils/text'
@@ -27,6 +25,7 @@ import { StackActions } from '@react-navigation/native'
import TextTicker from 'react-native-text-ticker'
import { TextTickerConfig } from '../Player/component.config'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { trigger } from 'react-native-haptic-feedback'
type StackNavigation = Pick<NativeStackNavigationProp<BaseStackParamList>, 'navigate' | 'dispatch'>
@@ -47,8 +46,6 @@ export default function ItemContext({ item, stackNavigation }: ContextProps): Re
const isTrack = item.Type === BaseItemKind.Audio
const isPlaylist = item.Type === BaseItemKind.Playlist
const itemArtists = item.ArtistItems ?? []
const { data: album } = useQuery({
queryKey: [QueryKeys.Album, item.AlbumId],
queryFn: () => fetchItem(api, item.AlbumId!),
@@ -77,7 +74,7 @@ export default function ItemContext({ item, stackNavigation }: ContextProps): Re
const renderAddToPlaylistRow = isTrack
const renderViewAlbumRow = useMemo(() => isAlbum || (isTrack && album), [album, item])
const renderViewAlbumRow = isAlbum || (isTrack && album)
const artistIds = !isPlaylist
? isArtist
@@ -87,24 +84,22 @@ export default function ItemContext({ item, stackNavigation }: ContextProps): Re
: []
: []
return (
<ScrollView>
<YGroup unstyled marginBottom={bottom}>
<FavoriteContextMenuRow item={item} />
{renderAddToQueueRow && (
<AddToQueueMenuRow
tracks={
isTrack
const itemTracks = isTrack
? [item]
: isAlbum && discs
? discs.flatMap((data) => data.data)
: isPlaylist && tracks
? tracks
: []
}
/>
)}
useEffect(() => trigger('impactLight'), [item?.Id])
return (
<ScrollView>
<YGroup unstyled marginBottom={bottom}>
<FavoriteContextMenuRow item={item} />
{renderAddToQueueRow && <AddToQueueMenuRow tracks={itemTracks} />}
{renderAddToPlaylistRow && <AddToPlaylistRow track={item} />}

View File

@@ -1,14 +1,15 @@
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import React, { useEffect, useState } from 'react'
import React from 'react'
import Icon from './icon'
import { useQuery } from '@tanstack/react-query'
import { isUndefined } from 'lodash'
import { getTokens, Spinner } from 'tamagui'
import { Spinner } from 'tamagui'
import { QueryKeys } from '../../../enums/query-keys'
import { fetchUserData } from '../../../api/queries/favorites'
import { useJellifyUserDataContext } from '../../../providers/UserData'
import { useJellifyContext } from '../../../providers'
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
import { ONE_HOUR } from '../../../constants/query-client'
interface SetFavoriteMutation {
item: BaseItemDto
@@ -21,26 +22,21 @@ export default function FavoriteButton({
item: BaseItemDto
onToggle?: () => void
}): React.JSX.Element {
const [isFavorite, setFavorite] = useState<boolean>(isFavoriteItem(item))
const { api, user } = useJellifyContext()
const { toggleFavorite } = useJellifyUserDataContext()
const { data, isFetching, refetch } = useQuery({
queryKey: [QueryKeys.UserData, item.Id!],
const {
data: isFavorite,
isFetching,
refetch,
} = useQuery({
queryKey: [QueryKeys.UserData, item.Id],
queryFn: () => fetchUserData(api, user, item.Id!),
staleTime: 1000 * 60 * 60 * 1, // 1 hour,
select: (data) => typeof data === 'object' && data.IsFavorite,
staleTime: ONE_HOUR,
})
useEffect(() => {
refetch()
}, [item])
useEffect(() => {
if (data) setFavorite(data.IsFavorite ?? false)
}, [data])
return isFetching && isUndefined(item.UserData) ? (
return isFetching ? (
<Spinner alignSelf='center' />
) : isFavorite ? (
<Animated.View entering={FadeIn} exiting={FadeOut}>
@@ -50,7 +46,6 @@ export default function FavoriteButton({
onPress={() =>
toggleFavorite(isFavorite, {
item,
setFavorite,
onToggle,
})
}
@@ -62,9 +57,8 @@ export default function FavoriteButton({
name={'heart-outline'}
color={'$primary'}
onPress={() =>
toggleFavorite(isFavorite, {
toggleFavorite(!!isFavorite, {
item,
setFavorite,
onToggle,
})
}

View File

@@ -3,31 +3,24 @@ import { useQuery } from '@tanstack/react-query'
import { QueryKeys } from '../../../enums/query-keys'
import { fetchUserData } from '../../../api/queries/favorites'
import { useJellifyContext } from '../../../providers'
import { getToken, ListItem, XStack } from 'tamagui'
import { ListItem, XStack } from 'tamagui'
import Icon from './icon'
import { useJellifyUserDataContext } from '../../../providers/UserData'
import { useEffect, useState } from 'react'
import { Text } from '../helpers/text'
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
import { ONE_HOUR } from '../../../constants/query-client'
export default function FavoriteContextMenuRow({ item }: { item: BaseItemDto }): React.JSX.Element {
const { api, user } = useJellifyContext()
const { toggleFavorite } = useJellifyUserDataContext()
const { data: userData, refetch } = useQuery({
const { data: isFavorite, refetch } = useQuery({
queryKey: [QueryKeys.UserData, item.Id],
queryFn: () => fetchUserData(api, user, item.Id!),
staleTime: 1000 * 60 * 60 * 1, // 1 hour,
select: (data) => typeof data === 'object' && data.IsFavorite,
staleTime: ONE_HOUR,
})
const [isFavorite, setIsFavorite] = useState<boolean>(
userData?.IsFavorite ?? item.UserData?.IsFavorite ?? false,
)
useEffect(() => {
setIsFavorite(userData?.IsFavorite ?? false)
}, [userData])
return isFavorite ? (
<ListItem
animation={'quick'}
@@ -36,7 +29,6 @@ export default function FavoriteContextMenuRow({ item }: { item: BaseItemDto }):
onPress={() => {
toggleFavorite(isFavorite, {
item,
setFavorite: setIsFavorite,
onToggle: () => refetch(),
})
}}
@@ -47,12 +39,10 @@ export default function FavoriteContextMenuRow({ item }: { item: BaseItemDto }):
exiting={FadeOut}
key={`${item.Id}-remove-favorite-row`}
>
<XStack alignContent='center' justifyContent='flex-start' gap={'$3'}>
<XStack alignItems='center' justifyContent='flex-start' gap={'$2'}>
<Icon name={'heart'} small color={'$primary'} />
<Text marginTop={'$2'} bold>
Remove from favorites
</Text>
<Text bold>Remove from favorites</Text>
</XStack>
</Animated.View>
</ListItem>
@@ -63,9 +53,8 @@ export default function FavoriteContextMenuRow({ item }: { item: BaseItemDto }):
justifyContent='flex-start'
gap={'$2'}
onPress={() => {
toggleFavorite(isFavorite, {
toggleFavorite(!!isFavorite, {
item,
setFavorite: setIsFavorite,
onToggle: () => refetch(),
})
}}

View File

@@ -4,7 +4,7 @@ import Icon from './icon'
import { useQuery } from '@tanstack/react-query'
import { QueryKeys } from '../../../enums/query-keys'
import { fetchUserData } from '../../../api/queries/favorites'
import { useEffect, useState, memo } from 'react'
import { memo } from 'react'
import { useJellifyContext } from '../../../providers'
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
@@ -16,23 +16,15 @@ import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
* @returns A React component that displays a favorite icon for a given item.
*/
function FavoriteIcon({ item }: { item: BaseItemDto }): React.JSX.Element {
const [isFavorite, setIsFavorite] = useState<boolean>(item.UserData?.IsFavorite ?? false)
const { api, user } = useJellifyContext()
const { data: userData, isPending } = useQuery({
queryKey: [QueryKeys.UserData, item.Id!],
const { data: isFavorite } = useQuery({
queryKey: [QueryKeys.UserData, item.Id],
queryFn: () => fetchUserData(api, user, item.Id!),
staleTime: 1000 * 60 * 5, // 5 minutes,
select: (data) => typeof data === 'object' && data.IsFavorite,
enabled: !!api && !!user && !!item.Id, // Only run if we have the required data
})
useEffect(() => {
if (!isPending && userData !== undefined) {
setIsFavorite(userData?.IsFavorite ?? false)
}
}, [userData, isPending])
return isFavorite ? (
<Animated.View entering={FadeIn} exiting={FadeOut}>
<Icon small name='heart' color={'$primary'} flex={1} />

View File

@@ -20,7 +20,6 @@ export default function InstantMixButton({
const { data, isFetching, refetch } = useQuery({
queryKey: [QueryKeys.InstantMix, item.Id!],
queryFn: () => fetchInstantMixFromItem(api, user, item),
staleTime: 1000 * 60 * 60 * 24, // 24 hours
})
return data ? (

View File

@@ -2,28 +2,19 @@ import React, { useMemo, useCallback } from 'react'
import { getToken, Theme, useTheme, XStack, YStack } from 'tamagui'
import { Text } from '../helpers/text'
import { RunTimeTicks } from '../helpers/time-codes'
import { BaseItemDto, BaseItemKind, ImageType } from '@jellyfin/sdk/lib/generated-client/models'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import Icon from './icon'
import { QueuingType } from '../../../enums/queuing-type'
import { Queue } from '../../../player/types/queue-item'
import FavoriteIcon from './favorite-icon'
import FastImage from 'react-native-fast-image'
import { getImageApi } from '@jellyfin/sdk/lib/utils/api'
import { networkStatusTypes } from '../../../components/Network/internetConnectionWatcher'
import { useNetworkContext } from '../../../providers/Network'
import { useLoadQueueContext, usePlayQueueContext } from '../../../providers/Player/queue'
import { useJellifyContext } from '../../../providers'
import DownloadedIcon from './downloaded-icon'
import { useQuery } from '@tanstack/react-query'
import { QueryKeys } from '../../../enums/query-keys'
import { fetchMediaInfo } from '../../../api/queries/media'
import { useStreamingQualityContext } from '../../../providers/Settings'
import { getQualityParams } from '../../../utils/mappings'
import { useNowPlayingContext } from '../../../providers/Player'
import navigationRef from '../../../../navigation'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { BaseStackParamList } from '../../../screens/types'
import { fetchItem } from '../../../api/queries/item'
import ItemImage from './image'
import { ItemProvider } from '../../../providers/Item'
@@ -61,12 +52,10 @@ export default function Track({
}: TrackProps): React.JSX.Element {
const theme = useTheme()
const { api, user } = useJellifyContext()
const nowPlaying = useNowPlayingContext()
const playQueue = usePlayQueueContext()
const useLoadNewQueue = useLoadQueueContext()
const { downloadedTracks, networkStatus } = useNetworkContext()
const streamingQuality = useStreamingQualityContext()
// Memoize expensive computations
const isPlaying = useMemo(
@@ -129,29 +118,6 @@ export default function Track({
}
}, [showRemove, onRemove, track, isNested])
// Only fetch media info if needed (for streaming)
useQuery({
queryKey: [QueryKeys.MediaSources, streamingQuality, track.Id],
queryFn: () => fetchMediaInfo(api, user, getQualityParams(streamingQuality), track.Id!),
staleTime: Infinity, // Don't refetch media info unless the user changes the quality
enabled: !isDownloaded, // Only fetch if not downloaded
})
// Fire query for fetching the track's media sources
useQuery({
queryKey: [QueryKeys.MediaSources, streamingQuality, track.Id],
queryFn: () => fetchMediaInfo(api, user, getQualityParams(streamingQuality), track.Id!),
staleTime: Infinity, // Don't refetch media info unless the user changes the quality
enabled: track.Type === 'Audio',
})
// Fire query for fetching the track's album
useQuery({
queryKey: [QueryKeys.Album, track.AlbumId],
queryFn: () => fetchItem(api, track.AlbumId!),
enabled: track.Type === BaseItemKind.Audio && !!track.AlbumId,
})
// Memoize text color to prevent recalculation
const textColor = useMemo(() => {
if (isPlaying) return theme.primary.val

View File

@@ -1,5 +1,9 @@
import { QueryClient } from '@tanstack/react-query'
export const ONE_MINUTE = 1000 * 60
export const ONE_HOUR = ONE_MINUTE * 60
export const ONE_DAY = ONE_HOUR * 24
/**
* A global instance of the Tanstack React Query client
*
@@ -16,18 +20,18 @@ export const queryClient = new QueryClient({
/**
* This needs to be set equal to or higher than the `maxAge` set in `../App.tsx`
*
* Because data can remain on the server forever, the `maxAge` is set to `Infinity`
* Because we want to preserve hybrid network functionality, the `maxAge` is set to {@link ONE_DAY}
*
* Therefore, this also needs to be set to `Infinity`, disabling garbage collection
* Therefore, this also needs to be set to {@link ONE_DAY}
*
* @see https://tanstack.com/query/latest/docs/framework/react/plugins/persistQueryClient#how-it-works
*/
gcTime: Infinity,
gcTime: ONE_DAY,
/**
* 1 hour as a default - reduced from 2 hours for better battery usage
*/
staleTime: 1000 * 60 * 60, // 1 hour
staleTime: ONE_HOUR, // 1 hour
retry(failureCount: number, error: Error) {
if (failureCount > 2) return false

View File

@@ -1,14 +1,6 @@
import React, {
createContext,
ReactNode,
useCallback,
useContext,
useEffect,
useState,
} from 'react'
import React, { createContext, ReactNode, useCallback, useContext, useState } from 'react'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import {
InfiniteData,
InfiniteQueryObserverResult,
useInfiniteQuery,
UseInfiniteQueryResult,
@@ -19,7 +11,6 @@ import { queryClient } from '../../constants/query-client'
import QueryConfig from '../../api/queries/query.config'
import { fetchFrequentlyPlayed, fetchFrequentlyPlayedArtists } from '../../api/queries/frequents'
import { useJellifyContext } from '..'
import { useIsFocused } from '@react-navigation/native'
interface HomeContext {
refreshing: boolean
onRefresh: () => void
@@ -44,12 +35,6 @@ const HomeContextInitializer = () => {
const { api, library, user } = useJellifyContext()
const [refreshing, setRefreshing] = useState<boolean>(false)
const isFocused = useIsFocused()
useEffect(() => {
console.debug(`Home focused: ${isFocused}`)
}, [isFocused])
const {
data: recentTracks,
isFetching: isFetchingRecentTracks,

View File

@@ -1,5 +1,5 @@
import { BaseItemDto, BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models'
import { createContext, ReactNode, useMemo } from 'react'
import { createContext, ReactNode, useEffect } from 'react'
import { useQuery } from '@tanstack/react-query'
import { QueryKeys } from '../../enums/query-keys'
import { fetchMediaInfo } from '../../api/queries/media'
@@ -9,6 +9,8 @@ import { getQualityParams } from '../../utils/mappings'
import { fetchAlbumDiscs, fetchItem } from '../../api/queries/item'
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api'
import { ItemArtistProvider } from './item-artists'
import { queryClient } from '../../constants/query-client'
import { fetchUserData } from '../../api/queries/favorites'
interface ItemContext {
item: BaseItemDto
@@ -45,18 +47,20 @@ export const ItemProvider: ({ item, children }: ItemProviderProps) => React.JSX.
const streamingQuality = useStreamingQualityContext()
const { Id, Type, AlbumId, ArtistItems } = item
const { Id, Type, AlbumId, ArtistItems, UserData } = item
const artistIds = ArtistItems?.map(({ Id }) => Id) ?? []
useEffect(() => {
// Fail fast if we don't have an Item ID to work with
if (!Id) return
/**
* Fetch and cache the media sources if this item is a track
*/
useQuery({
if (Type === BaseItemKind.Audio)
queryClient.ensureQueryData({
queryKey: [QueryKeys.MediaSources, streamingQuality, Id],
queryFn: () => fetchMediaInfo(api, user, getQualityParams(streamingQuality), Id!),
staleTime: Infinity, // Don't refetch media info unless the user changes the quality
enabled: !!Id && Type === BaseItemKind.Audio,
})
/**
@@ -64,21 +68,18 @@ export const ItemProvider: ({ item, children }: ItemProviderProps) => React.JSX.
*
* Referenced later in the context sheet
*/
useQuery({
queryKey: [QueryKeys.ArtistById, Id],
queryFn: () => item,
enabled: !!Id && Type === BaseItemKind.MusicArtist,
})
if (Type === BaseItemKind.MusicArtist)
queryClient.setQueryData([QueryKeys.ArtistById, Id], item)
/**
* Fire query for a track's album...
*
* Referenced later in the context sheet
*/
useQuery({
if (!!AlbumId && Type === BaseItemKind.Audio)
queryClient.ensureQueryData({
queryKey: [QueryKeys.Album, AlbumId],
queryFn: () => fetchItem(api, item.AlbumId!),
enabled: !!AlbumId && Type === BaseItemKind.Audio,
})
/**
@@ -86,29 +87,26 @@ export const ItemProvider: ({ item, children }: ItemProviderProps) => React.JSX.
*
* Referenced later in the context sheet
*/
useQuery({
queryKey: [QueryKeys.Album, Id],
queryFn: () => item,
enabled: !!Id && Type === BaseItemKind.MusicAlbum,
})
if (Type === BaseItemKind.MusicAlbum) queryClient.setQueryData([QueryKeys.Album, Id], item)
/**
* Prefetch for an album's tracks
*
* Referenced later in the context sheet
*/
useQuery({
if (Type === BaseItemKind.MusicAlbum)
queryClient.ensureQueryData({
queryKey: [QueryKeys.ItemTracks, Id],
queryFn: () => fetchAlbumDiscs(api, item),
enabled: !!Id && item.Type === BaseItemKind.MusicAlbum,
})
/**
* Fire query for a playlist's tracks
* Prefetch query for a playlist's tracks
*
* Referenced later in the context sheet
*/
useQuery({
if (Type === BaseItemKind.Playlist)
queryClient.ensureQueryData({
queryKey: [QueryKeys.ItemTracks, Id],
queryFn: () =>
getItemsApi(api!)
@@ -117,16 +115,20 @@ export const ItemProvider: ({ item, children }: ItemProviderProps) => React.JSX.
if (data.Items) return data.Items
else return []
}),
enabled: !!Id && Type === BaseItemKind.Playlist,
})
return useMemo(
() => (
if (UserData) queryClient.setQueryData([QueryKeys.UserData, Id], UserData)
else
queryClient.ensureQueryData({
queryKey: [QueryKeys.UserData, Id],
queryFn: () => fetchUserData(api, user, Id),
})
}, [queryClient, api, user, Id, Type, AlbumId, UserData, item, streamingQuality])
return (
<ItemContext.Provider value={{ item }}>
{artistIds.map((Id) => Id && <ItemArtistProvider artistId={Id} key={Id} />)}
{children}
</ItemContext.Provider>
),
[item],
)
}

View File

@@ -1,8 +1,8 @@
import { createContext } from 'react'
import { createContext, useEffect } from 'react'
import { useJellifyContext } from '..'
import { useQuery } from '@tanstack/react-query'
import { QueryKeys } from '../../enums/query-keys'
import { fetchItem } from '../../api/queries/item'
import { queryClient } from '../../constants/query-client'
interface ItemArtistContext {
artistId: string | undefined
@@ -19,13 +19,15 @@ export const ItemArtistProvider: ({
}) => React.JSX.Element = ({ artistId }) => {
const { api } = useJellifyContext()
useEffect(() => {
/**
* Store queryable of artist item
*/
useQuery({
if (artistId)
queryClient.ensureQueryData({
queryKey: [QueryKeys.ArtistById, artistId],
queryFn: () => fetchItem(api, artistId!),
enabled: !!artistId,
})
})
return <ItemArtistContext.Provider value={{ artistId }} />

View File

@@ -100,8 +100,6 @@ const LibraryContextInitializer = () => {
),
select: selectArtists,
initialPageParam: 0,
staleTime: QueryConfig.staleTime.oneDay, // Cache for 1 day to reduce network requests
gcTime: QueryConfig.staleTime.oneWeek, // Keep in memory for 1 week
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => {
return lastPage.length === QueryConfig.limits.library ? lastPageParam + 1 : undefined
},
@@ -123,8 +121,6 @@ const LibraryContextInitializer = () => {
sortDescending ? SortOrder.Descending : SortOrder.Ascending,
),
initialPageParam: 0,
staleTime: QueryConfig.staleTime.oneDay, // Cache for 1 day to reduce network requests
gcTime: QueryConfig.staleTime.oneWeek, // Keep in memory for 1 week
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => {
console.debug(`Tracks last page length: ${lastPage.length}`)
return lastPage.length === QueryConfig.limits.library * 2
@@ -149,8 +145,6 @@ const LibraryContextInitializer = () => {
initialPageParam: alphabet[0],
select: (data) => data.pages.flatMap((page) => [page.title, ...page.data]),
maxPages: alphabet.length,
staleTime: QueryConfig.staleTime.oneDay, // Cache for 1 day to reduce network requests
gcTime: QueryConfig.staleTime.oneWeek, // Keep in memory for 1 week
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => {
console.debug(`Albums last page length: ${lastPage.data.length}`)
if (lastPageParam !== alphabet[alphabet.length - 1]) {
@@ -180,8 +174,6 @@ const LibraryContextInitializer = () => {
queryFn: () => fetchUserPlaylists(api, user, library),
select: (data) => data.pages.flatMap((page) => page),
initialPageParam: 0,
staleTime: QueryConfig.staleTime.oneDay, // Cache for 1 day to reduce network requests
gcTime: QueryConfig.staleTime.oneWeek, // Keep in memory for 1 week
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => {
return lastPage.length === QueryConfig.limits.library ? lastPageParam + 1 : undefined
},

View File

@@ -115,7 +115,6 @@ const NetworkContextInitializer = () => {
const { data: storageUsage } = useQuery({
queryKey: [QueryKeys.StorageInUse],
queryFn: () => fetchStorageInUse(),
staleTime: 1000 * 60 * 60 * 1, // 1 hour
})
const { mutate: clearDownloads } = useMutation({

View File

@@ -90,7 +90,6 @@ const PlayerContextInitializer = () => {
nowPlayingJson ? JSON.parse(nowPlayingJson) : undefined,
)
const [initialized, setInitialized] = useState<boolean>(false)
const [repeatMode, setRepeatMode] = useState<RepeatMode>(
repeatModeJson ? JSON.parse(repeatModeJson) : RepeatMode.Off,
)
@@ -579,26 +578,6 @@ const PlayerContextInitializer = () => {
}
}, [currentIndex, playQueue])
/**
* Initialize the player. This is used to load the queue from the {@link QueueProvider}
* and set it to the player if we have already completed the onboarding process
* and the user has a valid queue in storage
*/
useEffect(() => {
console.debug('Initialized', initialized)
console.debug('Play queue length', playQueue.length)
console.debug('Current index', currentIndex)
if (playQueue.length > 0 && currentIndex > -1 && !initialized) {
TrackPlayer.setQueue(playQueue)
TrackPlayer.skip(currentIndex)
console.debug('Loaded queue from storage')
setInitialized(true)
} else if (queueRef === 'Recently Played' && currentIndex === -1) {
console.debug('Not loading queue as it is empty')
setInitialized(true)
}
}, [])
/**
* Clean up prefetched track IDs when the current index changes significantly
*/

View File

@@ -161,10 +161,10 @@ const QueueContextInitailizer = () => {
* {@link Event.PlaybackActiveTrackChanged} events until that mutation has settled
*/
const [skipping, setSkipping] = useState<boolean>(false)
const [initialized, setInitialized] = useState<boolean>(false)
const [shuffled, setShuffled] = useState<boolean>(shuffledInit ?? false)
const [initialized, setInitialized] = useState<boolean>(false)
//#endregion State
//#region Context
@@ -178,13 +178,10 @@ const QueueContextInitailizer = () => {
useTrackPlayerEvents(
[Event.PlaybackActiveTrackChanged],
async ({ index, track }: { index?: number | undefined; track?: Track | undefined }) => {
console.debug(`Active Track Changed to: ${index}. Skipping: ${skipping}`)
if (skipping) return
// We get an event emitted when the queue is loaded from storage for the first time
// This is the most convenient place I could find to flip this boolean and start
// listening to emitted updates
if (!initialized) return setInitialized(true)
console.debug(
`Active Track Changed to: ${index}. Skipping: ${skipping}, Initialized: ${initialized}`,
)
if (skipping || !initialized) return
let newIndex = -1
@@ -547,7 +544,6 @@ const QueueContextInitailizer = () => {
setSkipping(true)
},
onSuccess: async (data: void, { startPlayback }: QueueMutation) => {
setInitialized(true)
trigger('notificationSuccess')
console.debug(`Loaded new queue`)
@@ -717,6 +713,20 @@ const QueueContextInitailizer = () => {
//#region useEffect(s)
/**
* Initialization
*/
useEffect(() => {
if (playQueue.length > 0 && currentIndex > -1 && !initialized) {
TrackPlayer.setQueue(playQueue)
TrackPlayer.skip(currentIndex)
setInitialized(true)
} else {
console.debug(`No queue to initialize from`)
setInitialized(true)
}
}, [initialized])
/**
* Store play queue in storage when it changes
*/

View File

@@ -56,7 +56,6 @@ const PlaylistContextInitializer = (playlist: BaseItemDto) => {
return response.data.Items ? response.data.Items! : []
})
},
staleTime: 1000 * 60 * 60 * 2, // 2 hours, since these are mutable
})
const useUpdatePlaylist = useMutation({

View File

@@ -11,7 +11,6 @@ import { useJellifyContext } from '..'
interface SetFavoriteMutation {
item: BaseItemDto
setFavorite: React.Dispatch<SetStateAction<boolean>>
onToggle?: () => void
}
@@ -27,7 +26,7 @@ const JellifyUserDataContextInitializer = () => {
itemId: mutation.item.Id!,
})
},
onSuccess: ({ data }, { item, setFavorite, onToggle }) => {
onSuccess: ({ data }, { item, onToggle }) => {
// Burnt.alert({
// title: `Added favorite`,
// duration: 1,
@@ -40,7 +39,6 @@ const JellifyUserDataContextInitializer = () => {
trigger('notificationSuccess')
setFavorite(true)
if (onToggle) onToggle()
// Force refresh of track user data
@@ -54,7 +52,7 @@ const JellifyUserDataContextInitializer = () => {
itemId: mutation.item.Id!,
})
},
onSuccess: ({ data }, { item, setFavorite, onToggle }) => {
onSuccess: ({ data }, { item, onToggle }) => {
// Burnt.alert({
// title: `Removed favorite`,
// duration: 1,
@@ -65,7 +63,6 @@ const JellifyUserDataContextInitializer = () => {
type: 'error',
})
trigger('notificationSuccess')
setFavorite(false)
if (onToggle) onToggle()