diff --git a/navigation.ts b/navigation.ts index 6ec70baa..ae60d33b 100644 --- a/navigation.ts +++ b/navigation.ts @@ -2,10 +2,3 @@ import { createNavigationContainerRef } from '@react-navigation/native' import { RootStackParamList } from './src/screens/types' export const navigationRef = createNavigationContainerRef() - -export default function navigate( - name: keyof RootStackParamList, - params?: RootStackParamList[keyof RootStackParamList], -) { - if (navigationRef.isReady()) navigationRef.navigate(name, params) -} diff --git a/src/components/Album/index.tsx b/src/components/Album/index.tsx index d7f3da0a..69dcff22 100644 --- a/src/components/Album/index.tsx +++ b/src/components/Album/index.tsx @@ -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>() + const navigation = + useNavigation< + NativeStackNavigationProp< + HomeStackParamList | LibraryStackParamList | DiscoverStackParamList + > + >() return ( @@ -217,7 +225,12 @@ function AlbumTrackListHeader( } function AlbumTrackListFooter(album: BaseItemDto): React.JSX.Element { - const navigation = useNavigation>() + const navigation = + useNavigation< + NativeStackNavigationProp< + HomeStackParamList | LibraryStackParamList | DiscoverStackParamList + > + >() return ( diff --git a/src/components/Context/components/multiple-artists.tsx b/src/components/Context/components/multiple-artists.tsx index c6f2e808..cc8cf904 100644 --- a/src/components/Context/components/multiple-artists.tsx +++ b/src/components/Context/components/multiple-artists.tsx @@ -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 @@ -13,6 +13,7 @@ export default function MultipleArtists({ navigation, route, }: MultipleArtistsProps): React.JSX.Element { + const rootNavigation = useNavigation>() return ( { - 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, diff --git a/src/components/Context/index.tsx b/src/components/Context/index.tsx index 3184dd97..66251708 100644 --- a/src/components/Context/index.tsx +++ b/src/components/Context/index.tsx @@ -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 } -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 )} - {album && } + {(!isArtist || !isPlaylist) && ( + + )} - {artist && } + {!isPlaylist && ( + + )} ) @@ -149,8 +166,30 @@ function BackgroundGradient(): React.JSX.Element { return } -function ViewAlbumMenuRow({ item: album, navigation }: ContextProps): React.JSX.Element { - const rootNavigation = useNavigation>() +interface MenuRowProps { + item: BaseItemDto | undefined + stackNavigation?: NativeStackNavigationProp< + HomeStackParamList | LibraryStackParamList | DiscoverStackParamList + > + rootNavigation: NativeStackNavigationProp +} + +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 ( { - 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 }} > @@ -183,8 +207,22 @@ function ViewAlbumMenuRow({ item: album, navigation }: ContextProps): React.JSX. ) } -function ViewArtistMenuRow({ item: artist, navigation }: ContextProps): React.JSX.Element { - const rootNavigation = useNavigation>() +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 ( { - 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 }} > diff --git a/src/components/Library/component.tsx b/src/components/Library/component.tsx index 15cf7db5..4c38498e 100644 --- a/src/components/Library/component.tsx +++ b/src/components/Library/component.tsx @@ -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 - navigation: NativeStackNavigationProp -}): 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 }} /> ) diff --git a/src/components/Player/components/song-info.tsx b/src/components/Player/components/song-info.tsx index 4ff7b590..e573fe81 100644 --- a/src/components/Player/components/song-info.tsx +++ b/src/components/Player/components/song-info.tsx @@ -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', diff --git a/src/components/Playlist/components/header.tsx b/src/components/Playlist/components/header.tsx index c168a08d..edaf0e12 100644 --- a/src/components/Playlist/components/header.tsx +++ b/src/components/Playlist/components/header.tsx @@ -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, 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>() + 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 /> diff --git a/src/providers/Artist/index.tsx b/src/providers/Artist/index.tsx index cf372d80..b6ea897e 100644 --- a/src/providers/Artist/index.tsx +++ b/src/providers/Artist/index.tsx @@ -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 } -const ArtistContextInitializer = (artist: BaseItemDto) => { +const ArtistContext = createContext({ + fetchingAlbums: false, + fetchingFeaturedOn: false, + fetchingSimilarArtists: false, + artist: {}, + albums: [], + featuredOn: [], + similarArtists: [], + refresh: () => {}, + scroll: { value: 0 } as SharedValue, +}) + +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({ - fetchingAlbums: false, - fetchingFeaturedOn: false, - fetchingSimilarArtists: false, - artist: {}, - albums: [], - featuredOn: [], - similarArtists: [], - refresh: () => {}, - scroll: { value: 0 } as SharedValue, -}) + 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 {children} + return {children} } export const useArtistContext = () => useContext(ArtistContext) diff --git a/src/providers/Library/index.tsx b/src/providers/Library/index.tsx index 9c59078b..0ddef75f 100644 --- a/src/providers/Library/index.tsx +++ b/src/providers/Library/index.tsx @@ -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 {children} diff --git a/src/screens/Artist/index.tsx b/src/screens/Artist/index.tsx index f96e57d1..950d3906 100644 --- a/src/screens/Artist/index.tsx +++ b/src/screens/Artist/index.tsx @@ -11,10 +11,8 @@ export function ArtistScreen({ route: RouteProp navigation: NativeStackNavigationProp }): React.JSX.Element { - const { artist } = route.params - return ( - + ) diff --git a/src/screens/Context/index.tsx b/src/screens/Context/index.tsx index b3eacdba..7fabc654 100644 --- a/src/screens/Context/index.tsx +++ b/src/screens/Context/index.tsx @@ -1,6 +1,12 @@ import ItemContext from '../../components/Context' import { ContextProps } from '../types' -export default function ItemContextScreen({ route }: ContextProps): React.JSX.Element { - return +export default function ItemContextScreen({ route, navigation }: ContextProps): React.JSX.Element { + return ( + + ) } diff --git a/src/screens/Library/index.tsx b/src/screens/Library/index.tsx index 5013ee55..56f2dedc 100644 --- a/src/screens/Library/index.tsx +++ b/src/screens/Library/index.tsx @@ -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() 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 ( @@ -72,7 +103,7 @@ export default function LibraryStack({ route, navigation }: LibraryTabProps): Re - - export type LibraryArtistProps = NativeStackScreenProps export type LibraryAlbumProps = NativeStackScreenProps @@ -20,3 +21,11 @@ export type LibraryDeletePlaylistProps = NativeStackScreenProps< LibraryStackParamList, 'DeletePlaylist' > + +type LibraryNavigation = { + album?: BaseItemDto + artist?: BaseItemDto + playlist?: BaseItemDto +} + +export const LibraryNavigation: LibraryNavigation = {}