mirror of
https://github.com/Jellify-Music/App.git
synced 2026-03-18 03:00:35 -05:00
dehookify some things
This commit is contained in:
@@ -94,7 +94,6 @@
|
||||
<array>
|
||||
<string>audio</string>
|
||||
<string>fetch</string>
|
||||
<string>processing</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { useLoadNewQueue } from '../../hooks/player/callbacks'
|
||||
import { BaseStackParamList } from '../../screens/types'
|
||||
import { useApi } from '../../stores'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { YStack, H5, XStack, Separator, Text, Paragraph } from 'tamagui'
|
||||
import { YStack, H5, XStack, Separator, Paragraph } from 'tamagui'
|
||||
import Icon from '../Global/components/icon'
|
||||
import ItemImage from '../Global/components/image'
|
||||
import { RunTimeTicks } from '../Global/helpers/time-codes'
|
||||
@@ -13,6 +11,7 @@ import { InstantMixButton } from '../Global/components/instant-mix-button'
|
||||
import { useAlbumDiscs } from '../../api/queries/album'
|
||||
import { formatArtistName } from '../../utils/formatting/artist-names'
|
||||
import { BUTTON_PRESS_STYLES, ICON_PRESS_STYLES } from '../../configs/style.config'
|
||||
import { loadNewQueue } from '../../hooks/player/functions/queue'
|
||||
|
||||
/**
|
||||
* Renders a header for an Album's track list
|
||||
@@ -22,10 +21,6 @@ import { BUTTON_PRESS_STYLES, ICON_PRESS_STYLES } from '../../configs/style.conf
|
||||
* @returns A React component
|
||||
*/
|
||||
export default function AlbumTrackListHeader({ album }: { album: BaseItemDto }): React.JSX.Element {
|
||||
const api = useApi()
|
||||
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
|
||||
const { data: discs, isPending } = useAlbumDiscs(album)
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<BaseStackParamList>>()
|
||||
|
||||
@@ -11,11 +11,11 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { BaseStackParamList } from '@/src/screens/types'
|
||||
import IconButton from '../Global/helpers/icon-button'
|
||||
import { fetchAlbumDiscs } from '../../api/queries/item'
|
||||
import { useLoadNewQueue } from '../../hooks/player/callbacks'
|
||||
import { getApi } from '../../stores'
|
||||
import Icon from '../Global/components/icon'
|
||||
import { useArtistTracks } from '../../api/queries/track'
|
||||
import { ICON_PRESS_STYLES } from '../../configs/style.config'
|
||||
import { loadNewQueue } from '../../hooks/player/functions/queue'
|
||||
|
||||
export default function ArtistHeader(): React.JSX.Element {
|
||||
const { width } = useSafeAreaFrame()
|
||||
@@ -24,8 +24,6 @@ export default function ArtistHeader(): React.JSX.Element {
|
||||
|
||||
const { artist, albums } = useArtistContext()
|
||||
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<BaseStackParamList>>()
|
||||
|
||||
const playArtist = async (shuffled: boolean = false) => {
|
||||
@@ -41,7 +39,7 @@ export default function ArtistHeader(): React.JSX.Element {
|
||||
|
||||
if (allTracks.length === 0) return
|
||||
|
||||
loadNewQueue({
|
||||
await loadNewQueue({
|
||||
track: allTracks[0],
|
||||
index: 0,
|
||||
tracklist: allTracks,
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
BaseItemKind,
|
||||
MediaSourceInfo,
|
||||
} from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { ListItem, Spinner, View, YGroup } from 'tamagui'
|
||||
import { ListItem, 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'
|
||||
@@ -23,9 +23,7 @@ import ItemImage from '../Global/components/image'
|
||||
import { StackActions } from '@react-navigation/native'
|
||||
import TextTicker from 'react-native-text-ticker'
|
||||
import { TextTickerConfig } from '../Player/component.config'
|
||||
import { useAddToQueue } from '../../hooks/player/callbacks'
|
||||
import { triggerHaptic } from '../../hooks/use-haptic-feedback'
|
||||
import { Platform } from 'react-native'
|
||||
import { useApi } from '../../stores'
|
||||
import DeletePlaylistRow from './components/delete-playlist-row'
|
||||
import RemoveFromPlaylistRow from './components/remove-from-playlist-row'
|
||||
@@ -34,6 +32,7 @@ import { useIsDownloaded } from '../../hooks/downloads'
|
||||
import { useDownloadProgress } from 'react-native-nitro-player'
|
||||
import CircularProgressIndicator from '../Global/components/circular-progress-indicator'
|
||||
import { useArtist } from '../../api/queries/artist'
|
||||
import { addToQueue } from '../../hooks/player/functions/queue'
|
||||
|
||||
type StackNavigation = Pick<NativeStackNavigationProp<BaseStackParamList>, 'navigate' | 'dispatch'>
|
||||
|
||||
@@ -188,8 +187,6 @@ function AddToPlaylistRow({
|
||||
}
|
||||
|
||||
function AddToQueueMenuRow({ tracks }: { tracks: BaseItemDto[] }): React.JSX.Element {
|
||||
const addToQueue = useAddToQueue()
|
||||
|
||||
const mutation: AddToQueueMutation = {
|
||||
tracks,
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useNetworkStatus } from '../../../../stores/network'
|
||||
import navigationRef from '../../../../screens/navigation'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { BaseStackParamList } from '../../../../screens/types'
|
||||
import { useAddToQueue, useLoadNewQueue } from '../../../../hooks/player/callbacks'
|
||||
import SwipeableRow from '../SwipeableRow'
|
||||
import { useSwipeSettingsStore } from '../../../../stores/settings/swipe'
|
||||
import { buildSwipeConfig } from '../../helpers/swipe-actions'
|
||||
@@ -20,6 +19,7 @@ import { StackActions } from '@react-navigation/native'
|
||||
import { useHideRunTimesSetting } from '../../../../stores/settings/app'
|
||||
import TrackRowContent from './content'
|
||||
import { useIsDownloaded } from '../../../../hooks/downloads'
|
||||
import { addToQueue, loadNewQueue } from '../../../../hooks/player/functions/queue'
|
||||
|
||||
export interface TrackProps {
|
||||
track: BaseItemDto
|
||||
@@ -63,8 +63,6 @@ export default function Track({
|
||||
const [hideRunTimes] = useHideRunTimesSetting()
|
||||
|
||||
const currentTrackId = useCurrentTrackId()
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
const addToQueue = useAddToQueue()
|
||||
const [networkStatus] = useNetworkStatus()
|
||||
|
||||
const isDownloaded = useIsDownloaded([track.Id!])
|
||||
|
||||
@@ -9,7 +9,6 @@ import FavoriteIcon from './favorite-icon'
|
||||
import navigationRef from '../../../screens/navigation'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { BaseStackParamList } from '../../../screens/types'
|
||||
import { useAddToQueue, useLoadNewQueue } from '../../../hooks/player/callbacks'
|
||||
import useItemContext from '../../../hooks/use-item-context'
|
||||
import { RouteProp, useRoute } from '@react-navigation/native'
|
||||
import React from 'react'
|
||||
@@ -30,6 +29,7 @@ import { useAddFavorite, useRemoveFavorite } from '../../../api/mutations/favori
|
||||
import { useHideRunTimesSetting } from '../../../stores/settings/app'
|
||||
import { Queue } from '../../../services/types/queue-item'
|
||||
import { formatArtistName } from '../../../utils/formatting/artist-names'
|
||||
import { addToQueue, loadNewQueue } from '../../../hooks/player/functions/queue'
|
||||
|
||||
interface ItemRowProps {
|
||||
item: BaseItemDto
|
||||
@@ -63,8 +63,6 @@ function ItemRow({
|
||||
}: ItemRowProps): React.JSX.Element {
|
||||
const artworkAreaWidth = useSharedValue(0)
|
||||
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
const addToQueue = useAddToQueue()
|
||||
const { mutate: addFavorite } = useAddFavorite()
|
||||
const { mutate: removeFavorite } = useRemoveFavorite()
|
||||
const [hideRunTimes] = useHideRunTimesSetting()
|
||||
|
||||
@@ -2,15 +2,14 @@ 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 { QueuingType } from '../../../enums/queuing-type'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { useLoadNewQueue } from '../../../hooks/player/callbacks'
|
||||
import { useDisplayContext } from '../../../providers/Display/display-provider'
|
||||
import HomeStackParamList from '../../../screens/Home/types'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { RootStackParamList } from '../../../screens/types'
|
||||
import { useFrequentlyPlayedTracks } from '../../../api/queries/frequents'
|
||||
import AnimatedRow from '../../Global/helpers/animated-row'
|
||||
import { loadNewQueue } from '../../../hooks/player/functions/queue'
|
||||
|
||||
export default function FrequentlyPlayedTracks(): React.JSX.Element {
|
||||
const tracksInfiniteQuery = useFrequentlyPlayedTracks()
|
||||
@@ -19,7 +18,6 @@ export default function FrequentlyPlayedTracks(): React.JSX.Element {
|
||||
|
||||
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
const { horizontalItems } = useDisplayContext()
|
||||
|
||||
return tracksInfiniteQuery.data ? (
|
||||
@@ -47,8 +45,8 @@ export default function FrequentlyPlayedTracks(): React.JSX.Element {
|
||||
caption={track.Name}
|
||||
subCaption={`${track.Artists?.join(', ')}`}
|
||||
squared
|
||||
onPress={() => {
|
||||
loadNewQueue({
|
||||
onPress={async () => {
|
||||
await loadNewQueue({
|
||||
track,
|
||||
index,
|
||||
tracklist: tracksInfiniteQuery.data ?? [track],
|
||||
|
||||
@@ -3,23 +3,20 @@ import { H5, XStack } from 'tamagui'
|
||||
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'
|
||||
import HorizontalCardList from '../../../components/Global/components/horizontal-list'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { useLoadNewQueue } from '../../../hooks/player/callbacks'
|
||||
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 { BaseItemDto, BaseItemKind } from '@jellyfin/sdk/lib/generated-client'
|
||||
import AnimatedRow from '../../Global/helpers/animated-row'
|
||||
import { loadNewQueue } from '../../../hooks/player/functions/queue'
|
||||
|
||||
export default function RecentlyPlayed(): React.JSX.Element {
|
||||
const navigation = useNavigation<NativeStackNavigationProp<HomeStackParamList>>()
|
||||
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
|
||||
const tracksInfiniteQuery = useRecentlyPlayedTracks()
|
||||
|
||||
const { horizontalItems } = useDisplayContext()
|
||||
|
||||
@@ -2,30 +2,20 @@ import React from 'react'
|
||||
import { Spacer, XStack, getToken } from 'tamagui'
|
||||
import PlayPauseButton from './buttons'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import {
|
||||
usePrevious,
|
||||
useSkip,
|
||||
useToggleRepeatMode,
|
||||
useToggleShuffle,
|
||||
} from '../../../hooks/player/callbacks'
|
||||
import { useRepeatMode, useShuffle } from '../../../stores/player/queue'
|
||||
import { RepeatMode } from '@jellyfin/sdk/lib/generated-client/models/repeat-mode'
|
||||
import { toggleRepeatMode } from '../../../hooks/player/functions/repeat-mode'
|
||||
import { toggleShuffle } from '../../../hooks/player/functions/shuffle'
|
||||
import { previous, skip } from '../../../hooks/player/functions/controls'
|
||||
|
||||
export default function Controls({
|
||||
onLyricsScreen,
|
||||
}: {
|
||||
onLyricsScreen?: boolean
|
||||
}): React.JSX.Element {
|
||||
const previous = usePrevious()
|
||||
const skip = useSkip()
|
||||
const repeatMode = useRepeatMode()
|
||||
|
||||
const toggleRepeatMode = useToggleRepeatMode()
|
||||
|
||||
const shuffled = useShuffle()
|
||||
|
||||
const toggleShuffle = useToggleShuffle()
|
||||
|
||||
return (
|
||||
<XStack alignItems='center' justifyContent='space-between'>
|
||||
{!onLyricsScreen && (
|
||||
@@ -33,7 +23,7 @@ export default function Controls({
|
||||
small
|
||||
color={shuffled ? '$primary' : '$color'}
|
||||
name='shuffle'
|
||||
onPress={() => toggleShuffle(shuffled)}
|
||||
onPress={async () => await toggleShuffle(shuffled)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -42,7 +32,7 @@ export default function Controls({
|
||||
<Icon
|
||||
name='skip-previous'
|
||||
color='$primary'
|
||||
onPress={previous}
|
||||
onPress={async () => await previous()}
|
||||
large
|
||||
testID='previous-button-test-id'
|
||||
/>
|
||||
@@ -53,7 +43,7 @@ export default function Controls({
|
||||
<Icon
|
||||
name='skip-next'
|
||||
color='$primary'
|
||||
onPress={() => skip(undefined)}
|
||||
onPress={async () => await skip(undefined)}
|
||||
large
|
||||
testID='skip-button-test-id'
|
||||
/>
|
||||
@@ -65,7 +55,7 @@ export default function Controls({
|
||||
small
|
||||
color={repeatMode === 'off' ? '$color' : '$primary'}
|
||||
name={repeatMode === 'track' ? 'repeat-once' : 'repeat'}
|
||||
onPress={async () => toggleRepeatMode()}
|
||||
onPress={toggleRepeatMode}
|
||||
/>
|
||||
)}
|
||||
</XStack>
|
||||
|
||||
@@ -9,23 +9,11 @@ import { QueryKeys } from '../../../enums/query-keys'
|
||||
import navigationRef from '../../../screens/navigation'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { CommonActions } from '@react-navigation/native'
|
||||
import { Gesture } from 'react-native-gesture-handler'
|
||||
import Animated, {
|
||||
Easing,
|
||||
FadeIn,
|
||||
FadeOut,
|
||||
LinearTransition,
|
||||
useSharedValue,
|
||||
withDelay,
|
||||
withSpring,
|
||||
} from 'react-native-reanimated'
|
||||
import Animated, { Easing, FadeIn, FadeOut } from 'react-native-reanimated'
|
||||
import type { SharedValue } from 'react-native-reanimated'
|
||||
import { runOnJS } from 'react-native-worklets'
|
||||
import { usePrevious, useSkip } from '../../../hooks/player/callbacks'
|
||||
import { useCurrentTrack } from '../../../stores/player/queue'
|
||||
import { useApi } from '../../../stores'
|
||||
import { isExplicit } from '../../../utils/trackDetails'
|
||||
import { triggerHaptic } from '../../../hooks/use-haptic-feedback'
|
||||
import { MediaSourceInfo } from '@jellyfin/sdk/lib/generated-client'
|
||||
import getTrackDto from '../../../utils/mapping/track-extra-payload'
|
||||
import { ICON_PRESS_STYLES } from '../../../configs/style.config'
|
||||
@@ -35,43 +23,8 @@ type SongInfoProps = {
|
||||
swipeX?: SharedValue<number>
|
||||
}
|
||||
|
||||
export default function SongInfo({ swipeX }: SongInfoProps = {}): React.JSX.Element {
|
||||
export default function SongInfo(): React.JSX.Element {
|
||||
const api = useApi()
|
||||
const skip = useSkip()
|
||||
const previous = usePrevious()
|
||||
// local fallback if no shared value was provided
|
||||
const localX = useSharedValue(0)
|
||||
const x = swipeX ?? localX
|
||||
|
||||
const albumGesture = Gesture.Pan()
|
||||
.activeOffsetX([-12, 12])
|
||||
.onUpdate((e) => {
|
||||
if (Math.abs(e.translationY) < 40) {
|
||||
x.value = Math.max(-160, Math.min(160, e.translationX))
|
||||
}
|
||||
})
|
||||
.onEnd((e) => {
|
||||
const threshold = 120
|
||||
const minVelocity = 600
|
||||
const isHorizontal = Math.abs(e.translationY) < 40
|
||||
if (
|
||||
isHorizontal &&
|
||||
(Math.abs(e.translationX) > threshold || Math.abs(e.velocityX) > minVelocity)
|
||||
) {
|
||||
if (e.translationX > 0) {
|
||||
x.value = withSpring(220)
|
||||
runOnJS(triggerHaptic)('notificationSuccess')
|
||||
runOnJS(skip)(undefined)
|
||||
} else {
|
||||
x.value = withSpring(-220)
|
||||
runOnJS(triggerHaptic)('notificationSuccess')
|
||||
runOnJS(previous)()
|
||||
}
|
||||
x.value = withDelay(160, withSpring(0))
|
||||
} else {
|
||||
x.value = withSpring(0)
|
||||
}
|
||||
})
|
||||
|
||||
const currentTrack = useCurrentTrack()
|
||||
|
||||
|
||||
@@ -11,15 +11,13 @@ import { usePerformanceMonitor } from '../../hooks/use-performance-monitor'
|
||||
import { useSharedValue, withDelay, withSpring } from 'react-native-reanimated'
|
||||
import { Gesture, GestureDetector } from 'react-native-gesture-handler'
|
||||
import { runOnJS } from 'react-native-worklets'
|
||||
import { usePrevious, useSkip } from '../../hooks/player/callbacks'
|
||||
import { triggerHaptic } from '../../hooks/use-haptic-feedback'
|
||||
import { useCurrentTrack } from '../../stores/player/queue'
|
||||
import { previous, skip } from '../../hooks/player/functions/controls'
|
||||
|
||||
export default function PlayerScreen(): React.JSX.Element {
|
||||
usePerformanceMonitor('PlayerScreen', 5)
|
||||
|
||||
const skip = useSkip()
|
||||
const previous = usePrevious()
|
||||
const nowPlaying = useCurrentTrack()
|
||||
|
||||
const { width, height } = useWindowDimensions()
|
||||
|
||||
@@ -11,15 +11,12 @@ import { Gesture, GestureDetector } from 'react-native-gesture-handler'
|
||||
import Animated, {
|
||||
Easing,
|
||||
FadeIn,
|
||||
FadeInDown,
|
||||
FadeOut,
|
||||
FadeOutDown,
|
||||
useSharedValue,
|
||||
useAnimatedStyle,
|
||||
withTiming,
|
||||
useAnimatedReaction,
|
||||
ReduceMotion,
|
||||
SlideInUp,
|
||||
SlideInDown,
|
||||
SlideOutDown,
|
||||
interpolate,
|
||||
@@ -28,17 +25,15 @@ import { runOnJS } from 'react-native-worklets'
|
||||
import { RootStackParamList } from '../../screens/types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import ItemImage from '../Global/components/image'
|
||||
import { usePrevious, useSkip } from '../../hooks/player/callbacks'
|
||||
import { useCurrentTrack } from '../../stores/player/queue'
|
||||
import getTrackDto from '../../utils/mapping/track-extra-payload'
|
||||
import { ICON_PRESS_STYLES } from '../../configs/style.config'
|
||||
import { previous, skip } from '../../hooks/player/functions/controls'
|
||||
|
||||
export default function Miniplayer(): React.JSX.Element | null {
|
||||
const nowPlaying = useCurrentTrack()
|
||||
const item = getTrackDto(nowPlaying)
|
||||
|
||||
const skip = useSkip()
|
||||
const previous = usePrevious()
|
||||
const theme = useTheme()
|
||||
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
|
||||
@@ -3,14 +3,9 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { H5, Spacer, XStack, YStack } from 'tamagui'
|
||||
import { InstantMixButton } from '../../Global/components/instant-mix-button'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { useNetworkStatus } from '../../../stores/network'
|
||||
import { QueuingType } from '../../../enums/queuing-type'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import LibraryStackParamList from '@/src/screens/Library/types'
|
||||
import { useLoadNewQueue } from '../../../hooks/player/callbacks'
|
||||
import useStreamingDeviceProfile from '../../../stores/device-profile'
|
||||
import ItemImage from '../../Global/components/image'
|
||||
import { useApi } from '../../../stores'
|
||||
import Input from '../../Global/helpers/input'
|
||||
import Animated, { Easing, FadeInDown, FadeOutDown } from 'react-native-reanimated'
|
||||
import { Dispatch, SetStateAction } from 'react'
|
||||
@@ -18,6 +13,7 @@ import Button from '../../Global/helpers/button'
|
||||
import { Text } from '../../Global/helpers/text'
|
||||
import { RunTimeTicks } from '../../Global/helpers/time-codes'
|
||||
import { BUTTON_PRESS_STYLES } from '../../../configs/style.config'
|
||||
import { loadNewQueue } from '../../../hooks/player/functions/queue'
|
||||
|
||||
export default function PlaylistTracklistHeader({
|
||||
playlist,
|
||||
@@ -97,12 +93,6 @@ function PlaylistHeaderControls({
|
||||
playlist: BaseItemDto
|
||||
playlistTracks: BaseItemDto[]
|
||||
}): React.JSX.Element {
|
||||
const streamingDeviceProfile = useStreamingDeviceProfile()
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
const api = useApi()
|
||||
|
||||
const [networkStatus] = useNetworkStatus()
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<LibraryStackParamList>>()
|
||||
|
||||
const playPlaylist = async (shuffled: boolean = false) => {
|
||||
|
||||
@@ -11,8 +11,6 @@ import { RenderItemInfo } from 'react-native-sortables/dist/typescript/types'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'
|
||||
import PlaylistTracklistHeader from './components/header'
|
||||
import navigationRef from '../../screens/navigation'
|
||||
import { useLoadNewQueue } from '../../hooks/player/callbacks'
|
||||
import { QueuingType } from '../../enums/queuing-type'
|
||||
import { useApi } from '../../stores'
|
||||
import useStreamingDeviceProfile from '../../stores/device-profile'
|
||||
import { useEffect, useLayoutEffect, useState } from 'react'
|
||||
@@ -36,6 +34,7 @@ import { queryClient } from '../../constants/query-client'
|
||||
import { PlaylistTracksQueryKey } from '../../api/queries/playlist/keys'
|
||||
import { useIsDownloaded } from '../../hooks/downloads'
|
||||
import { useDeleteDownloads } from '../../hooks/downloads/mutations'
|
||||
import { loadNewQueue } from '../../hooks/player/functions/queue'
|
||||
|
||||
export default function Playlist({
|
||||
playlist,
|
||||
@@ -145,8 +144,6 @@ export default function Playlist({
|
||||
if (!editing) refetch()
|
||||
}, [editing])
|
||||
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
|
||||
const isDownloaded = useIsDownloaded(playlistTracks?.map(({ Id }) => Id) ?? [])
|
||||
|
||||
const playlistDownloadPending = useIsDownloading(playlistTracks ?? [])
|
||||
|
||||
@@ -4,7 +4,6 @@ import { RootStackParamList } from '../../screens/types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { Text, XStack } from 'tamagui'
|
||||
import { useLayoutEffect, useRef } from 'react'
|
||||
import { useRemoveFromQueue, useReorderQueue, useSkip } from '../../hooks/player/callbacks'
|
||||
import { useCurrentIndex, usePlayQueue, useQueueRef } from '../../stores/player/queue'
|
||||
import Sortable from 'react-native-sortables'
|
||||
import { OrderChangeParams, RenderItemInfo } from 'react-native-sortables/dist/typescript/types'
|
||||
@@ -13,6 +12,8 @@ import Animated, { useAnimatedRef } from 'react-native-reanimated'
|
||||
import { TrackItem } from 'react-native-nitro-player'
|
||||
import getTrackDto from '../../utils/mapping/track-extra-payload'
|
||||
import { View } from 'react-native'
|
||||
import { skip } from '../../hooks/player/functions/controls'
|
||||
import { removeItemFromQueue, reorderQueue } from '../../hooks/player/functions/queue'
|
||||
|
||||
export default function Queue({
|
||||
navigation,
|
||||
@@ -24,9 +25,6 @@ export default function Queue({
|
||||
const currentIndex = useCurrentIndex()
|
||||
|
||||
const queueRef = useQueueRef()
|
||||
const removeFromQueue = useRemoveFromQueue()
|
||||
const reorderQueue = useReorderQueue()
|
||||
const skip = useSkip()
|
||||
|
||||
const scrollableRef = useAnimatedRef<Animated.ScrollView>()
|
||||
|
||||
@@ -81,7 +79,7 @@ export default function Queue({
|
||||
|
||||
<Sortable.Touchable
|
||||
onTap={async () => {
|
||||
await removeFromQueue(index)
|
||||
await removeItemFromQueue(index)
|
||||
}}
|
||||
>
|
||||
<Icon name='close' color='$warning' />
|
||||
|
||||
@@ -1,27 +1,13 @@
|
||||
import {
|
||||
loadQueue,
|
||||
playLaterInQueue,
|
||||
playNextInQueue,
|
||||
removeItemFromQueue,
|
||||
} from './functions/queue'
|
||||
import { addToQueue, loadNewQueue, removeItemFromQueue } from './functions/queue'
|
||||
import { previous, skip } from './functions/controls'
|
||||
import { AddToQueueMutation, QueueMutation, QueueOrderMutation } from './interfaces'
|
||||
import { QueuingType } from '../../enums/queuing-type'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import { QueueOrderMutation } from './interfaces'
|
||||
import { handleDeshuffle, handleShuffle } from './functions/shuffle'
|
||||
import usePlayerEngineStore, { PlayerEngine } from '../../stores/player/engine'
|
||||
import { useRemoteMediaClient } from 'react-native-google-cast'
|
||||
import { triggerHaptic } from '../use-haptic-feedback'
|
||||
import { usePlayerQueueStore } from '../../stores/player/queue'
|
||||
import {
|
||||
PlayerQueue,
|
||||
RepeatMode,
|
||||
TrackItem,
|
||||
TrackPlayer,
|
||||
TrackPlayerState,
|
||||
} from 'react-native-nitro-player'
|
||||
import reportPlaybackStarted from '../../api/mutations/playback/functions/playback-started'
|
||||
import { updateTrackMediaInfo } from '../../providers/Player/utils/event-handlers'
|
||||
import { PlayerQueue, TrackItem, TrackPlayer, TrackPlayerState } from 'react-native-nitro-player'
|
||||
import { toggleRepeatMode } from './functions/repeat-mode'
|
||||
|
||||
/**
|
||||
* A mutation to handle toggling the playback state
|
||||
@@ -48,27 +34,12 @@ export const useTogglePlayback = () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Let's just use the function this returns directly instead
|
||||
* of subscribing to a hook
|
||||
*/
|
||||
export const useToggleRepeatMode = () => {
|
||||
return () => {
|
||||
const currentMode = usePlayerQueueStore.getState().repeatMode
|
||||
triggerHaptic('impactLight')
|
||||
|
||||
let nextMode: RepeatMode
|
||||
|
||||
switch (currentMode) {
|
||||
case 'off':
|
||||
nextMode = 'Playlist'
|
||||
break
|
||||
case 'Playlist':
|
||||
nextMode = 'track'
|
||||
break
|
||||
default:
|
||||
nextMode = 'off'
|
||||
}
|
||||
|
||||
TrackPlayer.setRepeatMode(nextMode)
|
||||
usePlayerQueueStore.getState().setRepeatMode(nextMode)
|
||||
}
|
||||
return toggleRepeatMode
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,109 +75,6 @@ const useSeekBy = () => {
|
||||
}
|
||||
}
|
||||
|
||||
export const useAddToQueue = () => {
|
||||
return async (variables: AddToQueueMutation) => {
|
||||
try {
|
||||
if (variables.queuingType === QueuingType.PlayNext)
|
||||
await playNextInQueue({ ...variables })
|
||||
else await playLaterInQueue({ ...variables })
|
||||
|
||||
triggerHaptic('notificationSuccess')
|
||||
Toast.show({
|
||||
text1:
|
||||
variables.queuingType === QueuingType.PlayNext
|
||||
? 'Playing next'
|
||||
: 'Added to queue',
|
||||
type: 'success',
|
||||
})
|
||||
} catch (error) {
|
||||
triggerHaptic('notificationError')
|
||||
console.error(
|
||||
`Failed to ${variables.queuingType === QueuingType.PlayNext ? 'play next' : 'add to queue'}`,
|
||||
error,
|
||||
)
|
||||
Toast.show({
|
||||
text1:
|
||||
variables.queuingType === QueuingType.PlayNext
|
||||
? 'Failed to play next'
|
||||
: 'Failed to add to queue',
|
||||
type: 'error',
|
||||
})
|
||||
} finally {
|
||||
const queue = await TrackPlayer.getActualQueue()
|
||||
|
||||
usePlayerQueueStore.getState().setQueue(queue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const useLoadNewQueue = () => {
|
||||
return async (variables: QueueMutation) => {
|
||||
triggerHaptic('impactLight')
|
||||
usePlayerQueueStore.getState().setIsQueuing(true)
|
||||
const { tracks, finalStartIndex } = await loadQueue({ ...variables })
|
||||
|
||||
// skipToIndex is now settled. Drive a single, authoritative URL-resolution
|
||||
// pass while isQueuing=true so any concurrent native callbacks are still
|
||||
// silenced. resolveTrackUrls bypasses the isQueuing guard intentionally.
|
||||
const tracksNeedingUrls = await TrackPlayer.getTracksNeedingUrls()
|
||||
if (tracksNeedingUrls.length > 0) {
|
||||
await updateTrackMediaInfo(tracksNeedingUrls)
|
||||
}
|
||||
|
||||
usePlayerQueueStore.getState().setIsQueuing(false)
|
||||
|
||||
if (variables.startPlayback) {
|
||||
TrackPlayer.play()
|
||||
reportPlaybackStarted(tracks[finalStartIndex], 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const usePrevious = () => {
|
||||
return async () => {
|
||||
triggerHaptic('impactMedium')
|
||||
|
||||
await previous()
|
||||
}
|
||||
}
|
||||
|
||||
export const useSkip = () => {
|
||||
return async (index?: number | undefined) => {
|
||||
triggerHaptic('impactMedium')
|
||||
|
||||
await skip(index)
|
||||
}
|
||||
}
|
||||
|
||||
export const useRemoveFromQueue = () => {
|
||||
return async (index: number) => {
|
||||
await removeItemFromQueue(index)
|
||||
}
|
||||
}
|
||||
|
||||
export const useReorderQueue = () => {
|
||||
return async ({ fromIndex, toIndex }: QueueOrderMutation) => {
|
||||
const playlistId = PlayerQueue.getCurrentPlaylistId()
|
||||
|
||||
if (!playlistId) return
|
||||
|
||||
const { tracks } = PlayerQueue.getPlaylist(playlistId)!
|
||||
|
||||
PlayerQueue.reorderTrackInPlaylist(playlistId, tracks[fromIndex].id, toIndex)
|
||||
|
||||
const { currentIndex } = await TrackPlayer.getState()
|
||||
|
||||
const queue = await TrackPlayer.getActualQueue()
|
||||
|
||||
usePlayerQueueStore.setState((state) => ({
|
||||
...state,
|
||||
queue,
|
||||
currentIndex,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
export const useResetQueue = () => () => {
|
||||
usePlayerQueueStore.getState().setUnshuffledQueue([])
|
||||
usePlayerQueueStore.getState().setShuffled(false)
|
||||
@@ -214,21 +82,3 @@ export const useResetQueue = () => () => {
|
||||
usePlayerQueueStore.getState().setQueue([])
|
||||
usePlayerQueueStore.getState().setCurrentIndex(undefined)
|
||||
}
|
||||
|
||||
export const useToggleShuffle = () => {
|
||||
return async (shuffled: boolean) => {
|
||||
triggerHaptic('impactMedium')
|
||||
|
||||
let result: { currentIndex: number; queue: TrackItem[] } | undefined
|
||||
|
||||
if (shuffled) result = await handleDeshuffle()
|
||||
else result = await handleShuffle()
|
||||
|
||||
usePlayerQueueStore.setState((state) => ({
|
||||
...state,
|
||||
queue: result.queue,
|
||||
currentIndex: result.currentIndex,
|
||||
shuffled: !shuffled,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { SKIP_TO_PREVIOUS_THRESHOLD } from '../../../configs/player.config'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { TrackPlayer } from 'react-native-nitro-player'
|
||||
import { triggerHaptic } from '../../use-haptic-feedback'
|
||||
|
||||
/**
|
||||
* A function that will skip to the previous track if
|
||||
@@ -15,11 +16,13 @@ import { TrackPlayer } from 'react-native-nitro-player'
|
||||
* Does not resume playback if the player was paused
|
||||
*/
|
||||
export async function previous(): Promise<void> {
|
||||
triggerHaptic('impactMedium')
|
||||
|
||||
const { currentState, currentIndex, currentPosition } = await TrackPlayer.getState()
|
||||
|
||||
if (isUndefined(currentIndex)) return
|
||||
|
||||
if (Math.floor(currentPosition) < SKIP_TO_PREVIOUS_THRESHOLD) {
|
||||
if (Math.floor(currentPosition) <= SKIP_TO_PREVIOUS_THRESHOLD) {
|
||||
TrackPlayer.skipToPrevious()
|
||||
} else {
|
||||
TrackPlayer.seek(0)
|
||||
@@ -39,6 +42,8 @@ export async function previous(): Promise<void> {
|
||||
* @param index The track index to skip to, to skip multiple tracks
|
||||
*/
|
||||
export async function skip(index: number | undefined): Promise<void> {
|
||||
triggerHaptic('impactMedium')
|
||||
|
||||
const { currentIndex } = await TrackPlayer.getState()
|
||||
|
||||
if (!isUndefined(index)) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mapDtoToTrack } from '../../../utils/mapping/item-to-track'
|
||||
import { networkStatusTypes } from '../../../components/Network/internetConnectionWatcher'
|
||||
import { clearPlaylists, filterTracksOnNetworkStatus } from './utils/queue'
|
||||
import { AddToQueueMutation, QueueMutation } from '../interfaces'
|
||||
import { AddToQueueMutation, QueueMutation, QueueOrderMutation } from '../interfaces'
|
||||
import { shuffleJellifyTracks } from './utils/shuffle'
|
||||
|
||||
import { setNewQueue, usePlayerQueueStore } from '../../../stores/player/queue'
|
||||
@@ -10,12 +10,37 @@ import { useNetworkStore } from '../../../stores/network'
|
||||
import { DownloadManager, PlayerQueue, TrackItem, TrackPlayer } from 'react-native-nitro-player'
|
||||
import uuid from 'react-native-uuid'
|
||||
import { triggerHaptic } from '../../use-haptic-feedback'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import { QueuingType } from '../../../enums/queuing-type'
|
||||
import { updateTrackMediaInfo } from '../../../providers/Player/utils/event-handlers'
|
||||
import reportPlaybackStarted from '../../../api/mutations/playback/functions/playback-started'
|
||||
|
||||
type LoadQueueResult = {
|
||||
finalStartIndex: number
|
||||
tracks: TrackItem[]
|
||||
}
|
||||
|
||||
export const loadNewQueue = async (variables: QueueMutation) => {
|
||||
triggerHaptic('impactLight')
|
||||
usePlayerQueueStore.getState().setIsQueuing(true)
|
||||
const { tracks, finalStartIndex } = await loadQueue({ ...variables })
|
||||
|
||||
// skipToIndex is now settled. Drive a single, authoritative URL-resolution
|
||||
// pass while isQueuing=true so any concurrent native callbacks are still
|
||||
// silenced. resolveTrackUrls bypasses the isQueuing guard intentionally.
|
||||
const tracksNeedingUrls = await TrackPlayer.getTracksNeedingUrls()
|
||||
if (tracksNeedingUrls.length > 0) {
|
||||
await updateTrackMediaInfo(tracksNeedingUrls)
|
||||
}
|
||||
|
||||
usePlayerQueueStore.getState().setIsQueuing(false)
|
||||
|
||||
if (variables.startPlayback) {
|
||||
TrackPlayer.play()
|
||||
reportPlaybackStarted(tracks[finalStartIndex], 0)
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadQueue({
|
||||
index = 0,
|
||||
tracklist,
|
||||
@@ -120,6 +145,37 @@ export const playLaterInQueue = async ({ tracks }: AddToQueueMutation) => {
|
||||
.setUnshuffledQueue([...usePlayerQueueStore.getState().unShuffledQueue, ...newTracks])
|
||||
}
|
||||
|
||||
export const addToQueue = async (variables: AddToQueueMutation) => {
|
||||
try {
|
||||
if (variables.queuingType === QueuingType.PlayNext) await playNextInQueue({ ...variables })
|
||||
else await playLaterInQueue({ ...variables })
|
||||
|
||||
triggerHaptic('notificationSuccess')
|
||||
Toast.show({
|
||||
text1:
|
||||
variables.queuingType === QueuingType.PlayNext ? 'Playing next' : 'Added to queue',
|
||||
type: 'success',
|
||||
})
|
||||
} catch (error) {
|
||||
triggerHaptic('notificationError')
|
||||
console.error(
|
||||
`Failed to ${variables.queuingType === QueuingType.PlayNext ? 'play next' : 'add to queue'}`,
|
||||
error,
|
||||
)
|
||||
Toast.show({
|
||||
text1:
|
||||
variables.queuingType === QueuingType.PlayNext
|
||||
? 'Failed to play next'
|
||||
: 'Failed to add to queue',
|
||||
type: 'error',
|
||||
})
|
||||
} finally {
|
||||
const queue = await TrackPlayer.getActualQueue()
|
||||
|
||||
usePlayerQueueStore.getState().setQueue(queue)
|
||||
}
|
||||
}
|
||||
|
||||
export const removeItemFromQueue = async (index: number) => {
|
||||
triggerHaptic('impactMedium')
|
||||
|
||||
@@ -171,3 +227,23 @@ export const removeItemFromQueue = async (index: number) => {
|
||||
currentIndex: newCurrentIndex,
|
||||
}))
|
||||
}
|
||||
|
||||
export const reorderQueue = async ({ fromIndex, toIndex }: QueueOrderMutation) => {
|
||||
const playlistId = PlayerQueue.getCurrentPlaylistId()
|
||||
|
||||
if (!playlistId) return
|
||||
|
||||
const { tracks } = PlayerQueue.getPlaylist(playlistId)!
|
||||
|
||||
PlayerQueue.reorderTrackInPlaylist(playlistId, tracks[fromIndex].id, toIndex)
|
||||
|
||||
const { currentIndex } = await TrackPlayer.getState()
|
||||
|
||||
const queue = await TrackPlayer.getActualQueue()
|
||||
|
||||
usePlayerQueueStore.setState((state) => ({
|
||||
...state,
|
||||
queue,
|
||||
currentIndex,
|
||||
}))
|
||||
}
|
||||
|
||||
24
src/hooks/player/functions/repeat-mode.ts
Normal file
24
src/hooks/player/functions/repeat-mode.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { usePlayerQueueStore } from '../../../stores/player/queue'
|
||||
import { triggerHaptic } from '../../use-haptic-feedback'
|
||||
import { RepeatMode, TrackPlayer } from 'react-native-nitro-player'
|
||||
|
||||
export const toggleRepeatMode = () => {
|
||||
const currentMode = usePlayerQueueStore.getState().repeatMode
|
||||
triggerHaptic('impactLight')
|
||||
|
||||
let nextMode: RepeatMode
|
||||
|
||||
switch (currentMode) {
|
||||
case 'off':
|
||||
nextMode = 'Playlist'
|
||||
break
|
||||
case 'Playlist':
|
||||
nextMode = 'track'
|
||||
break
|
||||
default:
|
||||
nextMode = 'off'
|
||||
}
|
||||
|
||||
TrackPlayer.setRepeatMode(nextMode)
|
||||
usePlayerQueueStore.getState().setRepeatMode(nextMode)
|
||||
}
|
||||
@@ -19,6 +19,23 @@ import { ApiLimits } from '../../../configs/query.config'
|
||||
import { mapDtoToTrack } from '../../../utils/mapping/item-to-track'
|
||||
import getTrackDto from '../../../utils/mapping/track-extra-payload'
|
||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { triggerHaptic } from '../../use-haptic-feedback'
|
||||
|
||||
export const toggleShuffle = async (shuffled: boolean) => {
|
||||
triggerHaptic('impactMedium')
|
||||
|
||||
let result: { currentIndex: number; queue: TrackItem[] } | undefined
|
||||
|
||||
if (shuffled) result = await handleDeshuffle()
|
||||
else result = await handleShuffle()
|
||||
|
||||
usePlayerQueueStore.setState((state) => ({
|
||||
...state,
|
||||
queue: result.queue,
|
||||
currentIndex: result.currentIndex,
|
||||
shuffled: !shuffled,
|
||||
}))
|
||||
}
|
||||
|
||||
export async function handleShuffle(
|
||||
keepCurrentTrack: boolean = true,
|
||||
@@ -259,25 +276,21 @@ export async function handleShuffle(
|
||||
// Save off unshuffledQueue
|
||||
usePlayerQueueStore.getState().setUnshuffledQueue([...playQueue])
|
||||
|
||||
// Tracks that come AFTER the currently playing track — these are what we shuffle.
|
||||
const tracksAfterCurrent = playQueue.filter((_, index) => index > currentIndex)
|
||||
// Only shuffle tracks AFTER the current position. Tracks before it have already
|
||||
// played and must stay in the native playlist prefix so ExoPlayer's window index
|
||||
// keeps pointing at the right item — removing them would cause the next skip to
|
||||
// jump to the wrong track.
|
||||
const tracksAfterCurrent = playQueue.filter((_, i) => i > currentIndex)
|
||||
const { shuffled: newShuffledQueue } = shuffleJellifyTracks(tracksAfterCurrent)
|
||||
|
||||
if (keepCurrentTrack) {
|
||||
// KEY: only touch tracks that are AFTER the current track.
|
||||
//
|
||||
// Android's ExoPlayer rebuilds via setMediaItems(list, resetPosition=false), which
|
||||
// preserves the current window INDEX — not the track identity. If we remove tracks
|
||||
// before the current track, the playlist shrinks and ExoPlayer's saved index can
|
||||
// become out-of-bounds, causing it to jump to the last item.
|
||||
//
|
||||
// By leaving all tracks before (and including) currentIndex in place, ExoPlayer's
|
||||
// current window index still points at the right track after the rebuild.
|
||||
tracksAfterCurrent.forEach((track) =>
|
||||
PlayerQueue.removeTrackFromPlaylist(playlistId!, track.id),
|
||||
)
|
||||
|
||||
// Insert the shuffled upcoming tracks right after currentIndex.
|
||||
// Insert the shuffled upcoming tracks right after the current track.
|
||||
// Must use currentIndex + 1 (not a hardcoded 1) so the insert position is
|
||||
// correct regardless of how many tracks precede the current one.
|
||||
PlayerQueue.addTracksToPlaylist(playlistId!, newShuffledQueue, currentIndex + 1)
|
||||
|
||||
// Present a clean queue to the JS store (current track first, then shuffled upcoming).
|
||||
|
||||
@@ -6,24 +6,26 @@ import { QueryKeys } from '../enums/query-keys'
|
||||
import { fetchAlbumDiscs, fetchItem } from '../api/queries/item'
|
||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import fetchUserData from '../api/queries/user-data/utils'
|
||||
import { useRef } from 'react'
|
||||
import UserDataQueryKey from '../api/queries/user-data/keys'
|
||||
import { getApi, getUser } from '../stores'
|
||||
import { ArtistQueryKey } from '../api/queries/artist/keys'
|
||||
|
||||
// Module-level dedup guard — no hook needed, this is just a long-lived Set
|
||||
const prefetchedContext = new Set<string>()
|
||||
|
||||
export default function useItemContext(): (item: BaseItemDto) => void {
|
||||
const api = getApi()
|
||||
const user = getUser()
|
||||
|
||||
const prefetchedContext = useRef<Set<string>>(new Set())
|
||||
|
||||
return (item: BaseItemDto) => {
|
||||
const effectSig = `${item.Id}-${item.Type}`
|
||||
|
||||
// If we've already warmed the cache for this item, return
|
||||
if (prefetchedContext.current.has(effectSig)) return
|
||||
if (prefetchedContext.has(effectSig)) return
|
||||
|
||||
// Mark this item's context as warmed, preventing reruns
|
||||
prefetchedContext.current.add(effectSig)
|
||||
prefetchedContext.add(effectSig)
|
||||
|
||||
// Read api/user inside the callback so they're only resolved when actually needed
|
||||
const api = getApi()
|
||||
const user = getUser()
|
||||
|
||||
warmItemContext(api, user, item)
|
||||
}
|
||||
@@ -41,8 +43,7 @@ function warmItemContext(
|
||||
|
||||
if (Type === BaseItemKind.Audio) warmTrackContext(api, item)
|
||||
|
||||
if (Type === BaseItemKind.MusicArtist)
|
||||
queryClient.setQueryData([QueryKeys.ArtistById, Id], item)
|
||||
if (Type === BaseItemKind.MusicArtist) queryClient.setQueryData(ArtistQueryKey(Id), item)
|
||||
|
||||
if (Type === BaseItemKind.MusicAlbum) warmAlbumContext(api, item)
|
||||
|
||||
@@ -64,13 +65,11 @@ function warmItemContext(
|
||||
staleTime: ONE_HOUR,
|
||||
})
|
||||
|
||||
if (queryClient.getQueryState(UserDataQueryKey(user, item.Id!))?.status !== 'success') {
|
||||
queryClient.ensureQueryData({
|
||||
queryKey: UserDataQueryKey(user, item.Id!),
|
||||
queryFn: () => fetchUserData(Id),
|
||||
staleTime: ONE_MINUTE * 15,
|
||||
})
|
||||
}
|
||||
queryClient.ensureQueryData({
|
||||
queryKey: UserDataQueryKey(user, item.Id!),
|
||||
queryFn: () => fetchUserData(Id),
|
||||
staleTime: ONE_MINUTE * 15,
|
||||
})
|
||||
}
|
||||
|
||||
function warmAlbumContext(api: Api | undefined, album: BaseItemDto): void {
|
||||
@@ -92,17 +91,10 @@ function warmArtistContext(api: Api | undefined, artistId: string): void {
|
||||
// Fail fast if we don't have an artist ID to work with
|
||||
if (!artistId) return
|
||||
|
||||
const queryKey = [QueryKeys.ArtistById, artistId]
|
||||
|
||||
// Bail out if we have data
|
||||
if (queryClient.getQueryState(queryKey)?.status === 'success') return
|
||||
|
||||
/**
|
||||
* Store queryable of artist item
|
||||
*/
|
||||
// ensureQueryData respects staleTime internally — no need to check getQueryState first
|
||||
queryClient.ensureQueryData({
|
||||
queryKey,
|
||||
queryFn: () => fetchItem(api, artistId!),
|
||||
queryKey: ArtistQueryKey(artistId),
|
||||
queryFn: () => fetchItem(api, artistId),
|
||||
staleTime: ONE_DAY,
|
||||
})
|
||||
}
|
||||
@@ -110,12 +102,10 @@ function warmArtistContext(api: Api | undefined, artistId: string): void {
|
||||
function warmTrackContext(api: Api | undefined, track: BaseItemDto): void {
|
||||
const { AlbumId, ArtistItems } = track
|
||||
|
||||
const albumQueryKey = [QueryKeys.Album, AlbumId]
|
||||
|
||||
if (AlbumId && queryClient.getQueryState(albumQueryKey)?.status !== 'success')
|
||||
if (AlbumId)
|
||||
queryClient.ensureQueryData({
|
||||
queryKey: albumQueryKey,
|
||||
queryFn: () => fetchItem(api, AlbumId!),
|
||||
queryKey: [QueryKeys.Album, AlbumId],
|
||||
queryFn: () => fetchItem(api, AlbumId),
|
||||
staleTime: ONE_DAY,
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user