mirror of
https://github.com/Jellify-Music/App.git
synced 2025-12-30 07:20:06 -06:00
Maintenance/migrate jellify context to zustand (#640)
Remove `JellifyContext` provider in favor of a persisted Zustand store This should address issues where the user would open the app and be randomly signed out and have to sign back in
This commit is contained in:
@@ -5,13 +5,12 @@ import CarPlay
|
||||
class CarSceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate {
|
||||
func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, didConnect interfaceController: CPInterfaceController) {
|
||||
|
||||
RNCarPlay.connect(with: interfaceController, window: templateApplicationScene.carWindow);
|
||||
RNCarPlay.connect(with: interfaceController, window: templateApplicationScene.carWindow)
|
||||
|
||||
}
|
||||
|
||||
func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, didDisconnectInterfaceController interfaceController: CPInterfaceController) {
|
||||
|
||||
RNCarPlay.disconnect()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,8 +93,6 @@
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
CFE47DDB2EA56B0200EB6067 /* icons */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
path = icons;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -397,10 +395,14 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Jellify/Pods-Jellify-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Jellify/Pods-Jellify-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Jellify/Pods-Jellify-frameworks.sh\"\n";
|
||||
@@ -414,10 +416,14 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Jellify/Pods-Jellify-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Jellify/Pods-Jellify-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Jellify/Pods-Jellify-resources.sh\"\n";
|
||||
@@ -697,10 +703,7 @@
|
||||
"-DFOLLY_CFG_NO_COROUTINES=1",
|
||||
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
@@ -787,10 +790,7 @@
|
||||
"-DFOLLY_CFG_NO_COROUTINES=1",
|
||||
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react-native'
|
||||
import { JellifyProvider, useJellifyContext } from '../../src/providers'
|
||||
import { Text, View } from 'react-native'
|
||||
import { MMKVStorageKeys } from '../../src/enums/mmkv-storage-keys'
|
||||
import { storage } from '../../src/constants/storage'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
const JellifyConsumer = () => {
|
||||
const { server, user, library } = useJellifyContext()
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text testID='api-base-path'>{server?.url}</Text>
|
||||
<Text testID='user-name'>{user?.name}</Text>
|
||||
<Text testID='library-name'>{library?.musicLibraryName}</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
test(`${JellifyProvider.name} renders correctly`, async () => {
|
||||
storage.set(
|
||||
MMKVStorageKeys.Server,
|
||||
JSON.stringify({
|
||||
url: 'http://localhost:8096',
|
||||
}),
|
||||
)
|
||||
|
||||
storage.set(
|
||||
MMKVStorageKeys.User,
|
||||
JSON.stringify({
|
||||
name: 'Violet Caulfield',
|
||||
}),
|
||||
)
|
||||
|
||||
storage.set(
|
||||
MMKVStorageKeys.Library,
|
||||
JSON.stringify({
|
||||
musicLibraryName: 'Music Library',
|
||||
}),
|
||||
)
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<JellifyProvider>
|
||||
<JellifyConsumer />
|
||||
</JellifyProvider>
|
||||
,
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
|
||||
const apiBasePath = screen.getByTestId('api-base-path')
|
||||
const userName = screen.getByTestId('user-name')
|
||||
const libraryName = screen.getByTestId('library-name')
|
||||
|
||||
await waitFor(() => {
|
||||
expect(apiBasePath.props.children).toBe('http://localhost:8096')
|
||||
expect(userName.props.children).toBe('Violet Caulfield')
|
||||
expect(libraryName.props.children).toBe('Music Library')
|
||||
})
|
||||
})
|
||||
@@ -4,16 +4,13 @@ import { render } from '@testing-library/react-native'
|
||||
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { PlayerProvider } from '../../src/providers/Player'
|
||||
import { JellifyProvider } from '../../src/providers'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
test(`${PlayerProvider.name} renders correctly`, () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<JellifyProvider>
|
||||
<PlayerProvider />
|
||||
</JellifyProvider>
|
||||
<PlayerProvider />
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
})
|
||||
|
||||
@@ -14,9 +14,9 @@ appId: com.cosmonautical.jellify
|
||||
|
||||
# Test App (Preferences) Tab - should already be selected
|
||||
- assertVisible:
|
||||
text: "Send Metrics and Crash Reports"
|
||||
text: "Send Analytics"
|
||||
- assertVisible:
|
||||
text: "Send anonymous usage and crash data"
|
||||
text: "Send usage and crash data"
|
||||
- assertVisible:
|
||||
text: "Reduce Haptics"
|
||||
- assertVisible:
|
||||
|
||||
@@ -2,9 +2,10 @@ import { AxiosResponse } from 'axios'
|
||||
import { JellyfinCredentials } from '../../types/jellyfin-credentials'
|
||||
import { AuthenticationResult } from '@jellyfin/sdk/lib/generated-client'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { JellifyUser } from '../../../types/JellifyUser'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { getUserApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { useApi, useJellifyUser } from '../../../stores'
|
||||
|
||||
interface AuthenticateUserByNameMutation {
|
||||
onSuccess?: () => void
|
||||
@@ -12,11 +13,17 @@ interface AuthenticateUserByNameMutation {
|
||||
}
|
||||
|
||||
const useAuthenticateUserByName = ({ onSuccess, onError }: AuthenticateUserByNameMutation) => {
|
||||
const { api, setUser } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user, setUser] = useJellifyUser()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (credentials: JellyfinCredentials) => {
|
||||
return await api!.authenticateUserByName(credentials.username, credentials.password)
|
||||
return await getUserApi(api!).authenticateUserByName({
|
||||
authenticateUserByName: {
|
||||
Username: credentials.username,
|
||||
Pw: credentials.password,
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess: async (authResult: AxiosResponse<AuthenticationResult>) => {
|
||||
console.log(`Received auth response from server`)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { useDownloadingDeviceProfile } from '../../../stores/device-profile'
|
||||
import { UseMutateFunction, useMutation } from '@tanstack/react-query'
|
||||
import { mapDtoToTrack } from '../../../utils/mappings'
|
||||
@@ -7,12 +6,13 @@ import { deleteAudio, saveAudio } from './offlineModeUtils'
|
||||
import { useState } from 'react'
|
||||
import { JellifyDownloadProgress } from '../../../types/JellifyDownload'
|
||||
import { useAllDownloadedTracks } from '../../queries/download'
|
||||
import { useApi } from '../../../stores'
|
||||
|
||||
export const useDownloadAudioItem: () => [
|
||||
JellifyDownloadProgress,
|
||||
UseMutateFunction<boolean, Error, { item: BaseItemDto; autoCached: boolean }, void>,
|
||||
] = () => {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const { data: downloadedTracks, refetch } = useAllDownloadedTracks()
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { queryClient } from '../../../constants/query-client'
|
||||
import useHapticFeedback from '../../../hooks/use-haptic-feedback'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { BaseItemDto, UserItemDataDto } from '@jellyfin/sdk/lib/generated-client'
|
||||
import { getUserLibraryApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { isUndefined } from 'lodash'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import UserDataQueryKey from '../../queries/user-data/keys'
|
||||
import { useApi, useJellifyUser } from '../../../../src/stores'
|
||||
|
||||
interface SetFavoriteMutation {
|
||||
item: BaseItemDto
|
||||
@@ -14,7 +14,8 @@ interface SetFavoriteMutation {
|
||||
}
|
||||
|
||||
export const useAddFavorite = () => {
|
||||
const { api, user } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
|
||||
const trigger = useHapticFeedback()
|
||||
|
||||
@@ -59,7 +60,8 @@ export const useAddFavorite = () => {
|
||||
}
|
||||
|
||||
export const useRemoveFavorite = () => {
|
||||
const { api, user } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
|
||||
const trigger = useHapticFeedback()
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import JellifyTrack from '../../../types/JellifyTrack'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import reportPlaybackCompleted from './functions/playback-completed'
|
||||
@@ -6,13 +5,14 @@ import reportPlaybackStopped from './functions/playback-stopped'
|
||||
import isPlaybackFinished from './utils'
|
||||
import reportPlaybackProgress from './functions/playback-progress'
|
||||
import reportPlaybackStarted from './functions/playback-started'
|
||||
import { useApi } from '../../../stores'
|
||||
|
||||
interface PlaybackStartedMutation {
|
||||
track: JellifyTrack
|
||||
}
|
||||
|
||||
export const useReportPlaybackStarted = () => {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
return useMutation({
|
||||
onMutate: () => {},
|
||||
@@ -29,7 +29,7 @@ interface PlaybackStoppedMutation {
|
||||
}
|
||||
|
||||
export const useReportPlaybackStopped = () => {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
return useMutation({
|
||||
onMutate: ({ lastPosition, duration }) =>
|
||||
@@ -59,7 +59,7 @@ interface PlaybackProgressMutation {
|
||||
}
|
||||
|
||||
export const useReportPlaybackProgress = () => {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
return useMutation({
|
||||
onMutate: ({ position }) => console.debug(`Reporting progress at ${position}`),
|
||||
|
||||
@@ -3,7 +3,7 @@ import { connectToServer } from './utils'
|
||||
import { JellifyServer } from '@/src/types/JellifyServer'
|
||||
import serverAddressContainsProtocol from './utils/parsing'
|
||||
import HTTPS, { HTTP } from '../../../constants/protocols'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import useJellifyStore from '../../../stores'
|
||||
|
||||
interface PublicSystemInfoMutation {
|
||||
serverAddress: string
|
||||
@@ -16,7 +16,7 @@ interface PublicSystemInfoHook {
|
||||
}
|
||||
|
||||
const usePublicSystemInfo = ({ onSuccess, onError }: PublicSystemInfoHook) => {
|
||||
const { setServer } = useJellifyContext()
|
||||
const setServer = useJellifyStore((state) => state.setServer)
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ serverAddress, useHttps }: PublicSystemInfoMutation) =>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useLibrarySortAndFilterContext } from '../../../providers/Library'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { InfiniteData, useInfiniteQuery, UseInfiniteQueryResult } from '@tanstack/react-query'
|
||||
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by'
|
||||
import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'
|
||||
@@ -11,14 +10,17 @@ import flattenInfiniteQueryPages from '../../../utils/query-selectors'
|
||||
import { ApiLimits } from '../query.config'
|
||||
import { fetchRecentlyAdded } from '../recents/utils'
|
||||
import { queryClient } from '../../../constants/query-client'
|
||||
import { useApi, useJellifyLibrary, useJellifyUser } from '../../../stores'
|
||||
|
||||
const useAlbums: () => [
|
||||
RefObject<Set<string>>,
|
||||
UseInfiniteQueryResult<(string | number | BaseItemDto)[]>,
|
||||
] = () => {
|
||||
const { api, user, library } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
const { isFavorites, sortDescending } = useLibrarySortAndFilterContext()
|
||||
const { isFavorites } = useLibrarySortAndFilterContext()
|
||||
|
||||
const albumPageParams = useRef<Set<string>>(new Set<string>())
|
||||
|
||||
@@ -43,10 +45,10 @@ const useAlbums: () => [
|
||||
),
|
||||
initialPageParam: 0,
|
||||
select: selectAlbums,
|
||||
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => {
|
||||
getNextPageParam: (lastPage, allPages, lastPageParam) => {
|
||||
return lastPage.length === ApiLimits.Library ? lastPageParam + 1 : undefined
|
||||
},
|
||||
getPreviousPageParam: (firstPage, allPages, firstPageParam, allPageParams) => {
|
||||
getPreviousPageParam: (firstPage, allPages, firstPageParam) => {
|
||||
return firstPageParam === 0 ? null : firstPageParam - 1
|
||||
},
|
||||
})
|
||||
@@ -57,20 +59,21 @@ const useAlbums: () => [
|
||||
export default useAlbums
|
||||
|
||||
export const useRecentlyAddedAlbums = () => {
|
||||
const { api, user, library } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
return useInfiniteQuery({
|
||||
queryKey: [QueryKeys.RecentlyAddedAlbums, library?.musicLibraryId],
|
||||
queryFn: ({ pageParam }) => fetchRecentlyAdded(api, library, pageParam),
|
||||
select: (data) => data.pages.flatMap((page) => page),
|
||||
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
|
||||
getNextPageParam: (lastPage, allPages, lastPageParam) =>
|
||||
lastPage.length > 0 ? lastPageParam + 1 : undefined,
|
||||
initialPageParam: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export const useRefetchRecentlyAdded: () => () => void = () => {
|
||||
const { library } = useJellifyContext()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
return () =>
|
||||
queryClient.invalidateQueries({
|
||||
|
||||
@@ -6,16 +6,17 @@ import {
|
||||
UseInfiniteQueryResult,
|
||||
useQuery,
|
||||
} from '@tanstack/react-query'
|
||||
import { isString, isUndefined } from 'lodash'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { fetchArtistAlbums, fetchArtistFeaturedOn, fetchArtists } from './utils/artist'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { ApiLimits } from '../query.config'
|
||||
import { RefObject, useCallback, useRef } from 'react'
|
||||
import { useLibrarySortAndFilterContext } from '../../../providers/Library'
|
||||
import flattenInfiniteQueryPages from '../../../utils/query-selectors'
|
||||
import { useApi, useJellifyLibrary, useJellifyUser } from '../../../stores'
|
||||
|
||||
export const useArtistAlbums = (artist: BaseItemDto) => {
|
||||
const { api, library } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.ArtistAlbums, library?.musicLibraryId, artist.Id],
|
||||
@@ -25,7 +26,8 @@ export const useArtistAlbums = (artist: BaseItemDto) => {
|
||||
}
|
||||
|
||||
export const useArtistFeaturedOn = (artist: BaseItemDto) => {
|
||||
const { api, library } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.ArtistFeaturedOn, library?.musicLibraryId, artist.Id],
|
||||
@@ -38,7 +40,9 @@ export const useAlbumArtists: () => [
|
||||
RefObject<Set<string>>,
|
||||
UseInfiniteQueryResult<(string | number | BaseItemDto)[], Error>,
|
||||
] = () => {
|
||||
const { api, user, library } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
const { isFavorites, sortDescending } = useLibrarySortAndFilterContext()
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useInfiniteQuery } from '@tanstack/react-query'
|
||||
import { FrequentlyPlayedArtistsQueryKey, FrequentlyPlayedTracksQueryKey } from './keys'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { fetchFrequentlyPlayed, fetchFrequentlyPlayedArtists } from './utils/frequents'
|
||||
import { ApiLimits } from '../query.config'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { useApi, useJellifyLibrary, useJellifyUser } from '../../../stores'
|
||||
|
||||
const FREQUENTS_QUERY_CONFIG = {
|
||||
refetchOnMount: false,
|
||||
@@ -11,7 +11,9 @@ const FREQUENTS_QUERY_CONFIG = {
|
||||
} as const
|
||||
|
||||
export const useFrequentlyPlayedTracks = () => {
|
||||
const { api, user, library } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
return useInfiniteQuery({
|
||||
queryKey: FrequentlyPlayedTracksQueryKey(user, library),
|
||||
@@ -27,7 +29,9 @@ export const useFrequentlyPlayedTracks = () => {
|
||||
}
|
||||
|
||||
export const useFrequentlyPlayedArtists = () => {
|
||||
const { api, user, library } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
const { data: frequentlyPlayedTracks } = useFrequentlyPlayedTracks()
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useQuery, UseQueryResult } from '@tanstack/react-query'
|
||||
import LyricsQueryKey from './keys'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { fetchRawLyrics } from './utils'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { useApi } from '../../../stores'
|
||||
import { useCurrentTrack } from '../../../stores/player/queue'
|
||||
|
||||
/**
|
||||
@@ -11,7 +11,7 @@ import { useCurrentTrack } from '../../../stores/player/queue'
|
||||
* @returns a {@link UseQueryResult} for the
|
||||
*/
|
||||
const useRawLyrics = () => {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const nowPlaying = useCurrentTrack()
|
||||
|
||||
return useQuery({
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { Api } from '@jellyfin/sdk'
|
||||
import { useJellifyContext } from '../../../../src/providers'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { JellifyUser } from '@/src/types/JellifyUser'
|
||||
import useStreamingDeviceProfile, {
|
||||
useDownloadingDeviceProfile,
|
||||
} from '../../../stores/device-profile'
|
||||
import { fetchMediaInfo } from './utils'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'
|
||||
import MediaInfoQueryKey from './keys'
|
||||
import { useApi } from '../../../stores'
|
||||
|
||||
/**
|
||||
* A React hook that will retrieve the latest media info
|
||||
@@ -16,15 +15,15 @@ import MediaInfoQueryKey from './keys'
|
||||
* Depends on the {@link useStreamingDeviceProfile} hook for retrieving
|
||||
* the currently configured device profile
|
||||
*
|
||||
* Depends on the {@link useJellifyContext} hook for retrieving
|
||||
* the currently configured {@link Api} and {@link JellifyUser}
|
||||
* Depends on the {@link useApi} hook for retrieving
|
||||
* the currently configured {@link Api}
|
||||
* instance
|
||||
*
|
||||
* @param itemId The Id of the {@link BaseItemDto}
|
||||
* @returns
|
||||
*/
|
||||
const useStreamedMediaInfo = (itemId: string | null | undefined) => {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const deviceProfile = useStreamingDeviceProfile()
|
||||
|
||||
@@ -45,15 +44,15 @@ export default useStreamedMediaInfo
|
||||
* Depends on the {@link useDownloadingDeviceProfile} hook for retrieving
|
||||
* the currently configured device profile
|
||||
*
|
||||
* Depends on the {@link useJellifyContext} hook for retrieving
|
||||
* the currently configured {@link Api} and {@link JellifyUser}
|
||||
* Depends on the {@link useApi} hook for retrieving
|
||||
* the currently configured {@link Api}
|
||||
* instance
|
||||
*
|
||||
* @param itemId The Id of the {@link BaseItemDto}
|
||||
* @returns
|
||||
*/
|
||||
export const useDownloadedMediaInfo = (itemId: string | null | undefined) => {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const deviceProfile = useDownloadingDeviceProfile()
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import fetchPatrons from './utils'
|
||||
import { ONE_DAY } from '../../../constants/query-client'
|
||||
import { useApi } from '../../../stores'
|
||||
|
||||
const usePatronsQuery = () => {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.Patrons],
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { UserPlaylistsQueryKey } from './keys'
|
||||
import { useInfiniteQuery, useQuery } from '@tanstack/react-query'
|
||||
import { fetchUserPlaylists, fetchPublicPlaylists } from './utils'
|
||||
import { ApiLimits } from '../query.config'
|
||||
import { useApi, useJellifyLibrary, useJellifyUser } from '../../../stores'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
|
||||
export const useUserPlaylists = () => {
|
||||
const { api, user, library } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
return useInfiniteQuery({
|
||||
queryKey: UserPlaylistsQueryKey(library),
|
||||
@@ -22,7 +24,7 @@ export const useUserPlaylists = () => {
|
||||
}
|
||||
|
||||
export const usePlaylistTracks = (playlist: BaseItemDto) => {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
return useQuery({
|
||||
queryKey: [QueryKeys.ItemTracks, playlist.Id!],
|
||||
|
||||
@@ -18,9 +18,9 @@ import QueryConfig from '../../query.config'
|
||||
* config directory of Jellyfin, as to avoid displaying .m3u files from
|
||||
* the library
|
||||
*
|
||||
* @param api The {@link Api} instance from the {@link useJellifyContext} hook
|
||||
* @param user The {@link JellifyUser} instance from the {@link useJellifyContext} hook
|
||||
* @param library The {@link JellifyLibrary} instance from the {@link useJellifyContext} hook
|
||||
* @param api The {@link Api} instance from the {@link useApi} hook
|
||||
* @param user The {@link JellifyUser} instance from the {@link useJellifyUser} hook
|
||||
* @param library The {@link JellifyLibrary} instance from the {@link useJellifyLibrary} hook
|
||||
* @param sortBy An array of {@link ItemSortBy} values to sort the response by
|
||||
* @returns
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { RecentlyPlayedArtistsQueryKey, RecentlyPlayedTracksQueryKey } from './keys'
|
||||
import { useInfiniteQuery } from '@tanstack/react-query'
|
||||
import { fetchRecentlyPlayed, fetchRecentlyPlayedArtists } from './utils'
|
||||
import { ApiLimits } from '../query.config'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { useApi, useJellifyUser, useJellifyLibrary } from '../../../stores'
|
||||
|
||||
const RECENTS_QUERY_CONFIG = {
|
||||
maxPages: 2,
|
||||
@@ -12,7 +12,9 @@ const RECENTS_QUERY_CONFIG = {
|
||||
} as const
|
||||
|
||||
export const useRecentlyPlayedTracks = () => {
|
||||
const { api, user, library } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
return useInfiniteQuery({
|
||||
queryKey: RecentlyPlayedTracksQueryKey(user, library),
|
||||
@@ -28,7 +30,9 @@ export const useRecentlyPlayedTracks = () => {
|
||||
}
|
||||
|
||||
export const useRecentArtists = () => {
|
||||
const { api, user, library } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
const { data: recentlyPlayedTracks } = useRecentlyPlayedTracks()
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { InfiniteData, useInfiniteQuery, UseInfiniteQueryResult } from '@tanstack/react-query'
|
||||
import { TracksQueryKey } from './keys'
|
||||
import { useLibrarySortAndFilterContext } from '../../../providers/Library'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import fetchTracks from './utils'
|
||||
import {
|
||||
BaseItemDto,
|
||||
@@ -16,12 +15,15 @@ import { useAllDownloadedTracks } from '../download'
|
||||
import { queryClient } from '../../../constants/query-client'
|
||||
import UserDataQueryKey from '../user-data/keys'
|
||||
import { JellifyUser } from '@/src/types/JellifyUser'
|
||||
import { useApi, useJellifyUser, useJellifyLibrary } from '../../../stores'
|
||||
|
||||
const useTracks: () => [
|
||||
RefObject<Set<string>>,
|
||||
UseInfiniteQueryResult<(string | number | BaseItemDto)[]>,
|
||||
] = () => {
|
||||
const { api, user, library } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
const { isFavorites, sortDescending, isDownloaded } = useLibrarySortAndFilterContext()
|
||||
|
||||
const { data: downloadedTracks } = useAllDownloadedTracks()
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import fetchUserData from './utils'
|
||||
import UserDataQueryKey from './keys'
|
||||
import { useApi, useJellifyUser } from '../../../stores'
|
||||
|
||||
export const useIsFavorite = (item: BaseItemDto) => {
|
||||
const { api, user } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
|
||||
return useQuery({
|
||||
queryKey: UserDataQueryKey(user!, item),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { addManyToPlaylist, addToPlaylist } from '../../api/mutations/playlists'
|
||||
import { useState } from 'react'
|
||||
@@ -15,6 +14,7 @@ import { getItemName } from '../../utils/text'
|
||||
import useHapticFeedback from '../../hooks/use-haptic-feedback'
|
||||
import { usePlaylistTracks, useUserPlaylists } from '../../api/queries/playlist'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { useApi, useJellifyUser } from '../../stores'
|
||||
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
|
||||
|
||||
export default function AddToPlaylist({
|
||||
@@ -80,7 +80,8 @@ function AddToPlaylistRow({
|
||||
playlist: BaseItemDto
|
||||
tracks: BaseItemDto[]
|
||||
}): React.JSX.Element {
|
||||
const { api, user } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
|
||||
const trigger = useHapticFeedback()
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import InstantMixButton from '../Global/components/instant-mix-button'
|
||||
import ItemImage from '../Global/components/image'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import Icon from '../Global/components/icon'
|
||||
import { mapDtoToTrack } from '../../utils/mappings'
|
||||
@@ -25,6 +24,7 @@ import LibraryStackParamList from '../../screens/Library/types'
|
||||
import DiscoverStackParamList from '../../screens/Discover/types'
|
||||
import { BaseStackParamList } from '../../screens/types'
|
||||
import useStreamingDeviceProfile, { useDownloadingDeviceProfile } from '../../stores/device-profile'
|
||||
import { useApi } from '../../stores'
|
||||
|
||||
/**
|
||||
* The screen for an Album's track list
|
||||
@@ -39,7 +39,7 @@ export function Album(): React.JSX.Element {
|
||||
|
||||
const { album, discs, isPending } = useAlbumContext()
|
||||
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const { addToDownloadQueue, pendingDownloads } = useNetworkContext()
|
||||
const downloadingDeviceProfile = useDownloadingDeviceProfile()
|
||||
|
||||
@@ -129,7 +129,7 @@ export function Album(): React.JSX.Element {
|
||||
* @returns A React component
|
||||
*/
|
||||
function AlbumTrackListHeader(): React.JSX.Element {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const { width } = useSafeAreaFrame()
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ImageType } from '@jellyfin/sdk/lib/generated-client'
|
||||
import LinearGradient from 'react-native-linear-gradient'
|
||||
import { getTokenValue, useTheme, XStack, YStack, ZStack } from 'tamagui'
|
||||
import Icon from '../Global/components/icon'
|
||||
import ItemImage from '../Global/components/image'
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import { H5 } from '../Global/helpers/text'
|
||||
@@ -13,16 +12,16 @@ 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 { useJellifyContext } from '../../providers'
|
||||
import { useLoadNewQueue } from '../../providers/Player/hooks/mutations'
|
||||
import { QueuingType } from '../../enums/queuing-type'
|
||||
import { useNetworkStatus } from '../../stores/network'
|
||||
import useStreamingDeviceProfile from '../../stores/device-profile'
|
||||
import { useApi } from '../../stores'
|
||||
|
||||
export default function ArtistHeader(): React.JSX.Element {
|
||||
const { width } = useSafeAreaFrame()
|
||||
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const { artist, albums } = useArtistContext()
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { fetchAlbumDiscs, fetchItem } from '../../api/queries/item'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { AddToQueueMutation } from '../../providers/Player/interfaces'
|
||||
import { QueuingType } from '../../enums/queuing-type'
|
||||
@@ -33,6 +32,7 @@ import { useIsDownloaded } from '../../api/queries/download'
|
||||
import { useDeleteDownloads } from '../../api/mutations/download'
|
||||
import useHapticFeedback from '../../hooks/use-haptic-feedback'
|
||||
import { Platform } from 'react-native'
|
||||
import { useApi } from '../../stores'
|
||||
|
||||
type StackNavigation = Pick<NativeStackNavigationProp<BaseStackParamList>, 'navigate' | 'dispatch'>
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function ItemContext({
|
||||
downloadedMediaSourceInfo,
|
||||
stackNavigation,
|
||||
}: ContextProps): React.JSX.Element {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const trigger = useHapticFeedback()
|
||||
|
||||
@@ -187,7 +187,7 @@ function AddToPlaylistRow({
|
||||
}
|
||||
|
||||
function AddToQueueMenuRow({ tracks }: { tracks: BaseItemDto[] }): React.JSX.Element {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const [networkStatus] = useNetworkStatus()
|
||||
|
||||
@@ -246,7 +246,7 @@ function AddToQueueMenuRow({ tracks }: { tracks: BaseItemDto[] }): React.JSX.Ele
|
||||
}
|
||||
|
||||
function DownloadMenuRow({ items }: { items: BaseItemDto[] }): React.JSX.Element {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const { addToDownloadQueue, pendingDownloads } = useNetworkContext()
|
||||
|
||||
const useRemoveDownload = useDeleteDownloads()
|
||||
@@ -376,7 +376,7 @@ function ViewArtistMenuRow({
|
||||
artistId: string | null | undefined
|
||||
stackNavigation: StackNavigation | undefined
|
||||
}): React.JSX.Element {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const { data: artist } = useQuery({
|
||||
queryKey: [QueryKeys.ArtistById, artistId],
|
||||
|
||||
@@ -2,14 +2,13 @@ import { H5, View, XStack } from 'tamagui'
|
||||
import { useDiscoverContext } from '../../../providers/Discover'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import HorizontalCardList from '../../Global/components/horizontal-list'
|
||||
import { ItemCard } from '../../Global/components/item-card'
|
||||
import { H4 } from '../../Global/helpers/text'
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import DiscoverStackParamList from '../../../screens/Discover/types'
|
||||
import navigationRef from '../../../../navigation'
|
||||
import { useJellifyServer } from '../../../stores'
|
||||
|
||||
export default function PublicPlaylists() {
|
||||
const {
|
||||
@@ -23,7 +22,7 @@ export default function PublicPlaylists() {
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<DiscoverStackParamList>>()
|
||||
|
||||
const { server } = useJellifyContext()
|
||||
const [server] = useJellifyServer()
|
||||
const { width } = useSafeAreaFrame()
|
||||
return (
|
||||
<View>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { getTokenValue, Token, View } from 'tamagui'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { StyleSheet, ViewStyle } from 'react-native'
|
||||
import { StyleSheet } from 'react-native'
|
||||
import { ImageType } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { NitroImage, useImage } from 'react-native-nitro-image'
|
||||
import { Blurhash } from 'react-native-blurhash'
|
||||
@@ -10,6 +9,7 @@ import { getBlurhashFromDto } from '../../../utils/blurhash'
|
||||
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
|
||||
import { getItemImageUrl } from '../../../api/queries/image/utils'
|
||||
import { useMemo } from 'react'
|
||||
import { useApi } from '../../../stores'
|
||||
|
||||
interface ItemImageProps {
|
||||
item: BaseItemDto
|
||||
@@ -30,9 +30,9 @@ export default function ItemImage({
|
||||
height,
|
||||
testID,
|
||||
}: ItemImageProps): React.JSX.Element {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const imageUrl = getItemImageUrl(api, item, type)
|
||||
const imageUrl = useMemo(() => getItemImageUrl(api, item, type), [api, item.Id, type])
|
||||
|
||||
return api ? (
|
||||
<Image
|
||||
|
||||
@@ -5,9 +5,10 @@ import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchInstantMixFromItem } from '../../../api/queries/instant-mixes'
|
||||
import Icon from './icon'
|
||||
import { Spacer, Spinner } from 'tamagui'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { BaseStackParamList } from '../../../screens/types'
|
||||
import { useApi, useJellifyUser } from '../../../stores'
|
||||
|
||||
export default function InstantMixButton({
|
||||
item,
|
||||
navigation,
|
||||
@@ -15,7 +16,8 @@ export default function InstantMixButton({
|
||||
item: BaseItemDto
|
||||
navigation: Pick<NativeStackNavigationProp<BaseStackParamList>, 'navigate' | 'dispatch'>
|
||||
}): React.JSX.Element {
|
||||
const { api, user } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
|
||||
const { data, isFetching, refetch } = useQuery({
|
||||
queryKey: [QueryKeys.InstantMix, item.Id!],
|
||||
|
||||
@@ -6,18 +6,16 @@ import { QueuingType } from '../../../enums/queuing-type'
|
||||
import { RunTimeTicks } from '../helpers/time-codes'
|
||||
import ItemImage from './image'
|
||||
import FavoriteIcon from './favorite-icon'
|
||||
import { Gesture, GestureDetector } from 'react-native-gesture-handler'
|
||||
import { runOnJS } from 'react-native-reanimated'
|
||||
import navigationRef from '../../../../navigation'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { BaseStackParamList } from '../../../screens/types'
|
||||
import { useLoadNewQueue } from '../../../providers/Player/hooks/mutations'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { useNetworkStatus } from '../../../stores/network'
|
||||
import useStreamingDeviceProfile from '../../../stores/device-profile'
|
||||
import useItemContext from '../../../hooks/use-item-context'
|
||||
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native'
|
||||
import { RouteProp, useRoute } from '@react-navigation/native'
|
||||
import { useCallback } from 'react'
|
||||
import { useApi } from '../../../stores'
|
||||
|
||||
interface ItemRowProps {
|
||||
item: BaseItemDto
|
||||
@@ -44,7 +42,7 @@ export default function ItemRow({
|
||||
navigation,
|
||||
onPress,
|
||||
}: ItemRowProps): React.JSX.Element {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const [networkStatus] = useNetworkStatus()
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ import { getToken, Spinner, ToggleGroup, YStack } from 'tamagui'
|
||||
import { H2, Text } from '../helpers/text'
|
||||
import Button from '../helpers/button'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { fetchUserViews } from '../../../api/queries/libraries'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import Icon from './icon'
|
||||
import { useApi, useJellifyLibrary, useJellifyUser } from '../../../stores'
|
||||
|
||||
interface LibrarySelectorProps {
|
||||
onLibrarySelected: (
|
||||
@@ -37,7 +37,9 @@ export default function LibrarySelector({
|
||||
showCancelButton = true,
|
||||
isOnboarding = false,
|
||||
}: LibrarySelectorProps): React.JSX.Element {
|
||||
const { api, user, library } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
const {
|
||||
data: libraries,
|
||||
|
||||
@@ -15,10 +15,10 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { BaseStackParamList } from '../../../screens/types'
|
||||
import ItemImage from './image'
|
||||
import { useLoadNewQueue } from '../../../providers/Player/hooks/mutations'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import useStreamingDeviceProfile from '../../../stores/device-profile'
|
||||
import useStreamedMediaInfo from '../../../api/queries/media'
|
||||
import { useDownloadedTrack } from '../../../api/queries/download'
|
||||
import { useApi } from '../../../stores'
|
||||
import { useCurrentTrack, usePlayQueue } from '../../../stores/player/queue'
|
||||
|
||||
export interface TrackProps {
|
||||
@@ -55,7 +55,7 @@ export default function Track({
|
||||
}: TrackProps): React.JSX.Element {
|
||||
const theme = useTheme()
|
||||
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const deviceProfile = useStreamingDeviceProfile()
|
||||
|
||||
|
||||
@@ -5,18 +5,17 @@ import { ItemCard } from '../../../components/Global/components/item-card'
|
||||
import { QueuingType } from '../../../enums/queuing-type'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { useLoadNewQueue } from '../../../providers/Player/hooks/mutations'
|
||||
import { H4 } from '../../../components/Global/helpers/text'
|
||||
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 { useJellifyContext } from '../../../providers'
|
||||
import { useNetworkStatus } from '../../../stores/network'
|
||||
import useStreamingDeviceProfile from '../../../stores/device-profile'
|
||||
import { useFrequentlyPlayedTracks } from '../../../api/queries/frequents'
|
||||
import { useApi } from '../../../stores'
|
||||
|
||||
export default function FrequentlyPlayedTracks(): React.JSX.Element {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const [networkStatus] = useNetworkStatus()
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { H5, View, XStack } from 'tamagui'
|
||||
import { H4 } from '../../Global/helpers/text'
|
||||
import { ItemCard } from '../../Global/components/item-card'
|
||||
import { RootStackParamList } from '../../../screens/types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
@@ -11,14 +10,14 @@ import { useLoadNewQueue } from '../../../providers/Player/hooks/mutations'
|
||||
import { useDisplayContext } from '../../../providers/Display/display-provider'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import HomeStackParamList from '../../../screens/Home/types'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { useNetworkStatus } from '../../../stores/network'
|
||||
import useStreamingDeviceProfile from '../../../stores/device-profile'
|
||||
import { useRecentlyPlayedTracks } from '../../../api/queries/recents'
|
||||
import { useCurrentTrack } from '../../../stores/player/queue'
|
||||
import { useApi } from '../../../stores'
|
||||
|
||||
export default function RecentlyPlayed(): React.JSX.Element {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const [networkStatus] = useNetworkStatus()
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import React, { useCallback, useMemo } from 'react'
|
||||
import ItemImage from '../../Global/components/image'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchItem } from '../../../api/queries/item'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import FavoriteButton from '../../Global/components/favorite-button'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import navigationRef from '../../../../navigation'
|
||||
@@ -14,9 +13,10 @@ import Icon from '../../Global/components/icon'
|
||||
import { getItemName } from '../../../utils/text'
|
||||
import { CommonActions } from '@react-navigation/native'
|
||||
import { useCurrentTrack } from '../../../stores/player/queue'
|
||||
import { useApi } from '../../../stores'
|
||||
|
||||
export default function SongInfo(): React.JSX.Element {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const nowPlaying = useCurrentTrack()
|
||||
|
||||
const { data: album } = useQuery({
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import { getToken, getTokens, Separator, View, XStack, YStack } from 'tamagui'
|
||||
import { getTokens, Separator, View, XStack, YStack } from 'tamagui'
|
||||
import { AnimatedH5 } from '../../Global/helpers/text'
|
||||
import InstantMixButton from '../../Global/components/instant-mix-button'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { usePlaylistContext } from '../../../providers/Playlist'
|
||||
import Animated, { useAnimatedStyle, withSpring } from 'react-native-reanimated'
|
||||
import { getImageApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { ImageType } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { useNetworkStatus } from '../../../../src/stores/network'
|
||||
import { useNetworkContext } from '../../../../src/providers/Network'
|
||||
import { ActivityIndicator } from 'react-native'
|
||||
@@ -17,12 +14,12 @@ import { mapDtoToTrack } from '../../../utils/mappings'
|
||||
import { QueuingType } from '../../../enums/queuing-type'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import LibraryStackParamList from '@/src/screens/Library/types'
|
||||
import { NitroImage } from 'react-native-nitro-image'
|
||||
import { useLoadNewQueue } from '../../../providers/Player/hooks/mutations'
|
||||
import useStreamingDeviceProfile, {
|
||||
useDownloadingDeviceProfile,
|
||||
} from '../../../stores/device-profile'
|
||||
import ItemImage from '../../Global/components/image'
|
||||
import { useApi } from '../../../stores'
|
||||
|
||||
export default function PlayliistTracklistHeader(
|
||||
playlist: BaseItemDto,
|
||||
@@ -136,7 +133,7 @@ function PlaylistHeaderControls({
|
||||
const downloadingDeviceProfile = useDownloadingDeviceProfile()
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
const isDownloading = pendingDownloads.length != 0
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const [networkStatus] = useNetworkStatus()
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import React, { useCallback, useState } from 'react'
|
||||
import Input from '../Global/helpers/input'
|
||||
import ItemRow from '../Global/components/item-row'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { RootStackParamList } from '../../screens/types'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { fetchSearchResults } from '../../api/queries/search'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
@@ -13,14 +12,16 @@ import Suggestions from './suggestions'
|
||||
import { isEmpty } from 'lodash'
|
||||
import HorizontalCardList from '../Global/components/horizontal-list'
|
||||
import { ItemCard } from '../Global/components/item-card'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import SearchParamList from '../../screens/Search/types'
|
||||
import { useApi, useJellifyLibrary, useJellifyUser } from '../../stores'
|
||||
export default function Search({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<SearchParamList, 'SearchScreen'>
|
||||
}): React.JSX.Element {
|
||||
const { api, library, user } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
const [searchString, setSearchString] = useState<string | undefined>(undefined)
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import SignOut from './sign-out-button'
|
||||
import { SettingsStackParamList } from '../../../screens/Settings/types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
@@ -7,9 +6,12 @@ import { useNavigation } from '@react-navigation/native'
|
||||
import { Text } from '../../Global/helpers/text'
|
||||
import SettingsListGroup from './settings-list-group'
|
||||
import HTTPS from '../../../constants/protocols'
|
||||
import { useJellifyUser, useJellifyLibrary, useJellifyServer } from '../../../stores'
|
||||
|
||||
export default function AccountTab(): React.JSX.Element {
|
||||
const { user, library, server } = useJellifyContext()
|
||||
const [server] = useJellifyServer()
|
||||
const [user] = useJellifyUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<SettingsStackParamList>>()
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import _ from 'lodash'
|
||||
import React, { useEffect } from 'react'
|
||||
import Root from '../screens'
|
||||
import { PlayerProvider } from '../providers/Player'
|
||||
import { JellifyProvider, useJellifyContext } from '../providers'
|
||||
import { NetworkContextProvider } from '../providers/Network'
|
||||
import { DisplayProvider } from '../providers/Display/display-provider'
|
||||
import {
|
||||
@@ -34,9 +33,7 @@ export default function Jellify(): React.JSX.Element {
|
||||
<Theme name={theme === 'system' ? (isDarkMode ? 'dark' : 'light') : theme}>
|
||||
<JellifyLoggingWrapper>
|
||||
<DisplayProvider>
|
||||
<JellifyProvider>
|
||||
<App />
|
||||
</JellifyProvider>
|
||||
<App />
|
||||
</DisplayProvider>
|
||||
</JellifyLoggingWrapper>
|
||||
</Theme>
|
||||
@@ -64,7 +61,7 @@ function JellifyLoggingWrapper({ children }: { children: React.ReactNode }): Rea
|
||||
}
|
||||
|
||||
/**
|
||||
* The main component for the Jellify app. Depends on {@link useJellifyContext} hook to determine if the user is logged in
|
||||
* The main component for the Jellify app
|
||||
* @returns The {@link App} component
|
||||
*/
|
||||
function App(): React.JSX.Element {
|
||||
@@ -81,7 +78,6 @@ function App(): React.JSX.Element {
|
||||
return (
|
||||
<NetworkContextProvider>
|
||||
<PlayerProvider />
|
||||
<CarPlayProvider />
|
||||
<Root />
|
||||
<Toast topOffset={getToken('$12')} config={JellifyToastConfig(theme)} />
|
||||
</NetworkContextProvider>
|
||||
|
||||
@@ -7,14 +7,15 @@ import { fetchMediaInfo } from '../api/queries/media/utils'
|
||||
import { fetchAlbumDiscs, fetchItem } from '../api/queries/item'
|
||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import fetchUserData from '../api/queries/user-data/utils'
|
||||
import { useJellifyContext } from '../providers'
|
||||
import { useCallback, useRef } from 'react'
|
||||
import useStreamingDeviceProfile, { useDownloadingDeviceProfile } from '../stores/device-profile'
|
||||
import UserDataQueryKey from '../api/queries/user-data/keys'
|
||||
import MediaInfoQueryKey from '../api/queries/media/keys'
|
||||
import { useApi, useJellifyUser } from '../stores'
|
||||
|
||||
export default function useItemContext(): (item: BaseItemDto) => void {
|
||||
const { api, user } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
|
||||
const streamingDeviceProfile = useStreamingDeviceProfile()
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { useJellifyContext } from '..'
|
||||
import { fetchAlbumDiscs } from '../../api/queries/item'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { createContext, ReactNode, useContext } from 'react'
|
||||
import { useApi } from '../../stores'
|
||||
|
||||
interface AlbumContext {
|
||||
album: BaseItemDto
|
||||
@@ -12,7 +12,7 @@ interface AlbumContext {
|
||||
}
|
||||
|
||||
function AlbumContextInitializer(album: BaseItemDto): AlbumContext {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const { data: discs, isPending } = useQuery({
|
||||
queryKey: [QueryKeys.ItemTracks, album.Id],
|
||||
|
||||
@@ -4,9 +4,9 @@ import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { createContext, ReactNode, useCallback, useContext, useMemo } from 'react'
|
||||
import { SharedValue, useSharedValue } from 'react-native-reanimated'
|
||||
import { useJellifyContext } from '..'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { useArtistAlbums, useArtistFeaturedOn } from '../../api/queries/artist'
|
||||
import { useApi, useJellifyUser, useJellifyLibrary } from '../../stores'
|
||||
|
||||
interface ArtistContext {
|
||||
fetchingAlbums: boolean
|
||||
@@ -39,7 +39,9 @@ export const ArtistProvider = ({
|
||||
artist: BaseItemDto
|
||||
children: ReactNode
|
||||
}) => {
|
||||
const { api, library, user } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
const {
|
||||
data: albums,
|
||||
|
||||
@@ -2,17 +2,18 @@ import CarPlayNavigation from '../../components/CarPlay/Navigation'
|
||||
import { createContext, useEffect, useState } from 'react'
|
||||
import { Platform } from 'react-native'
|
||||
import { CarPlay } from 'react-native-carplay'
|
||||
import { useJellifyContext } from '../index'
|
||||
import { useLoadNewQueue } from '../Player/hooks/mutations'
|
||||
import { useNetworkStatus } from '../../stores/network'
|
||||
import useStreamingDeviceProfile from '../../stores/device-profile'
|
||||
import useJellifyStore, { useApi, useJellifyLibrary } from '../../stores'
|
||||
|
||||
interface CarPlayContext {
|
||||
carplayConnected: boolean
|
||||
}
|
||||
|
||||
const CarPlayContextInitializer = () => {
|
||||
const { api, user, library } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [library] = useJellifyLibrary()
|
||||
const [carplayConnected, setCarPlayConnected] = useState(CarPlay ? CarPlay.connected : false)
|
||||
|
||||
const [networkStatus] = useNetworkStatus()
|
||||
@@ -25,13 +26,13 @@ const CarPlayContextInitializer = () => {
|
||||
function onConnect() {
|
||||
setCarPlayConnected(true)
|
||||
|
||||
if (api && library) {
|
||||
if (library) {
|
||||
CarPlay.setRootTemplate(
|
||||
CarPlayNavigation(
|
||||
library,
|
||||
loadNewQueue,
|
||||
api,
|
||||
user,
|
||||
useJellifyStore.getState().user,
|
||||
networkStatus,
|
||||
deviceProfile,
|
||||
),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
InfiniteData,
|
||||
InfiniteQueryObserverResult,
|
||||
useInfiniteQuery,
|
||||
UseInfiniteQueryResult,
|
||||
@@ -7,10 +6,10 @@ import {
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { createContext, ReactNode, useContext, useState } from 'react'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { useJellifyContext } from '..'
|
||||
import { fetchPublicPlaylists } from '../../api/queries/playlist/utils'
|
||||
import { fetchArtistSuggestions } from '../../api/queries/suggestions'
|
||||
import { useRefetchRecentlyAdded } from '../../api/queries/album'
|
||||
import { useApi, useJellifyUser, useJellifyLibrary } from '../../stores'
|
||||
|
||||
interface DiscoverContext {
|
||||
refreshing: boolean
|
||||
@@ -25,7 +24,9 @@ interface DiscoverContext {
|
||||
}
|
||||
|
||||
const DiscoverContextInitializer = () => {
|
||||
const { api, library, user } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
const [refreshing, setRefreshing] = useState<boolean>(false)
|
||||
|
||||
const refetchRecentlyAdded = useRefetchRecentlyAdded()
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useAutoDownload } from '../../stores/settings/usage'
|
||||
import reportPlaybackStopped from '../../api/mutations/playback/functions/playback-stopped'
|
||||
import reportPlaybackCompleted from '../../api/mutations/playback/functions/playback-completed'
|
||||
import isPlaybackFinished from '../../api/mutations/playback/utils'
|
||||
import { useJellifyContext } from '..'
|
||||
import reportPlaybackProgress from '../../api/mutations/playback/functions/playback-progress'
|
||||
import reportPlaybackStarted from '../../api/mutations/playback/functions/playback-started'
|
||||
import calculateTrackVolume from './utils/normalization'
|
||||
@@ -15,6 +14,7 @@ import saveAudioItem from '../../api/mutations/download/utils'
|
||||
import { useDownloadingDeviceProfile } from '../../stores/device-profile'
|
||||
import Initialize from './functions/initialization'
|
||||
import { useEnableAudioNormalization } from '../../stores/settings/player'
|
||||
import { useApi } from '../../stores'
|
||||
import { usePlayerQueueStore } from '../../stores/player/queue'
|
||||
|
||||
const PLAYER_EVENTS: Event[] = [
|
||||
@@ -28,7 +28,7 @@ interface PlayerContext {}
|
||||
export const PlayerContext = createContext<PlayerContext>({})
|
||||
|
||||
export const PlayerProvider: () => React.JSX.Element = () => {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const [initialized, setInitialized] = useState<boolean>(false)
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { useMutation, UseMutationResult } from '@tanstack/react-query'
|
||||
import { useJellifyContext } from '..'
|
||||
import { createContext, ReactNode, useContext, useEffect, useState } from 'react'
|
||||
import { removeFromPlaylist, updatePlaylist } from '../../api/mutations/playlists'
|
||||
import { RemoveFromPlaylistMutation } from '../../components/Playlist/interfaces'
|
||||
import { SharedValue, useSharedValue } from 'react-native-reanimated'
|
||||
import useHapticFeedback from '../../hooks/use-haptic-feedback'
|
||||
import { useApi } from '../../stores'
|
||||
import { usePlaylistTracks } from '../../api/queries/playlist'
|
||||
|
||||
interface PlaylistContext {
|
||||
@@ -30,7 +30,7 @@ interface PlaylistContext {
|
||||
}
|
||||
|
||||
const PlaylistContextInitializer = (playlist: BaseItemDto) => {
|
||||
const { api } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const canEdit = playlist.CanDelete
|
||||
const [editing, setEditing] = useState<boolean>(false)
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
import { isUndefined } from 'lodash'
|
||||
import {
|
||||
createContext,
|
||||
ReactNode,
|
||||
SetStateAction,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { JellifyLibrary } from '../types/JellifyLibrary'
|
||||
import { JellifyServer } from '../types/JellifyServer'
|
||||
import { JellifyUser } from '../types/JellifyUser'
|
||||
import { storage } from '../constants/storage'
|
||||
import { MMKVStorageKeys } from '../enums/mmkv-storage-keys'
|
||||
import { Api } from '@jellyfin/sdk/lib/api'
|
||||
import { JellyfinInfo } from '../api/info'
|
||||
import { queryClient } from '../constants/query-client'
|
||||
import AXIOS_INSTANCE from '../configs/axios.config'
|
||||
import useAppActive from '../hooks/use-app-active'
|
||||
import usePostFullCapabilities from '../api/mutations/session'
|
||||
|
||||
/**
|
||||
* The context for the Jellify provider.
|
||||
*/
|
||||
interface JellifyContext {
|
||||
/**
|
||||
* Whether the user is logged in.
|
||||
*/
|
||||
loggedIn: boolean
|
||||
|
||||
/**
|
||||
* The {@link Api} client.
|
||||
*/
|
||||
api: Api | undefined
|
||||
|
||||
/**
|
||||
* The connected {@link JellifyServer} object.
|
||||
*/
|
||||
server: JellifyServer | undefined
|
||||
|
||||
/**
|
||||
* The signed in {@link JellifyUser} object.
|
||||
*/
|
||||
user: JellifyUser | undefined
|
||||
|
||||
/**
|
||||
* The selected{@link JellifyLibrary} object.
|
||||
*/
|
||||
library: JellifyLibrary | undefined
|
||||
|
||||
/**
|
||||
* The function to set the context {@link JellifyServer}.
|
||||
*/
|
||||
setServer: React.Dispatch<SetStateAction<JellifyServer | undefined>>
|
||||
|
||||
/**
|
||||
* The function to set the context {@link JellifyUser}.
|
||||
*/
|
||||
setUser: React.Dispatch<SetStateAction<JellifyUser | undefined>>
|
||||
|
||||
/**
|
||||
* The function to set the context {@link JellifyLibrary}.
|
||||
*/
|
||||
setLibrary: React.Dispatch<SetStateAction<JellifyLibrary | undefined>>
|
||||
|
||||
/**
|
||||
* The function to sign out of Jellify. This will clear the context
|
||||
* and remove all data from the device.
|
||||
*/
|
||||
signOut: () => void
|
||||
}
|
||||
|
||||
const JellifyContextInitializer = () => {
|
||||
const userJson = storage.getString(MMKVStorageKeys.User)
|
||||
const serverJson = storage.getString(MMKVStorageKeys.Server)
|
||||
const libraryJson = storage.getString(MMKVStorageKeys.Library)
|
||||
const apiJson = storage.getString(MMKVStorageKeys.Api)
|
||||
|
||||
const appIsActive = useAppActive()
|
||||
|
||||
const [api, setApi] = useState<Api | undefined>(apiJson ? JSON.parse(apiJson) : undefined)
|
||||
const [server, setServer] = useState<JellifyServer | undefined>(
|
||||
serverJson ? JSON.parse(serverJson) : undefined,
|
||||
)
|
||||
const [user, setUser] = useState<JellifyUser | undefined>(
|
||||
userJson ? JSON.parse(userJson) : undefined,
|
||||
)
|
||||
const [library, setLibrary] = useState<JellifyLibrary | undefined>(
|
||||
libraryJson ? JSON.parse(libraryJson) : undefined,
|
||||
)
|
||||
|
||||
const [loggedIn, setLoggedIn] = useState<boolean>(false)
|
||||
|
||||
const postFullCapabilities = usePostFullCapabilities()
|
||||
|
||||
const signOut = () => {
|
||||
setServer(undefined)
|
||||
setUser(undefined)
|
||||
setLibrary(undefined)
|
||||
|
||||
queryClient.clear()
|
||||
|
||||
storage.clearAll()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isUndefined(server) && !isUndefined(user))
|
||||
setApi(JellyfinInfo.createApi(server.url, user.accessToken, AXIOS_INSTANCE))
|
||||
else if (!isUndefined(server))
|
||||
setApi(JellyfinInfo.createApi(server.url, undefined, AXIOS_INSTANCE))
|
||||
else setApi(undefined)
|
||||
|
||||
setLoggedIn(!isUndefined(server) && !isUndefined(user) && !isUndefined(library))
|
||||
}, [server, user, library])
|
||||
|
||||
useEffect(() => {
|
||||
if (api) storage.set(MMKVStorageKeys.Api, JSON.stringify(api))
|
||||
else storage.delete(MMKVStorageKeys.Api)
|
||||
}, [api])
|
||||
|
||||
useEffect(() => {
|
||||
if (server) storage.set(MMKVStorageKeys.Server, JSON.stringify(server))
|
||||
else storage.delete(MMKVStorageKeys.Server)
|
||||
}, [server])
|
||||
|
||||
useEffect(() => {
|
||||
if (user) storage.set(MMKVStorageKeys.User, JSON.stringify(user))
|
||||
else storage.delete(MMKVStorageKeys.User)
|
||||
}, [user])
|
||||
|
||||
useEffect(() => {
|
||||
if (library) storage.set(MMKVStorageKeys.Library, JSON.stringify(library))
|
||||
else storage.delete(MMKVStorageKeys.Library)
|
||||
}, [library])
|
||||
|
||||
useEffect(() => {
|
||||
if (appIsActive) postFullCapabilities.mutate(api)
|
||||
}, [appIsActive, api])
|
||||
|
||||
return {
|
||||
loggedIn,
|
||||
api,
|
||||
server,
|
||||
user,
|
||||
library,
|
||||
setServer,
|
||||
setUser,
|
||||
setLibrary,
|
||||
signOut,
|
||||
}
|
||||
}
|
||||
|
||||
const JellifyContext = createContext<JellifyContext>({
|
||||
loggedIn: false,
|
||||
api: undefined,
|
||||
server: undefined,
|
||||
user: undefined,
|
||||
library: undefined,
|
||||
setServer: () => {},
|
||||
setUser: () => {},
|
||||
setLibrary: () => {},
|
||||
signOut: () => {},
|
||||
})
|
||||
|
||||
/**
|
||||
* Top level provider for Jellify. Provides the {@link JellifyContext} to all children, containing
|
||||
* whether the user is logged in, and the {@link Api} client
|
||||
* @param children The children to render
|
||||
* @returns The {@link JellifyProvider} component
|
||||
*/
|
||||
export const JellifyProvider: ({ children }: { children: ReactNode }) => React.JSX.Element = ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode
|
||||
}) => {
|
||||
const context = JellifyContextInitializer()
|
||||
|
||||
// Memoize the context value to prevent unnecessary re-renders
|
||||
const value = useMemo(
|
||||
() => context,
|
||||
[
|
||||
context.loggedIn,
|
||||
context.api,
|
||||
context.server?.url,
|
||||
context.user?.id,
|
||||
context.library?.musicLibraryId,
|
||||
],
|
||||
)
|
||||
|
||||
return <JellifyContext.Provider value={value}>{children}</JellifyContext.Provider>
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook to access the {@link JellifyContext}
|
||||
*
|
||||
* @returns The {@link JellifyContext}
|
||||
*/
|
||||
export const useJellifyContext = () => useContext(JellifyContext)
|
||||
@@ -7,18 +7,20 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { createPlaylist } from '../../api/mutations/playlists'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import Icon from '../../components/Global/components/icon'
|
||||
import LibraryStackParamList from './types'
|
||||
import useHapticFeedback from '../../hooks/use-haptic-feedback'
|
||||
import { useUserPlaylists } from '../../api/queries/playlist'
|
||||
import { useApi, useJellifyUser, useJellifyLibrary } from '../../stores'
|
||||
|
||||
export default function AddPlaylist({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<LibraryStackParamList, 'AddPlaylist'>
|
||||
}): React.JSX.Element {
|
||||
const { api, user } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [user] = useJellifyUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
const [name, setName] = useState<string>('')
|
||||
|
||||
const { refetch } = useUserPlaylists()
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { View, XStack } from 'tamagui'
|
||||
import Button from '../../components/Global/helpers/button'
|
||||
import { H5, Text } from '../../components/Global/helpers/text'
|
||||
import { Text } from '../../components/Global/helpers/text'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { deletePlaylist } from '../../api/mutations/playlists'
|
||||
import { queryClient } from '../../constants/query-client'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import Icon from '../../components/Global/components/icon'
|
||||
import { LibraryDeletePlaylistProps } from './types'
|
||||
import useHapticFeedback from '../../hooks/use-haptic-feedback'
|
||||
import { useApi } from '../../stores'
|
||||
|
||||
export default function DeletePlaylist({
|
||||
navigation,
|
||||
route,
|
||||
}: LibraryDeletePlaylistProps): React.JSX.Element {
|
||||
const { api, user, library } = useJellifyContext()
|
||||
const api = useApi()
|
||||
|
||||
const trigger = useHapticFeedback()
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import ServerAuthentication from './server-authentication'
|
||||
import ServerAddress from './server-address'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import ServerLibrary from './server-library'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import { useMemo } from 'react'
|
||||
import { useApi, useJellifyUser } from '../../stores'
|
||||
|
||||
const LoginStack = createNativeStackNavigator()
|
||||
|
||||
@@ -13,7 +13,8 @@ const LoginStack = createNativeStackNavigator()
|
||||
* @returns The login screen.
|
||||
*/
|
||||
export default function Login(): React.JSX.Element {
|
||||
const { user, server } = useJellifyContext()
|
||||
const [user] = useJellifyUser()
|
||||
const [server] = useJellifyUser()
|
||||
|
||||
const initialRouteName = useMemo(() => {
|
||||
if (isUndefined(server)) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import Button from '../../components/Global/helpers/button'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import Icon from '../../components/Global/components/icon'
|
||||
import { IS_MAESTRO_BUILD } from '../../configs/config'
|
||||
import { sleepify } from '../../utils/sleep'
|
||||
@@ -16,6 +15,7 @@ import { useSendMetricsSetting } from '../../stores/settings/app'
|
||||
import usePublicSystemInfo from '../../api/mutations/public-system-info'
|
||||
import HTTPS, { HTTP } from '../../constants/protocols'
|
||||
import { JellifyServer } from '@/src/types/JellifyServer'
|
||||
import { useSignOut } from '../../stores'
|
||||
|
||||
export default function ServerAddress({
|
||||
navigation,
|
||||
@@ -29,7 +29,7 @@ export default function ServerAddress({
|
||||
const [useHttps, setUseHttps] = useState<boolean>(true)
|
||||
const [serverAddress, setServerAddress] = useState<string | undefined>(undefined)
|
||||
|
||||
const { signOut } = useJellifyContext()
|
||||
const signOut = useSignOut()
|
||||
|
||||
const [sendMetrics, setSendMetrics] = useSendMetricsSetting()
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@ import Button from '../../components/Global/helpers/button'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
import Input from '../../components/Global/helpers/input'
|
||||
import Icon from '../../components/Global/components/icon'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import { IS_MAESTRO_BUILD } from '../../configs/config'
|
||||
import LoginStackParamList from './types'
|
||||
import useAuthenticateUserByName from '../../api/mutations/authentication'
|
||||
import { useJellifyServer } from '../../stores'
|
||||
|
||||
export default function ServerAuthentication({
|
||||
navigation,
|
||||
@@ -21,7 +21,7 @@ export default function ServerAuthentication({
|
||||
const [username, setUsername] = useState<string | undefined>(undefined)
|
||||
const [password, setPassword] = React.useState<string | undefined>(undefined)
|
||||
|
||||
const { server } = useJellifyContext()
|
||||
const [server] = useJellifyServer()
|
||||
|
||||
const { mutate: authenticateUserByName, isPending } = useAuthenticateUserByName({
|
||||
onSuccess: () => {
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import React from 'react'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { RootStackParamList } from '../types'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import LibrarySelector from '../../components/Global/components/library-selector'
|
||||
import LoginStackParamList from './types'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { useJellifyLibrary } from '../../stores'
|
||||
|
||||
export default function ServerLibrary({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<LoginStackParamList>
|
||||
}): React.JSX.Element {
|
||||
const { setLibrary } = useJellifyContext()
|
||||
const [, setLibrary] = useJellifyLibrary()
|
||||
|
||||
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
|
||||
@@ -1,65 +1,64 @@
|
||||
import React from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { SettingsStackParamList } from './types'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { queryClient } from '../../constants/query-client'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import LibrarySelector from '../../components/Global/components/library-selector'
|
||||
import { useJellifyLibrary } from '../../stores'
|
||||
|
||||
export default function LibrarySelectionScreen({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<SettingsStackParamList, 'LibrarySelection'>
|
||||
}): React.JSX.Element {
|
||||
const { library, setLibrary } = useJellifyContext()
|
||||
const [library, setLibrary] = useJellifyLibrary()
|
||||
|
||||
const handleLibrarySelected = useCallback(
|
||||
(libraryId: string, selectedLibrary: BaseItemDto, playlistLibrary?: BaseItemDto) => {
|
||||
// Don't proceed if the same library is selected
|
||||
if (libraryId === library?.musicLibraryId) {
|
||||
navigation.goBack()
|
||||
return
|
||||
}
|
||||
|
||||
setLibrary({
|
||||
musicLibraryId: libraryId,
|
||||
musicLibraryName: selectedLibrary.Name ?? 'No library name',
|
||||
musicLibraryPrimaryImageId: selectedLibrary.ImageTags?.Primary,
|
||||
playlistLibraryId: playlistLibrary?.Id,
|
||||
playlistLibraryPrimaryImageId: playlistLibrary?.ImageTags?.Primary,
|
||||
})
|
||||
|
||||
// Invalidate all library-related queries to refresh the data
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.AllArtistsAlphabetical] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.AllAlbumsAlphabetical] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.AllTracks] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.AllAlbums] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.AllArtists] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.Playlists] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.FavoritePlaylists] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.FavoriteArtists] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.FavoriteAlbums] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.FavoriteTracks] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.RecentlyPlayed] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.RecentlyPlayedArtists] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.FrequentArtists] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.FrequentlyPlayed] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.RecentlyAdded] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.RefreshHome] })
|
||||
|
||||
Toast.show({
|
||||
text1: 'Library changed',
|
||||
text2: `Now using ${selectedLibrary.Name}`,
|
||||
type: 'success',
|
||||
})
|
||||
|
||||
const handleLibrarySelected = (
|
||||
libraryId: string,
|
||||
selectedLibrary: BaseItemDto,
|
||||
playlistLibrary?: BaseItemDto,
|
||||
) => {
|
||||
// Don't proceed if the same library is selected
|
||||
if (libraryId === library?.musicLibraryId) {
|
||||
navigation.goBack()
|
||||
return
|
||||
}
|
||||
|
||||
setLibrary({
|
||||
musicLibraryId: libraryId,
|
||||
musicLibraryName: selectedLibrary.Name ?? 'No library name',
|
||||
musicLibraryPrimaryImageId: selectedLibrary.ImageTags?.Primary,
|
||||
playlistLibraryId: playlistLibrary?.Id,
|
||||
playlistLibraryPrimaryImageId: playlistLibrary?.ImageTags?.Primary,
|
||||
})
|
||||
|
||||
// Invalidate all library-related queries to refresh the data
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.AllArtistsAlphabetical] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.AllAlbumsAlphabetical] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.AllTracks] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.AllAlbums] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.AllArtists] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.Playlists] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.FavoritePlaylists] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.FavoriteArtists] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.FavoriteAlbums] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.FavoriteTracks] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.RecentlyPlayed] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.RecentlyPlayedArtists] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.FrequentArtists] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.FrequentlyPlayed] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.RecentlyAdded] })
|
||||
queryClient.invalidateQueries({ queryKey: [QueryKeys.RefreshHome] })
|
||||
|
||||
Toast.show({
|
||||
text1: 'Library changed',
|
||||
text2: `Now using ${selectedLibrary.Name}`,
|
||||
type: 'success',
|
||||
})
|
||||
|
||||
navigation.goBack()
|
||||
}
|
||||
},
|
||||
[setLibrary],
|
||||
)
|
||||
|
||||
const handleCancel = () => {
|
||||
navigation.goBack()
|
||||
|
||||
@@ -3,13 +3,13 @@ import { SignOutModalProps } from './types'
|
||||
import { H5, Text } from '../../components/Global/helpers/text'
|
||||
import Button from '../../components/Global/helpers/button'
|
||||
import Icon from '../../components/Global/components/icon'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import { useResetQueue } from '../../providers/Player/hooks/mutations'
|
||||
import navigationRef from '../../../navigation'
|
||||
import { useClearAllDownloads } from '../../api/mutations/download'
|
||||
import { useJellifyServer } from '../../stores'
|
||||
|
||||
export default function SignOutModal({ navigation }: SignOutModalProps): React.JSX.Element {
|
||||
const { server } = useJellifyContext()
|
||||
const [server] = useJellifyServer()
|
||||
|
||||
const { mutate: resetQueue } = useResetQueue()
|
||||
const clearDownloads = useClearAllDownloads()
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import Player, { PlayerStack } from './Player'
|
||||
import Player from './Player'
|
||||
import Tabs from './Tabs'
|
||||
import { RootStackParamList } from './types'
|
||||
import { getToken, useTheme, YStack } from 'tamagui'
|
||||
import { useJellifyContext } from '../providers'
|
||||
import { useTheme, YStack } from 'tamagui'
|
||||
import Login from './Login'
|
||||
import { createNativeStackNavigator, NativeStackHeaderProps } from '@react-navigation/native-stack'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import Context from './Context'
|
||||
import { getItemName } from '../utils/text'
|
||||
import AddToPlaylistSheet from './AddToPlaylist'
|
||||
import { Platform } from 'react-native'
|
||||
import TextTicker from 'react-native-text-ticker'
|
||||
import { TextTickerConfig } from '../components/Player/component.config'
|
||||
import { Text } from '../components/Global/helpers/text'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import AudioSpecsSheet from './Stats'
|
||||
import { useApi, useJellifyLibrary } from '../stores'
|
||||
|
||||
const RootStack = createNativeStackNavigator<RootStackParamList>()
|
||||
|
||||
export default function Root(): React.JSX.Element {
|
||||
const theme = useTheme()
|
||||
|
||||
const { api, library } = useJellifyContext()
|
||||
const api = useApi()
|
||||
const [library] = useJellifyLibrary()
|
||||
|
||||
return (
|
||||
<RootStack.Navigator initialRouteName={api && library ? 'Tabs' : 'Login'}>
|
||||
|
||||
104
src/stores/index.ts
Normal file
104
src/stores/index.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { create } from 'zustand'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { JellifyLibrary } from '../types/JellifyLibrary'
|
||||
import { JellifyServer } from '../types/JellifyServer'
|
||||
import { JellifyUser } from '../types/JellifyUser'
|
||||
import { createJSONStorage, devtools, persist } from 'zustand/middleware'
|
||||
import { stateStorage, storage } from '../constants/storage'
|
||||
import { MMKVStorageKeys } from '../enums/mmkv-storage-keys'
|
||||
import { Api } from '@jellyfin/sdk'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { JellyfinInfo } from '../api/info'
|
||||
import AXIOS_INSTANCE from '../configs/axios.config'
|
||||
import { queryClient } from '../constants/query-client'
|
||||
|
||||
type JellifyStore = {
|
||||
server: JellifyServer | undefined
|
||||
setServer: (server: JellifyServer | undefined) => void
|
||||
|
||||
user: JellifyUser | undefined
|
||||
setUser: (user: JellifyUser | undefined) => void
|
||||
|
||||
library: JellifyLibrary | undefined
|
||||
setLibrary: (library: JellifyLibrary | undefined) => void
|
||||
}
|
||||
|
||||
const useJellifyStore = create<JellifyStore>()(
|
||||
devtools(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
server: storage.getString(MMKVStorageKeys.Server)
|
||||
? (JSON.parse(storage.getString(MMKVStorageKeys.Server)!) as JellifyServer)
|
||||
: undefined,
|
||||
|
||||
setServer: (server: JellifyServer | undefined) => set({ server }),
|
||||
|
||||
user: storage.getString(MMKVStorageKeys.User)
|
||||
? (JSON.parse(storage.getString(MMKVStorageKeys.User)!) as JellifyUser)
|
||||
: undefined,
|
||||
|
||||
setUser: (user: JellifyUser | undefined) => set({ user }),
|
||||
|
||||
library: storage.getString(MMKVStorageKeys.Library)
|
||||
? (JSON.parse(storage.getString(MMKVStorageKeys.Library)!) as JellifyLibrary)
|
||||
: undefined,
|
||||
|
||||
setLibrary: (library: JellifyLibrary | undefined) => set({ library }),
|
||||
}),
|
||||
{
|
||||
name: 'jellify-context-storage',
|
||||
storage: createJSONStorage(() => stateStorage),
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
export const useJellifyServer: () => [
|
||||
JellifyServer | undefined,
|
||||
(user: JellifyServer | undefined) => void,
|
||||
] = () => {
|
||||
return useJellifyStore(useShallow((state) => [state.server, state.setServer] as const))
|
||||
}
|
||||
|
||||
export const useJellifyUser: () => [
|
||||
user: JellifyUser | undefined,
|
||||
setUser: (user: JellifyUser | undefined) => void,
|
||||
] = () => {
|
||||
return useJellifyStore(useShallow((state) => [state.user, state.setUser] as const))
|
||||
}
|
||||
|
||||
export const useJellifyLibrary: () => [
|
||||
library: JellifyLibrary | undefined,
|
||||
setLibrary: (library: JellifyLibrary | undefined) => void,
|
||||
] = () => {
|
||||
return useJellifyStore(useShallow((state) => [state.library, state.setLibrary] as const))
|
||||
}
|
||||
|
||||
export const useApi: () => Api | undefined = () => {
|
||||
const [serverUrl, userAccessToken] = useJellifyStore(
|
||||
useShallow((state) => [state.server?.url, state.user?.accessToken] as const),
|
||||
)
|
||||
|
||||
return useMemo(() => {
|
||||
if (!serverUrl) return undefined
|
||||
else return JellyfinInfo.createApi(serverUrl, userAccessToken, AXIOS_INSTANCE)
|
||||
}, [serverUrl, userAccessToken])
|
||||
}
|
||||
|
||||
export const useSignOut = () => {
|
||||
const [setServer, setUser, setLibrary] = useJellifyStore(
|
||||
useShallow((state) => [state.setServer, state.setUser, state.setLibrary]),
|
||||
)
|
||||
|
||||
return useCallback(() => {
|
||||
setServer(undefined)
|
||||
setUser(undefined)
|
||||
setLibrary(undefined)
|
||||
|
||||
queryClient.clear()
|
||||
|
||||
storage.clearAll()
|
||||
}, [setServer, setUser, setLibrary])
|
||||
}
|
||||
|
||||
export default useJellifyStore
|
||||
Reference in New Issue
Block a user