mirror of
https://github.com/Jellify-Music/App.git
synced 2025-12-30 15:29:49 -06:00
Performance improvements and navigation fixes
Performance fixes for library, artist screens Navigational fixes in the player Item Context optimization
This commit is contained in:
@@ -2719,7 +2719,7 @@ PODS:
|
||||
- RNWorklets
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- RNScreens (4.15.0):
|
||||
- RNScreens (4.15.2):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
@@ -2746,10 +2746,10 @@ PODS:
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- RNScreens/common (= 4.15.0)
|
||||
- RNScreens/common (= 4.15.2)
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- RNScreens/common (4.15.0):
|
||||
- RNScreens/common (4.15.2):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
@@ -3306,7 +3306,7 @@ SPEC CHECKSUMS:
|
||||
RNGestureHandler: 3a73f098d74712952870e948b3d9cf7b6cae9961
|
||||
RNReactNativeHapticFeedback: be4f1b4bf0398c30b59b76ed92ecb0a2ff3a69c6
|
||||
RNReanimated: ee96d03fe3713993a30cc205522792b4cb08e4f9
|
||||
RNScreens: 48bbaca97a5f9aedc3e52bd48673efd2b6aac4f6
|
||||
RNScreens: 8d88d38778e35ce95abeb228d3b5ea0c6e635cad
|
||||
RNSentry: 95e1ed0ede28a4af58aaafedeac9fcfaba0e89ce
|
||||
RNWorklets: e8335dff9d27004709f58316985769040cd1e8f2
|
||||
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
"react-native-pager-view": "^7.0.0",
|
||||
"react-native-reanimated": "4.0.2",
|
||||
"react-native-safe-area-context": "^5.6.0",
|
||||
"react-native-screens": "4.15.0",
|
||||
"react-native-screens": "4.15.2",
|
||||
"react-native-swipeable-item": "^2.0.9",
|
||||
"react-native-text-ticker": "^1.15.0",
|
||||
"react-native-toast-message": "^2.3.3",
|
||||
|
||||
@@ -54,6 +54,7 @@ export default function SimilarArtists(): React.JSX.Element {
|
||||
)
|
||||
}
|
||||
onScroll={scrollHandler}
|
||||
removeClippedSubviews
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import TextTicker from 'react-native-text-ticker'
|
||||
import { getToken, XStack, YStack } from 'tamagui'
|
||||
import { TextTickerConfig } from '../component.config'
|
||||
import { Text } from '../../Global/helpers/text'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import React, { useCallback, useMemo, memo } from 'react'
|
||||
import ItemImage from '../../Global/components/image'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
@@ -10,17 +9,13 @@ import { fetchItem } from '../../../api/queries/item'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import FavoriteButton from '../../Global/components/favorite-button'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { PlayerParamList } from '../../../screens/Player/types'
|
||||
import { useNowPlayingContext } from '../../../providers/Player'
|
||||
import navigationRef from '../../../../navigation'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { getItemName } from '../../../utils/text'
|
||||
import { CommonActions } from '@react-navigation/native'
|
||||
|
||||
interface SongInfoProps {
|
||||
navigation: NativeStackNavigationProp<PlayerParamList>
|
||||
}
|
||||
|
||||
function SongInfo({ navigation }: SongInfoProps): React.JSX.Element {
|
||||
export default function SongInfo(): React.JSX.Element {
|
||||
const { api } = useJellifyContext()
|
||||
const nowPlaying = useNowPlayingContext()
|
||||
|
||||
@@ -43,39 +38,25 @@ function SongInfo({ navigation }: SongInfoProps): React.JSX.Element {
|
||||
// Memoize navigation handlers
|
||||
const handleAlbumPress = useCallback(() => {
|
||||
if (album) {
|
||||
navigation.goBack() // Dismiss player modal
|
||||
navigationRef.navigate('Tabs', {
|
||||
screen: 'LibraryTab',
|
||||
params: {
|
||||
screen: 'Album',
|
||||
params: {
|
||||
album,
|
||||
},
|
||||
},
|
||||
})
|
||||
navigationRef.goBack() // Dismiss player modal
|
||||
navigationRef.dispatch(CommonActions.navigate('Album', { album }))
|
||||
}
|
||||
}, [album, navigation])
|
||||
}, [album])
|
||||
|
||||
const handleArtistPress = useCallback(() => {
|
||||
if (artistItems) {
|
||||
if (artistItems.length > 1) {
|
||||
navigation.navigate('MultipleArtistsSheet', {
|
||||
artists: artistItems,
|
||||
})
|
||||
navigationRef.dispatch(
|
||||
CommonActions.navigate('MultipleArtistsSheet', {
|
||||
artists: artistItems,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
navigation.goBack() // Dismiss player modal
|
||||
navigationRef.navigate('Tabs', {
|
||||
screen: 'LibraryTab',
|
||||
params: {
|
||||
screen: 'Artist',
|
||||
params: {
|
||||
artist: artistItems[0],
|
||||
},
|
||||
},
|
||||
})
|
||||
navigationRef.goBack() // Dismiss player modal
|
||||
navigationRef.dispatch(CommonActions.navigate('Artist', { artist: artistItems[0] }))
|
||||
}
|
||||
}
|
||||
}, [artistItems, navigation])
|
||||
}, [artistItems])
|
||||
|
||||
return (
|
||||
<XStack>
|
||||
@@ -108,9 +89,3 @@ function SongInfo({ navigation }: SongInfoProps): React.JSX.Element {
|
||||
</XStack>
|
||||
)
|
||||
}
|
||||
|
||||
// Memoize the component to prevent unnecessary re-renders
|
||||
export default memo(SongInfo, (prevProps: SongInfoProps, nextProps: SongInfoProps) => {
|
||||
// Only re-render if navigation changes (which it shouldn't)
|
||||
return prevProps.navigation === nextProps.navigation
|
||||
})
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
import { useNowPlayingContext } from '../../providers/Player'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import {
|
||||
YStack,
|
||||
XStack,
|
||||
getToken,
|
||||
useTheme,
|
||||
ZStack,
|
||||
useWindowDimensions,
|
||||
View,
|
||||
getTokenValue,
|
||||
} from 'tamagui'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { YStack, useTheme, ZStack, useWindowDimensions, View, getTokenValue } from 'tamagui'
|
||||
import Scrubber from './components/scrubber'
|
||||
import Controls from './components/controls'
|
||||
import Toast from 'react-native-toast-message'
|
||||
@@ -21,15 +12,9 @@ import BlurredBackground from './components/blurred-background'
|
||||
import PlayerHeader from './components/header'
|
||||
import SongInfo from './components/song-info'
|
||||
import { usePerformanceMonitor } from '../../hooks/use-performance-monitor'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { PlayerParamList } from '../../screens/Player/types'
|
||||
import { Platform } from 'react-native'
|
||||
|
||||
export default function PlayerScreen({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<PlayerParamList>
|
||||
}): React.JSX.Element {
|
||||
export default function PlayerScreen(): React.JSX.Element {
|
||||
const performanceMetrics = usePerformanceMonitor('PlayerScreen', 5)
|
||||
|
||||
const [showToast, setShowToast] = useState(true)
|
||||
@@ -84,7 +69,7 @@ export default function PlayerScreen({
|
||||
<PlayerHeader />
|
||||
|
||||
<YStack justifyContent='flex-start' gap={'$4'} flexShrink={1}>
|
||||
<SongInfo navigation={navigation} />
|
||||
<SongInfo />
|
||||
|
||||
<Scrubber />
|
||||
{/* playback progress goes here */}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BaseItemDto, BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { createContext, ReactNode, useEffect } from 'react'
|
||||
import { createContext, ReactNode, useEffect, useRef } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { fetchMediaInfo } from '../../api/queries/media'
|
||||
@@ -11,6 +11,7 @@ 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'
|
||||
import { usePerformanceMonitor } from '../../hooks/use-performance-monitor'
|
||||
|
||||
interface ItemContext {
|
||||
item: BaseItemDto
|
||||
@@ -43,6 +44,8 @@ export const ItemProvider: ({ item, children }: ItemProviderProps) => React.JSX.
|
||||
item,
|
||||
children,
|
||||
}) => {
|
||||
const perfMonitor = usePerformanceMonitor('ItemProvider', 5)
|
||||
|
||||
const { api, user } = useJellifyContext()
|
||||
|
||||
const streamingQuality = useStreamingQualityContext()
|
||||
@@ -51,9 +54,20 @@ export const ItemProvider: ({ item, children }: ItemProviderProps) => React.JSX.
|
||||
|
||||
const artistIds = ArtistItems?.map(({ Id }) => Id) ?? []
|
||||
|
||||
const prefetchedContext = useRef<Record<string, true>>({})
|
||||
|
||||
useEffect(() => {
|
||||
// Fail fast if we don't have an Item ID to work with
|
||||
if (!Id) return
|
||||
|
||||
const effectSig = `${Id}-${Type}`
|
||||
|
||||
// If we've already warmed the cache for this item, return
|
||||
if (prefetchedContext.current[effectSig]) return
|
||||
prefetchedContext.current[effectSig] = true
|
||||
|
||||
console.debug(`Warming context query cache for item ${Id}`)
|
||||
|
||||
/**
|
||||
* Fetch and cache the media sources if this item is a track
|
||||
*/
|
||||
@@ -123,7 +137,7 @@ export const ItemProvider: ({ item, children }: ItemProviderProps) => React.JSX.
|
||||
queryKey: [QueryKeys.UserData, Id],
|
||||
queryFn: () => fetchUserData(api, user, Id),
|
||||
})
|
||||
}, [queryClient, api, user, Id, Type, AlbumId, UserData, item, streamingQuality])
|
||||
}, [queryClient, api?.basePath, user?.id, Id, streamingQuality])
|
||||
|
||||
return (
|
||||
<ItemContext.Provider value={{ item }}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createContext, useEffect } from 'react'
|
||||
import { createContext, useEffect, useRef } from 'react'
|
||||
import { useJellifyContext } from '..'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { fetchItem } from '../../api/queries/item'
|
||||
@@ -19,16 +19,28 @@ export const ItemArtistProvider: ({
|
||||
}) => React.JSX.Element = ({ artistId }) => {
|
||||
const { api } = useJellifyContext()
|
||||
|
||||
const prefetchedContext = useRef<Record<string, true>>({})
|
||||
|
||||
useEffect(() => {
|
||||
// Fail fast if we don't have an artist ID to work with
|
||||
if (!artistId) return
|
||||
|
||||
const effectSig = artistId
|
||||
|
||||
// If we've already warmed the cache for this artist, return
|
||||
if (prefetchedContext.current[effectSig]) return
|
||||
|
||||
prefetchedContext.current[effectSig] = true
|
||||
|
||||
console.debug(`Warming context cache for artist ${artistId}`)
|
||||
/**
|
||||
* Store queryable of artist item
|
||||
*/
|
||||
if (artistId)
|
||||
queryClient.ensureQueryData({
|
||||
queryKey: [QueryKeys.ArtistById, artistId],
|
||||
queryFn: () => fetchItem(api, artistId!),
|
||||
})
|
||||
})
|
||||
queryClient.ensureQueryData({
|
||||
queryKey: [QueryKeys.ArtistById, artistId],
|
||||
queryFn: () => fetchItem(api, artistId!),
|
||||
})
|
||||
}, [api, artistId])
|
||||
|
||||
return <ItemArtistContext.Provider value={{ artistId }} />
|
||||
}
|
||||
|
||||
@@ -348,11 +348,6 @@ const QueueContextInitailizer = () => {
|
||||
console.debug(
|
||||
`Queued ${queue.length} tracks, starting at ${finalStartIndex}${shuffleQueue ? ' (shuffled)' : ''}`,
|
||||
)
|
||||
|
||||
// Set skipping to false after a short delay to prevent flickering
|
||||
// IDK why this needs to be 1000ms, but there are a lot of events are emitted
|
||||
// by RNTP at this time so we need to wait for it to settle
|
||||
setTimeout(() => setSkipping(false), 1000)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -547,6 +542,10 @@ const QueueContextInitailizer = () => {
|
||||
trigger('notificationSuccess')
|
||||
console.debug(`Loaded new queue`)
|
||||
|
||||
// Set skipping to false after a short delay to prevent flickering
|
||||
// IDK why this needs to be 500ms, but there are a lot of events are emitted
|
||||
// by RNTP at this time so we need to wait for it to settle
|
||||
setTimeout(() => setSkipping(false), 500)
|
||||
if (startPlayback) await TrackPlayer.play()
|
||||
},
|
||||
onError: async (error: Error) => {
|
||||
|
||||
@@ -27,6 +27,7 @@ export default function Tabs({ route, navigation }: TabProps): React.JSX.Element
|
||||
animation: 'shift',
|
||||
tabBarActiveTintColor: theme.primary.val,
|
||||
tabBarInactiveTintColor: theme.neutral.val,
|
||||
lazy: true,
|
||||
}}
|
||||
tabBar={(props) => (
|
||||
<>
|
||||
@@ -63,7 +64,6 @@ export default function Tabs({ route, navigation }: TabProps): React.JSX.Element
|
||||
<MaterialDesignIcons name='music-box-multiple' color={color} size={size} />
|
||||
),
|
||||
tabBarButtonTestID: 'library-tab-button',
|
||||
lazy: false, // Load on mount since we need to be able to navigate here from the player
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -8523,10 +8523,10 @@ react-native-safe-area-context@^5.6.0:
|
||||
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.6.0.tgz#0ab284c291bb57d59330abf7dfe65156d6340e78"
|
||||
integrity sha512-tJas3YOdsuCg3kepCTGF3LWZp9onMbb9Agju2xfs2kRX8d/5TMUPmupBpjerk/B7Tv/zeJnk+qp5neA96Y0otQ==
|
||||
|
||||
react-native-screens@4.15.0:
|
||||
version "4.15.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.15.0.tgz#8b34056b72a2c92da4de2f77419a116154c88e81"
|
||||
integrity sha512-LPz+9qWDfwY3pPKojrWPcQnb2sAq9cy/qA8ZAS14ksSdzqFkTTUbs1as2WGBo7xBtdx5Ht78bF9nNh8libX1Xw==
|
||||
react-native-screens@4.15.2:
|
||||
version "4.15.2"
|
||||
resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.15.2.tgz#93ba015f5167a2fb5e2e2b71807f083ec8ed1ef2"
|
||||
integrity sha512-RA9fUT/5OTPJ2ML3BNUbe8UtfcU7iufE+r9sLr/IesFdBDj38bZiqmH88iorI5Vfgp7O3zf2aK390Tbkfp9Xfw==
|
||||
dependencies:
|
||||
react-freeze "^1.0.0"
|
||||
react-native-is-edge-to-edge "^1.2.1"
|
||||
|
||||
Reference in New Issue
Block a user