library navigation jumps

This commit is contained in:
Violet Caulfield
2025-08-14 18:18:09 -05:00
parent 2748e74bf1
commit a2e3237766
13 changed files with 205 additions and 140 deletions
-7
View File
@@ -2,10 +2,3 @@ import { createNavigationContainerRef } from '@react-navigation/native'
import { RootStackParamList } from './src/screens/types'
export const navigationRef = createNavigationContainerRef<RootStackParamList>()
export default function navigate(
name: keyof RootStackParamList,
params?: RootStackParamList[keyof RootStackParamList],
) {
if (navigationRef.isReady()) navigationRef.navigate(name, params)
}
+15 -2
View File
@@ -22,6 +22,9 @@ import { QueuingType } from '../../enums/queuing-type'
import { useAlbumContext } from '../../providers/Album'
import { useNavigation } from '@react-navigation/native'
import { isUndefined } from 'lodash'
import HomeStackParamList from '@/src/screens/Home/types'
import LibraryStackParamList from '@/src/screens/Library/types'
import DiscoverStackParamList from '@/src/screens/Discover/types'
/**
* The screen for an Album's track list
@@ -136,7 +139,12 @@ function AlbumTrackListHeader(
): React.JSX.Element {
const { width } = useSafeAreaFrame()
const navigation = useNavigation<NativeStackNavigationProp<BaseStackParamList>>()
const navigation =
useNavigation<
NativeStackNavigationProp<
HomeStackParamList | LibraryStackParamList | DiscoverStackParamList
>
>()
return (
<YStack marginTop={'$4'} alignItems='center'>
@@ -217,7 +225,12 @@ function AlbumTrackListHeader(
}
function AlbumTrackListFooter(album: BaseItemDto): React.JSX.Element {
const navigation = useNavigation<NativeStackNavigationProp<BaseStackParamList>>()
const navigation =
useNavigation<
NativeStackNavigationProp<
HomeStackParamList | LibraryStackParamList | DiscoverStackParamList
>
>()
return (
<YStack marginLeft={'$2'}>
@@ -2,8 +2,8 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import ItemRow from '../../Global/components/item-row'
import { FlashList } from '@shopify/flash-list'
import { PlayerParamList } from '../../../screens/Player/types'
import { RouteProp } from '@react-navigation/native'
import navigate from '../../../../navigation'
import { RouteProp, useNavigation } from '@react-navigation/native'
import { RootStackParamList } from '../../../screens/types'
interface MultipleArtistsProps {
navigation: NativeStackNavigationProp<PlayerParamList, 'MultipleArtists'>
@@ -13,6 +13,7 @@ export default function MultipleArtists({
navigation,
route,
}: MultipleArtistsProps): React.JSX.Element {
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
return (
<FlashList
data={route.params.artists}
@@ -23,11 +24,11 @@ export default function MultipleArtists({
item={artist}
queueName={''}
onPress={() => {
navigation.goBack() // Dismiss multiple artists modal
navigation.goBack() // Dismiss player modal
navigate('Tabs', {
navigation.popToTop()
rootNavigation.popTo('Tabs', {
screen: 'Library',
param: {
params: {
screen: 'Artist',
params: {
artist,
+66 -43
View File
@@ -1,5 +1,4 @@
import { BaseItemDto, BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models'
import { useNavigation } from '@react-navigation/native'
import { getToken, ListItem, YGroup, ZStack } from 'tamagui'
import { RootStackParamList } from '../../screens/types'
import { Text } from '../Global/helpers/text'
@@ -19,18 +18,24 @@ 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 LibraryStackParamList from '../../screens/Library/types'
import DiscoverStackParamList from '@/src/screens/Discover/types'
import LibraryStackParamList, { LibraryNavigation } from '../../screens/Library/types'
import DiscoverStackParamList from '../../screens/Discover/types'
import HomeStackParamList from '../../screens/Home/types'
import { useCallback } from 'react'
interface ContextProps {
item: BaseItemDto
navigation?: NativeStackNavigationProp<
stackNavigation?: NativeStackNavigationProp<
HomeStackParamList | LibraryStackParamList | DiscoverStackParamList
>
navigation: NativeStackNavigationProp<RootStackParamList>
}
export default function ItemContext({ item, navigation }: ContextProps): React.JSX.Element {
export default function ItemContext({
item,
stackNavigation,
navigation,
}: ContextProps): React.JSX.Element {
const { api, user, library } = useJellifyContext()
const isArtist = item.Type === BaseItemKind.MusicArtist
@@ -76,9 +81,21 @@ export default function ItemContext({ item, navigation }: ContextProps): React.J
<AddToQueueMenuRow tracks={isTrack ? [item] : tracks} />
)}
{album && <ViewAlbumMenuRow item={album} navigation={navigation} />}
{(!isArtist || !isPlaylist) && (
<ViewAlbumMenuRow
item={isAlbum ? item : album!}
stackNavigation={stackNavigation}
rootNavigation={navigation}
/>
)}
{artist && <ViewArtistMenuRow item={artist} navigation={navigation} />}
{!isPlaylist && (
<ViewArtistMenuRow
item={isArtist ? item : artist}
stackNavigation={stackNavigation}
rootNavigation={navigation}
/>
)}
</YGroup>
</ZStack>
)
@@ -149,8 +166,30 @@ function BackgroundGradient(): React.JSX.Element {
return <LinearGradient style={{ flex: 1 }} colors={gradientColors} />
}
function ViewAlbumMenuRow({ item: album, navigation }: ContextProps): React.JSX.Element {
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
interface MenuRowProps {
item: BaseItemDto | undefined
stackNavigation?: NativeStackNavigationProp<
HomeStackParamList | LibraryStackParamList | DiscoverStackParamList
>
rootNavigation: NativeStackNavigationProp<RootStackParamList>
}
function ViewAlbumMenuRow({
item: album,
stackNavigation,
rootNavigation,
}: MenuRowProps): React.JSX.Element {
const goToAlbum = useCallback(() => {
if (stackNavigation && album) stackNavigation.navigate('Album', { album })
else if (album) {
rootNavigation.popTo('Tabs', {
screen: 'Library',
merge: true,
})
LibraryNavigation.album = album
}
}, [album, stackNavigation, rootNavigation])
return (
<ListItem
@@ -158,22 +197,7 @@ function ViewAlbumMenuRow({ item: album, navigation }: ContextProps): React.JSX.
backgroundColor={'transparent'}
gap={'$2'}
justifyContent='flex-start'
onPress={() => {
if (navigation) navigation.navigate('Album', { album })
else {
rootNavigation.goBack()
rootNavigation.goBack()
rootNavigation.navigate('Tabs', {
screen: 'Library',
params: {
screen: 'Album',
params: {
album,
},
},
})
}
}}
onPress={goToAlbum}
pressStyle={{ opacity: 0.5 }}
>
<Icon color='$primary' name='disc' />
@@ -183,8 +207,22 @@ function ViewAlbumMenuRow({ item: album, navigation }: ContextProps): React.JSX.
)
}
function ViewArtistMenuRow({ item: artist, navigation }: ContextProps): React.JSX.Element {
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
function ViewArtistMenuRow({
item: artist,
stackNavigation,
rootNavigation,
}: MenuRowProps): React.JSX.Element {
const goToArtist = useCallback(() => {
if (stackNavigation && artist) stackNavigation.navigate('Artist', { artist })
else if (artist) {
rootNavigation.popTo('Tabs', {
screen: 'Library',
merge: true,
})
LibraryNavigation.artist = artist
}
}, [artist, stackNavigation, rootNavigation])
return (
<ListItem
@@ -192,22 +230,7 @@ function ViewArtistMenuRow({ item: artist, navigation }: ContextProps): React.JS
backgroundColor={'transparent'}
gap={'$2'}
justifyContent='flex-start'
onPress={() => {
if (navigation) navigation.navigate('Artist', { artist })
else {
rootNavigation.goBack()
rootNavigation.goBack()
rootNavigation.navigate('Tabs', {
screen: 'Library',
params: {
screen: 'Artist',
params: {
artist,
},
},
})
}
}}
onPress={goToArtist}
pressStyle={{ opacity: 0.5 }}
>
<Icon color='$primary' name='microphone-variant' />
+3 -12
View File
@@ -6,19 +6,12 @@ import TracksTab from './components/tracks-tab'
import ArtistsTab from './components/artists-tab'
import AlbumsTab from './components/albums-tab'
import LibraryTabBar from './tab-bar'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { RouteProp } from '@react-navigation/native'
import LibraryStackParamList from '../../screens/Library/types'
import { LibraryScreenProps } from '../../screens/Library/types'
import React from 'react'
const LibraryTabsNavigator = createMaterialTopTabNavigator()
export default function Library({
route,
navigation,
}: {
route: RouteProp<LibraryStackParamList, 'Library'>
navigation: NativeStackNavigationProp<LibraryStackParamList, 'Library'>
}): React.JSX.Element {
export default function Library({ route, navigation }: LibraryScreenProps): React.JSX.Element {
const theme = useTheme()
return (
@@ -65,7 +58,6 @@ export default function Library({
),
tabBarButtonTestID: 'library-albums-tab-button',
}}
initialParams={{ navigation }}
/>
<LibraryTabsNavigator.Screen
@@ -96,7 +88,6 @@ export default function Library({
),
tabBarButtonTestID: 'library-playlists-tab-button',
}}
initialParams={{ navigation }}
/>
</LibraryTabsNavigator.Navigator>
)
@@ -14,7 +14,6 @@ import FavoriteButton from '../../Global/components/favorite-button'
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
import Icon from '../../Global/components/icon'
import { useNavigation } from '@react-navigation/native'
import navigate from '../../../../navigation'
import { QueryKeys } from '../../../enums/query-keys'
import { BaseItemDto, BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models'
@@ -46,9 +45,7 @@ export default function SongInfo(): React.JSX.Element {
marginHorizontal={'$1.5'}
onPress={() => {
if (album) {
navigation.goBack() // Dismiss player modal
navigate('Tabs', {
navigation.popTo('Tabs', {
screen: 'Library',
params: {
screen: 'Album',
@@ -93,8 +90,7 @@ export default function SongInfo(): React.JSX.Element {
artists: nowPlaying!.item.ArtistItems!,
})
} else {
navigation.goBack() // Dismiss player modal
navigate('Tabs', {
navigation.popTo('Tabs', {
screen: 'Library',
params: {
screen: 'Artist',
@@ -18,11 +18,12 @@ import { mapDtoToTrack } from '../../../utils/mappings'
import { useLoadQueueContext } from '../../../providers/Player/queue'
import { QueuingType } from '../../../enums/queuing-type'
import { useDownloadQualityContext, useStreamingQualityContext } from '../../../providers/Settings'
import navigate from '../../../../navigation'
import { useNavigation } from '@react-navigation/native'
import LibraryStackParamList from '@/src/screens/Library/types'
import DiscoverStackParamList from '@/src/screens/Discover/types'
export default function PlayliistTracklistHeader(
playlist: BaseItemDto,
navigation: NativeStackNavigationProp<BaseStackParamList>,
editing: boolean,
playlistTracks: BaseItemDto[],
canEdit: boolean | undefined,
@@ -155,6 +156,8 @@ function PlaylistHeaderControls({
const isDownloading = pendingDownloads.length != 0
const { sessionId, api } = useJellifyContext()
const navigation = useNavigation<NativeStackNavigationProp<LibraryStackParamList>>()
const downloadPlaylist = () => {
if (!api || !sessionId) return
const jellifyTracks = playlistTracks.map((item) =>
@@ -185,13 +188,7 @@ function PlaylistHeaderControls({
color={'$danger'}
name='delete-sweep-outline' // otherwise use "delete-circle"
onPress={() => {
navigate('Tabs', {
screen: 'Library',
params: {
screen: 'DeletePlaylist',
params: { playlist },
},
})
navigation.push('DeletePlaylist', { playlist })
}}
small
/>
+50 -40
View File
@@ -2,7 +2,7 @@ import fetchSimilar from '../../api/queries/similar'
import { QueryKeys } from '../../enums/query-keys'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import { useQuery } from '@tanstack/react-query'
import { createContext, ReactNode, SetStateAction, useContext, useState } from 'react'
import { createContext, ReactNode, useCallback, useContext, useMemo } from 'react'
import { SharedValue, useSharedValue } from 'react-native-reanimated'
import { useJellifyContext } from '..'
import { fetchArtistAlbums, fetchArtistFeaturedOn } from '../../api/queries/artist'
@@ -19,7 +19,25 @@ interface ArtistContext {
scroll: SharedValue<number>
}
const ArtistContextInitializer = (artist: BaseItemDto) => {
const ArtistContext = createContext<ArtistContext>({
fetchingAlbums: false,
fetchingFeaturedOn: false,
fetchingSimilarArtists: false,
artist: {},
albums: [],
featuredOn: [],
similarArtists: [],
refresh: () => {},
scroll: { value: 0 } as SharedValue<number>,
})
export const ArtistProvider = ({
artist,
children,
}: {
artist: BaseItemDto
children: ReactNode
}) => {
const { api, library, user } = useJellifyContext()
const {
@@ -42,55 +60,47 @@ const ArtistContextInitializer = (artist: BaseItemDto) => {
const {
data: similarArtists,
refetch: refetchRefetchSimilarArtists,
refetch: refetchSimilar,
isPending: fetchingSimilarArtists,
} = useQuery({
queryKey: [QueryKeys.SimilarItems, library?.musicLibraryId, artist.Id],
queryKey: [QueryKeys.SimilarItems, library?.musicLibraryId, artist.Id!],
queryFn: () => fetchSimilar(api, user, library?.musicLibraryId, artist.Id!),
})
const refresh = () => {
const refresh = useCallback(() => {
refetchAlbums()
refetchFeaturedOn()
refetchRefetchSimilarArtists()
}
refetchSimilar()
}, [refetchAlbums, refetchFeaturedOn, refetchSimilar])
const scroll = useSharedValue(0)
return {
artist,
albums,
featuredOn,
similarArtists,
fetchingAlbums,
fetchingFeaturedOn,
fetchingSimilarArtists,
refresh,
scroll,
}
}
const ArtistContext = createContext<ArtistContext>({
fetchingAlbums: false,
fetchingFeaturedOn: false,
fetchingSimilarArtists: false,
artist: {},
albums: [],
featuredOn: [],
similarArtists: [],
refresh: () => {},
scroll: { value: 0 } as SharedValue<number>,
})
const value = useMemo(
() => ({
artist,
albums,
featuredOn,
similarArtists,
fetchingAlbums,
fetchingFeaturedOn,
fetchingSimilarArtists,
refresh,
scroll,
}),
[
artist,
albums,
featuredOn,
similarArtists,
fetchingAlbums,
fetchingFeaturedOn,
fetchingSimilarArtists,
refresh,
scroll,
],
)
export const ArtistProvider: ({
artist,
children,
}: {
artist: BaseItemDto
children: ReactNode
}) => React.JSX.Element = ({ artist, children }) => {
const context = ArtistContextInitializer(artist)
return <ArtistContext.Provider value={{ ...context }}>{children}</ArtistContext.Provider>
return <ArtistContext.Provider value={value}>{children}</ArtistContext.Provider>
}
export const useArtistContext = () => useContext(ArtistContext)
+4
View File
@@ -380,9 +380,13 @@ export const LibraryProvider = ({ children }: { children: React.ReactNode }) =>
() => context,
[
context.artistsInfiniteQuery.data,
context.artistsInfiniteQuery.isPending,
context.tracksInfiniteQuery.data,
context.tracksInfiniteQuery.isPending,
context.albumsInfiniteQuery.data,
context.albumsInfiniteQuery.isPending,
context.playlistsInfiniteQuery.data,
context.playlistsInfiniteQuery.isPending,
],
)
return <LibraryContext.Provider value={value}>{children}</LibraryContext.Provider>
+1 -3
View File
@@ -11,10 +11,8 @@ export function ArtistScreen({
route: RouteProp<BaseStackParamList, 'Artist'>
navigation: NativeStackNavigationProp<BaseStackParamList, 'Artist'>
}): React.JSX.Element {
const { artist } = route.params
return (
<ArtistProvider artist={artist}>
<ArtistProvider artist={route.params.artist}>
<ArtistNavigation />
</ArtistProvider>
)
+8 -2
View File
@@ -1,6 +1,12 @@
import ItemContext from '../../components/Context'
import { ContextProps } from '../types'
export default function ItemContextScreen({ route }: ContextProps): React.JSX.Element {
return <ItemContext item={route.params.item} navigation={route.params.navigation} />
export default function ItemContextScreen({ route, navigation }: ContextProps): React.JSX.Element {
return (
<ItemContext
item={route.params.item}
stackNavigation={route.params.navigation}
navigation={navigation}
/>
)
}
+34 -10
View File
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useEffect } from 'react'
import Library from '../../components/Library/component'
import { PlaylistScreen } from '../Playlist'
import AddPlaylist from './add-playlist'
@@ -9,14 +9,45 @@ import { LibraryProvider } from '../../providers/Library'
import { LibrarySortAndFilterProvider } from '../../providers/Library/sorting-filtering'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import AlbumScreen from '../Album'
import LibraryStackParamList from './types'
import LibraryStackParamList, { LibraryNavigation } from './types'
import { LibraryTabProps } from '../Tabs/types'
import { useIsFocused } from '@react-navigation/native'
const Stack = createNativeStackNavigator<LibraryStackParamList>()
export default function LibraryStack({ route, navigation }: LibraryTabProps): React.JSX.Element {
const theme = useTheme()
const isFocused = useIsFocused()
useEffect(() => {
if (!isFocused) return
if (LibraryNavigation.album) {
navigation.navigate('Library', {
screen: 'Album',
params: { album: LibraryNavigation.album },
})
LibraryNavigation.album = undefined
}
if (LibraryNavigation.artist) {
navigation.navigate('Library', {
screen: 'Artist',
params: { artist: LibraryNavigation.artist },
})
LibraryNavigation.artist = undefined
}
if (LibraryNavigation.playlist) {
navigation.navigate('Library', {
screen: 'Playlist',
params: { playlist: LibraryNavigation.playlist },
})
LibraryNavigation.playlist = undefined
}
}, [isFocused])
return (
<LibrarySortAndFilterProvider>
<LibraryProvider>
@@ -72,7 +103,7 @@ export default function LibraryStack({ route, navigation }: LibraryTabProps): Re
<Stack.Group
screenOptions={{
presentation: 'formSheet',
sheetAllowedDetents: [0.35],
sheetAllowedDetents: 'fitToContents',
}}
>
<Stack.Screen
@@ -82,14 +113,7 @@ export default function LibraryStack({ route, navigation }: LibraryTabProps): Re
title: 'Add Playlist',
}}
/>
</Stack.Group>
<Stack.Group
screenOptions={{
presentation: 'formSheet',
sheetAllowedDetents: [0.2],
}}
>
<Stack.Screen
name='DeletePlaylist'
component={DeletePlaylist}
@@ -12,6 +12,7 @@ type LibraryStackParamList = BaseStackParamList & {
export default LibraryStackParamList
export type LibraryScreenProps = NativeStackScreenProps<LibraryStackParamList, 'Library'>
export type LibraryArtistProps = NativeStackScreenProps<LibraryStackParamList, 'Artist'>
export type LibraryAlbumProps = NativeStackScreenProps<LibraryStackParamList, 'Album'>
@@ -20,3 +21,11 @@ export type LibraryDeletePlaylistProps = NativeStackScreenProps<
LibraryStackParamList,
'DeletePlaylist'
>
type LibraryNavigation = {
album?: BaseItemDto
artist?: BaseItemDto
playlist?: BaseItemDto
}
export const LibraryNavigation: LibraryNavigation = {}