mirror of
https://github.com/anultravioletaurora/Jellify.git
synced 2026-01-05 21:30:53 -06:00
converting api client to be a singleton, this means we can get rid of a lot of code
This commit is contained in:
@@ -7,15 +7,14 @@ import { MMKVStorageKeys } from "@/enums/mmkv-storage-keys";
|
||||
import uuid from 'react-native-uuid';
|
||||
import { JellifyLibrary } from "@/types/JellifyLibrary";
|
||||
|
||||
|
||||
export default class Client {
|
||||
static #instance: Client;
|
||||
|
||||
public api : Api | undefined;
|
||||
public user : JellifyUser | undefined;
|
||||
public server : JellifyServer | undefined;
|
||||
public library : JellifyLibrary | undefined;
|
||||
public sessionId : string = uuid.v4();
|
||||
private api : Api | undefined;
|
||||
private user : JellifyUser | undefined;
|
||||
private server : JellifyServer | undefined;
|
||||
private library : JellifyLibrary | undefined;
|
||||
private sessionId : string = uuid.v4();
|
||||
|
||||
private constructor(
|
||||
api?: Api | undefined,
|
||||
@@ -31,7 +30,7 @@ export default class Client {
|
||||
|
||||
|
||||
if (user)
|
||||
this.setAndPersistUser
|
||||
this.setAndPersistUser(user)
|
||||
else if (userJson)
|
||||
this.user = JSON.parse(userJson)
|
||||
|
||||
@@ -59,19 +58,36 @@ export default class Client {
|
||||
return Client.#instance;
|
||||
}
|
||||
|
||||
public static signOut(): void {
|
||||
if (!Client.#instance) {
|
||||
Client.instance;
|
||||
}
|
||||
public static get api(): Api | undefined {
|
||||
return Client.#instance.api;
|
||||
}
|
||||
|
||||
Client.instance.removeCredentials()
|
||||
public static get server(): JellifyServer | undefined {
|
||||
return Client.#instance.server;
|
||||
}
|
||||
|
||||
public static get user(): JellifyUser | undefined {
|
||||
return Client.#instance.user;
|
||||
}
|
||||
|
||||
public static get library(): JellifyLibrary | undefined {
|
||||
return Client.#instance.library;
|
||||
}
|
||||
|
||||
public static signOut(): void {
|
||||
Client.#instance.removeCredentials()
|
||||
}
|
||||
|
||||
public static switchServer() : void {
|
||||
Client.#instance.removeServer();
|
||||
}
|
||||
|
||||
public static switchUser(): void {
|
||||
if (!Client.#instance)
|
||||
Client.instance;
|
||||
Client.#instance.removeUser();
|
||||
}
|
||||
|
||||
Client.instance.removeUser();
|
||||
public static setUser(user: JellifyUser): void {
|
||||
Client.#instance.setAndPersistUser(user);
|
||||
}
|
||||
|
||||
private setAndPersistUser(user: JellifyUser) {
|
||||
@@ -104,6 +120,12 @@ export default class Client {
|
||||
storage.delete(MMKVStorageKeys.User)
|
||||
}
|
||||
|
||||
private removeServer() {
|
||||
this.server = undefined;
|
||||
|
||||
storage.delete(MMKVStorageKeys.Server)
|
||||
}
|
||||
|
||||
private removeUser() {
|
||||
this.user = undefined;
|
||||
|
||||
@@ -129,6 +151,10 @@ export default class Client {
|
||||
public static setPrivateApiClient(server : JellifyServer, user : JellifyUser) : void {
|
||||
const api = JellyfinInfo.createApi(server.url, user.accessToken);
|
||||
|
||||
Client.#instance = new Client(api, user, server, undefined)
|
||||
Client.#instance = new Client(api, user, server, undefined);
|
||||
}
|
||||
|
||||
public static setLibrary(library : JellifyLibrary) : void {
|
||||
Client.instance.library = library
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { QueryKeys } from "../../enums/query-keys"
|
||||
import { Api } from "@jellyfin/sdk"
|
||||
import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"
|
||||
import { BaseItemKind, ItemSortBy, SortOrder } from "@jellyfin/sdk/lib/generated-client/models"
|
||||
import Client from "../client"
|
||||
|
||||
export const useArtistAlbums = (artistId: string, api: Api) => useQuery({
|
||||
queryKey: [QueryKeys.ArtistAlbums, artistId, api],
|
||||
export const useArtistAlbums = (artistId: string) => useQuery({
|
||||
queryKey: [QueryKeys.ArtistAlbums, artistId],
|
||||
queryFn: ({ queryKey }) => {
|
||||
return getItemsApi(queryKey[2] as Api).getItems({
|
||||
return getItemsApi(Client.api!).getItems({
|
||||
includeItemTypes: [BaseItemKind.MusicAlbum],
|
||||
recursive: true,
|
||||
excludeItemIds: [queryKey[1] as string],
|
||||
@@ -26,10 +26,10 @@ export const useArtistAlbums = (artistId: string, api: Api) => useQuery({
|
||||
})
|
||||
|
||||
|
||||
export const useArtistFeaturedOnAlbums = (artistId: string, api: Api) => useQuery({
|
||||
queryKey: [QueryKeys.ArtistFeaturedAlbums, artistId, api],
|
||||
export const useArtistFeaturedOnAlbums = (artistId: string) => useQuery({
|
||||
queryKey: [QueryKeys.ArtistFeaturedAlbums, artistId],
|
||||
queryFn: ({ queryKey }) => {
|
||||
return getItemsApi(queryKey[2] as Api).getItems({
|
||||
return getItemsApi(Client.api!).getItems({
|
||||
includeItemTypes: [BaseItemKind.MusicAlbum],
|
||||
recursive: true,
|
||||
excludeItemIds: [queryKey[1] as string],
|
||||
|
||||
@@ -3,34 +3,28 @@ import { Api } from "@jellyfin/sdk";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { fetchFavoriteAlbums, fetchFavoriteArtists, fetchFavoriteTracks, fetchUserData } from "./functions/favorites";
|
||||
|
||||
export const useFavoriteArtists = (api: Api, libraryId: string) => useQuery({
|
||||
queryKey: [QueryKeys.FavoriteArtists, api, libraryId],
|
||||
export const useFavoriteArtists = () => useQuery({
|
||||
queryKey: [QueryKeys.FavoriteArtists],
|
||||
queryFn: () => {
|
||||
|
||||
return fetchFavoriteArtists(api, libraryId)
|
||||
return fetchFavoriteArtists()
|
||||
}
|
||||
});
|
||||
|
||||
export const useFavoriteAlbums = (api: Api, libraryId: string) => useQuery({
|
||||
queryKey: [QueryKeys.FavoriteAlbums, api, libraryId],
|
||||
queryFn: ({ queryKey }) => {
|
||||
export const useFavoriteAlbums = () => useQuery({
|
||||
queryKey: [QueryKeys.FavoriteAlbums],
|
||||
queryFn: () => {
|
||||
|
||||
return fetchFavoriteAlbums(api, libraryId)
|
||||
return fetchFavoriteAlbums()
|
||||
}
|
||||
});
|
||||
|
||||
export const useFavoriteTracks = (api: Api, libraryId: string) => useQuery({
|
||||
queryKey: [QueryKeys.FavoriteTracks, api, libraryId],
|
||||
queryFn: ({ queryKey }) => {
|
||||
|
||||
return fetchFavoriteTracks(api, libraryId)
|
||||
}
|
||||
export const useFavoriteTracks = () => useQuery({
|
||||
queryKey: [QueryKeys.FavoriteTracks],
|
||||
queryFn: () => fetchFavoriteTracks()
|
||||
});
|
||||
|
||||
export const useUserData = (api: Api, itemId: string) => useQuery({
|
||||
queryKey: [QueryKeys.UserData, api, itemId],
|
||||
queryFn: ({ queryKey }) => {
|
||||
|
||||
return fetchUserData(api, itemId)
|
||||
}
|
||||
})
|
||||
export const useUserData = (itemId: string) => useQuery({
|
||||
queryKey: [QueryKeys.UserData, itemId],
|
||||
queryFn: () => fetchUserData(itemId)
|
||||
});
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import Client from "@/api/client";
|
||||
import { BaseItemDto, BaseItemKind, ItemSortBy, SortOrder, UserItemDataDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { getItemsApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
|
||||
export function fetchFavoriteArtists(api: Api, musicLibraryId: string): Promise<BaseItemDto[]> {
|
||||
export function fetchFavoriteArtists(): Promise<BaseItemDto[]> {
|
||||
console.debug(`Fetching user's favorite artists`);
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
getItemsApi(api)
|
||||
getItemsApi(Client.api!)
|
||||
.getItems({
|
||||
includeItemTypes: [
|
||||
BaseItemKind.MusicArtist
|
||||
],
|
||||
isFavorite: true,
|
||||
parentId: musicLibraryId,
|
||||
parentId: Client.library!.musicLibraryId,
|
||||
recursive: true,
|
||||
sortBy: [
|
||||
ItemSortBy.SortName
|
||||
@@ -35,17 +35,17 @@ export function fetchFavoriteArtists(api: Api, musicLibraryId: string): Promise<
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchFavoriteAlbums(api: Api, musicLibraryId: string): Promise<BaseItemDto[]> {
|
||||
export function fetchFavoriteAlbums(): Promise<BaseItemDto[]> {
|
||||
console.debug(`Fetching user's favorite albums`);
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
getItemsApi(api)
|
||||
getItemsApi(Client.api!)
|
||||
.getItems({
|
||||
includeItemTypes: [
|
||||
BaseItemKind.MusicAlbum
|
||||
],
|
||||
isFavorite: true,
|
||||
parentId: musicLibraryId,
|
||||
parentId: Client.library!.musicLibraryId!,
|
||||
recursive: true,
|
||||
sortBy: [
|
||||
ItemSortBy.SortName
|
||||
@@ -68,17 +68,17 @@ export function fetchFavoriteAlbums(api: Api, musicLibraryId: string): Promise<B
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchFavoriteTracks(api: Api, musicLibraryId: string): Promise<BaseItemDto[]> {
|
||||
export function fetchFavoriteTracks(): Promise<BaseItemDto[]> {
|
||||
console.debug(`Fetching user's favorite artists`);
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
getItemsApi(api)
|
||||
getItemsApi(Client.api!)
|
||||
.getItems({
|
||||
includeItemTypes: [
|
||||
BaseItemKind.Audio
|
||||
],
|
||||
isFavorite: true,
|
||||
parentId: musicLibraryId,
|
||||
parentId: Client.library!.musicLibraryId,
|
||||
recursive: true,
|
||||
sortBy: [
|
||||
ItemSortBy.SortName
|
||||
@@ -101,9 +101,9 @@ export function fetchFavoriteTracks(api: Api, musicLibraryId: string): Promise<B
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchUserData(api: Api, itemId: string): Promise<UserItemDataDto> {
|
||||
export function fetchUserData(itemId: string): Promise<UserItemDataDto> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
getItemsApi(api)
|
||||
getItemsApi(Client.api!)
|
||||
.getItemUserData({
|
||||
itemId
|
||||
}).then((response) => {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import Client from "@/api/client";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { getItemsApi } from "@jellyfin/sdk/lib/utils/api/items-api";
|
||||
import { isUndefined } from "lodash";
|
||||
|
||||
|
||||
export function fetchMusicLibraries(api: Api): Promise<BaseItemDto[]> {
|
||||
export function fetchMusicLibraries(): Promise<BaseItemDto[]> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
console.debug("Fetching music libraries from Jellyfin");
|
||||
|
||||
let libraries = await getItemsApi(api).getItems({
|
||||
let libraries = await getItemsApi(Client.api!).getItems({
|
||||
includeItemTypes: ['CollectionFolder']
|
||||
});
|
||||
|
||||
@@ -24,11 +24,11 @@ export function fetchMusicLibraries(api: Api): Promise<BaseItemDto[]> {
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchPlaylistLibrary(api: Api): Promise<BaseItemDto> {
|
||||
export function fetchPlaylistLibrary(): Promise<BaseItemDto> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
console.debug("Fetching playlist library from Jellyfin");
|
||||
|
||||
let libraries = await getItemsApi(api).getItems({
|
||||
let libraries = await getItemsApi(Client.api!).getItems({
|
||||
includeItemTypes: ['ManualPlaylistsFolder'],
|
||||
excludeItemTypes: ['CollectionFolder']
|
||||
});
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import Client from "@/api/client";
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { BaseItemDto, ItemSortBy, SortOrder } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { getItemsApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
|
||||
export function fetchUserPlaylists(api: Api, userId: string, playlistLibraryId: string): Promise<BaseItemDto[]> {
|
||||
export function fetchUserPlaylists(): Promise<BaseItemDto[]> {
|
||||
console.debug("Fetching user playlists");
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
getItemsApi(api)
|
||||
getItemsApi(Client.api!)
|
||||
.getItems({
|
||||
userId: userId,
|
||||
parentId: playlistLibraryId,
|
||||
userId: Client.user!.id,
|
||||
parentId: Client.library!.playlistLibraryId!,
|
||||
fields: [
|
||||
"Path"
|
||||
],
|
||||
@@ -36,13 +37,13 @@ export function fetchUserPlaylists(api: Api, userId: string, playlistLibraryId:
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchPublicPlaylists(api: Api, playlistLibraryId: string): Promise<BaseItemDto[]> {
|
||||
export function fetchPublicPlaylists(): Promise<BaseItemDto[]> {
|
||||
console.debug("Fetching public playlists");
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
getItemsApi(api)
|
||||
getItemsApi(Client.api!)
|
||||
.getItems({
|
||||
parentId: playlistLibraryId,
|
||||
parentId: Client.library!.playlistLibraryId!,
|
||||
sortBy: [
|
||||
ItemSortBy.IsFolder,
|
||||
ItemSortBy.SortName
|
||||
|
||||
@@ -8,13 +8,13 @@ export function fetchRecentlyPlayed(): Promise<BaseItemDto[]> {
|
||||
console.debug("Fetching recently played items");
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
getItemsApi(Client.instance.api!)
|
||||
getItemsApi(Client.api!)
|
||||
.getItems({
|
||||
includeItemTypes: [
|
||||
BaseItemKind.Audio
|
||||
],
|
||||
limit: queryConfig.limits.recents,
|
||||
parentId: Client.instance.library!.musicLibraryId,
|
||||
parentId: Client.library!.musicLibraryId,
|
||||
recursive: true,
|
||||
sortBy: [
|
||||
ItemSortBy.DatePlayed
|
||||
@@ -37,4 +37,17 @@ export function fetchRecentlyPlayed(): Promise<BaseItemDto[]> {
|
||||
reject(error);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchRecentlyPlayedArtists() : Promise<BaseItemDto[]> {
|
||||
return fetchRecentlyPlayed()
|
||||
.then((tracks) => {
|
||||
return getItemsApi(Client.api!)
|
||||
.getItems({
|
||||
ids: tracks.map(track => track.ArtistItems![0].Id!)
|
||||
})
|
||||
.then((recentArtists) => {
|
||||
return recentArtists.data.Items!
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3,22 +3,12 @@ import { Api } from "@jellyfin/sdk";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { fetchMusicLibraries, fetchPlaylistLibrary } from "./functions/libraries";
|
||||
|
||||
export const useMusicLibraries = (api: Api) => useQuery({
|
||||
queryKey: [QueryKeys.Libraries, api],
|
||||
queryFn: async ({ queryKey }) => {
|
||||
|
||||
const api : Api = queryKey[1] as Api;
|
||||
|
||||
return await fetchMusicLibraries(api)
|
||||
}
|
||||
export const useMusicLibraries = () => useQuery({
|
||||
queryKey: [QueryKeys.Libraries],
|
||||
queryFn: () => fetchMusicLibraries()
|
||||
});
|
||||
|
||||
export const usePlaylistLibrary = (api: Api) => useQuery({
|
||||
queryKey: [QueryKeys.Playlist, api],
|
||||
queryFn: async ({ queryKey }) => {
|
||||
|
||||
const api : Api = queryKey[1] as Api;
|
||||
|
||||
return await fetchPlaylistLibrary(api)
|
||||
}
|
||||
export const usePlaylistLibrary = () => useQuery({
|
||||
queryKey: [QueryKeys.Playlist],
|
||||
queryFn: () => fetchPlaylistLibrary()
|
||||
});
|
||||
@@ -3,14 +3,8 @@ import { Api } from "@jellyfin/sdk";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { fetchUserPlaylists } from "./functions/playlists";
|
||||
|
||||
export const useUserPlaylists = (api: Api, userId: string, playlistLibraryId: string) => useQuery({
|
||||
queryKey: [QueryKeys.UserPlaylists, api, userId, playlistLibraryId],
|
||||
queryFn: ({ queryKey }) => {
|
||||
const api: Api = queryKey[1] as Api;
|
||||
const userId: string = queryKey[2] as string;
|
||||
const playlistLibraryId: string = queryKey[3] as string;
|
||||
|
||||
return fetchUserPlaylists(api, userId, playlistLibraryId);
|
||||
}
|
||||
})
|
||||
export const useUserPlaylists = () => useQuery({
|
||||
queryKey: [QueryKeys.UserPlaylists],
|
||||
queryFn: () => fetchUserPlaylists()
|
||||
});
|
||||
|
||||
|
||||
@@ -1,29 +1,15 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { QueryKeys } from "../../enums/query-keys";
|
||||
import { fetchRecentlyPlayed } from "./functions/recents";
|
||||
import { fetchRecentlyPlayed, fetchRecentlyPlayedArtists } from "./functions/recents";
|
||||
import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"
|
||||
import Client from "../client";
|
||||
|
||||
export const useRecentlyPlayed = () => useQuery({
|
||||
queryKey: [QueryKeys.RecentlyPlayed],
|
||||
queryFn: () => {
|
||||
|
||||
return fetchRecentlyPlayed()
|
||||
}
|
||||
queryFn: () => fetchRecentlyPlayed()
|
||||
});
|
||||
|
||||
export const useRecentlyPlayedArtists = () => useQuery({
|
||||
queryKey: [QueryKeys.RecentlyPlayedArtists],
|
||||
queryFn: () => {
|
||||
return fetchRecentlyPlayed()
|
||||
.then((tracks) => {
|
||||
return getItemsApi(Client.instance.api!)
|
||||
.getItems({
|
||||
ids: tracks.map(track => track.ArtistItems![0].Id!)
|
||||
})
|
||||
.then((recentArtists) => {
|
||||
return recentArtists.data.Items!
|
||||
});
|
||||
});
|
||||
}
|
||||
queryFn: () => fetchRecentlyPlayedArtists()
|
||||
});
|
||||
@@ -1,16 +1,15 @@
|
||||
import { QueryKeys } from "@/enums/query-keys";
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { ItemSortBy } from "@jellyfin/sdk/lib/generated-client/models/item-sort-by";
|
||||
import { getItemsApi } from "@jellyfin/sdk/lib/utils/api/items-api";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { queryConfig } from "./query.config";
|
||||
import Client from "../client";
|
||||
|
||||
export const useItemTracks = (itemId: string, api: Api, sort: boolean = false) => useQuery({
|
||||
queryKey: [QueryKeys.ItemTracks, itemId, api, sort],
|
||||
export const useItemTracks = (itemId: string, sort: boolean = false) => useQuery({
|
||||
queryKey: [QueryKeys.ItemTracks, itemId, sort],
|
||||
queryFn: ({ queryKey }) => {
|
||||
|
||||
const itemId : string = queryKey[1] as string;
|
||||
const api : Api = queryKey[2] as Api;
|
||||
const sort : boolean = queryKey[3] as boolean;
|
||||
|
||||
let sortBy: ItemSortBy[] = [];
|
||||
@@ -23,7 +22,7 @@ export const useItemTracks = (itemId: string, api: Api, sort: boolean = false) =
|
||||
]
|
||||
}
|
||||
|
||||
return getItemsApi(api).getItems({
|
||||
return getItemsApi(Client.api!).getItems({
|
||||
parentId: itemId,
|
||||
sortBy
|
||||
})
|
||||
|
||||
@@ -3,7 +3,6 @@ import { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||
import { ScrollView, YStack, XStack } from "tamagui";
|
||||
import { CachedImage } from "@georstat/react-native-image-cache";
|
||||
import { getImageApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { useApiClientContext } from "../jellyfin-api-provider";
|
||||
import { BaseItemDto, ImageType } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { queryConfig } from "../../api/queries/query.config";
|
||||
import { H4, H5, Text } from "../Global/helpers/text";
|
||||
@@ -15,6 +14,7 @@ import { useItemTracks } from "@/api/queries/tracks";
|
||||
import { SafeAreaView, useSafeAreaFrame } from "react-native-safe-area-context";
|
||||
import FavoriteHeaderButton from "../Global/components/favorite-header-button";
|
||||
import { useEffect } from "react";
|
||||
import Client from "@/api/client";
|
||||
|
||||
interface AlbumProps {
|
||||
album: BaseItemDto,
|
||||
@@ -31,12 +31,11 @@ export default function Album(props: AlbumProps): React.JSX.Element {
|
||||
}
|
||||
})
|
||||
|
||||
const { apiClient } = useApiClientContext();
|
||||
const { nowPlaying, nowPlayingIsFavorite } = usePlayerContext();
|
||||
|
||||
const { width } = useSafeAreaFrame();
|
||||
|
||||
const { data: tracks, isLoading, refetch } = useItemTracks(props.album.Id!, apiClient!, true);
|
||||
const { data: tracks, isLoading, refetch } = useItemTracks(props.album.Id!, true);
|
||||
|
||||
useEffect(() => {
|
||||
refetch();
|
||||
@@ -49,7 +48,7 @@ export default function Album(props: AlbumProps): React.JSX.Element {
|
||||
<ScrollView contentInsetAdjustmentBehavior="automatic">
|
||||
<YStack alignItems="center" minHeight={width / 1.1}>
|
||||
<CachedImage
|
||||
source={getImageApi(apiClient!)
|
||||
source={getImageApi(Client.api!)
|
||||
.getItemImageUrlById(
|
||||
props.album.Id!,
|
||||
ImageType.Primary,
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { useFavoriteAlbums } from "@/api/queries/favorites";
|
||||
import { AlbumsProps } from "../types";
|
||||
import { useApiClientContext } from "../jellyfin-api-provider";
|
||||
import { SafeAreaView, useSafeAreaFrame } from "react-native-safe-area-context";
|
||||
import { ItemCard } from "../Global/helpers/item-card";
|
||||
import { FlatList, RefreshControl } from "react-native";
|
||||
|
||||
export default function Albums({ navigation }: AlbumsProps) : React.JSX.Element {
|
||||
const { apiClient, library } = useApiClientContext();
|
||||
const { data: albums, refetch, isPending } = useFavoriteAlbums(apiClient!, library!.musicLibraryId);
|
||||
const { data: albums, refetch, isPending } = useFavoriteAlbums();
|
||||
|
||||
const { width } = useSafeAreaFrame();
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ScrollView, YStack } from "tamagui";
|
||||
import { useArtistAlbums } from "../../api/queries/artist";
|
||||
import { useApiClientContext } from "../jellyfin-api-provider";
|
||||
import { FlatList } from "react-native";
|
||||
import { ItemCard } from "../Global/helpers/item-card";
|
||||
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||
@@ -13,6 +12,7 @@ import { queryConfig } from "@/api/queries/query.config";
|
||||
import { getImageApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { SafeAreaView, useSafeAreaFrame } from "react-native-safe-area-context";
|
||||
import FavoriteHeaderButton from "../Global/components/favorite-header-button";
|
||||
import Client from "@/api/client";
|
||||
|
||||
interface ArtistProps {
|
||||
artist: BaseItemDto
|
||||
@@ -31,13 +31,11 @@ export default function Artist(props: ArtistProps): React.JSX.Element {
|
||||
|
||||
const [columns, setColumns] = useState<number>(2);
|
||||
|
||||
const { apiClient } = useApiClientContext();
|
||||
|
||||
const { height, width } = useSafeAreaFrame();
|
||||
|
||||
const bannerHeight = height / 6;
|
||||
|
||||
const { data: albums } = useArtistAlbums(props.artist.Id!, apiClient!);
|
||||
const { data: albums } = useArtistAlbums(props.artist.Id!);
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1 }} edges={["top", "right", "left"]}>
|
||||
@@ -46,7 +44,7 @@ export default function Artist(props: ArtistProps): React.JSX.Element {
|
||||
alignContent="center">
|
||||
<YStack alignContent="center" justifyContent="center" minHeight={bannerHeight}>
|
||||
<CachedImage
|
||||
source={getImageApi(apiClient!)
|
||||
source={getImageApi(Client.api!)
|
||||
.getItemImageUrlById(
|
||||
props.artist.Id!,
|
||||
ImageType.Primary,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useFavoriteArtists } from "@/api/queries/favorites";
|
||||
import { SafeAreaView, useSafeAreaFrame } from "react-native-safe-area-context";
|
||||
import { useApiClientContext } from "../jellyfin-api-provider";
|
||||
import React from "react";
|
||||
import { FlatList, RefreshControl } from "react-native";
|
||||
import { ItemCard } from "../Global/helpers/item-card";
|
||||
@@ -8,8 +7,7 @@ import { ArtistsProps } from "../types";
|
||||
|
||||
export default function Artists({ navigation }: ArtistsProps): React.JSX.Element {
|
||||
|
||||
const { apiClient, library } = useApiClientContext();
|
||||
const { data: artists, refetch, isPending } = useFavoriteArtists(apiClient!, library!.musicLibraryId);
|
||||
const { data: artists, refetch, isPending } = useFavoriteArtists();
|
||||
|
||||
const { width } = useSafeAreaFrame();
|
||||
|
||||
|
||||
@@ -2,17 +2,16 @@ import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Icon from "../helpers/icon";
|
||||
import { Colors } from "@/enums/colors";
|
||||
import { useApiClientContext } from "@/components/jellyfin-api-provider";
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { isUndefined } from "lodash";
|
||||
import { useUserData } from "@/api/queries/favorites";
|
||||
import { Spinner } from "tamagui";
|
||||
import Client from "@/api/client";
|
||||
|
||||
interface SetFavoriteMutation {
|
||||
item: BaseItemDto,
|
||||
api: Api
|
||||
}
|
||||
|
||||
export default function FavoriteHeaderButton({
|
||||
@@ -23,16 +22,13 @@ export default function FavoriteHeaderButton({
|
||||
onToggle?: () => void
|
||||
}) : React.JSX.Element {
|
||||
|
||||
|
||||
const { apiClient } = useApiClientContext();
|
||||
|
||||
const [isFavorite, setIsFavorite] = useState<boolean>(isFavoriteItem(item));
|
||||
|
||||
const { data, isFetching, isFetched, refetch } = useUserData(apiClient!, item.Id!);
|
||||
const { data, isFetching, isFetched, refetch } = useUserData(item.Id!);
|
||||
|
||||
const useSetFavorite = useMutation({
|
||||
mutationFn: async (mutation: SetFavoriteMutation) => {
|
||||
return getUserLibraryApi(mutation.api)
|
||||
return getUserLibraryApi(Client.api!)
|
||||
.markFavoriteItem({
|
||||
itemId: mutation.item.Id!
|
||||
})
|
||||
@@ -46,7 +42,7 @@ export default function FavoriteHeaderButton({
|
||||
|
||||
const useRemoveFavorite = useMutation({
|
||||
mutationFn: async (mutation: SetFavoriteMutation) => {
|
||||
return getUserLibraryApi(mutation.api)
|
||||
return getUserLibraryApi(Client.api!)
|
||||
.unmarkFavoriteItem({
|
||||
itemId: mutation.item.Id!
|
||||
})
|
||||
@@ -58,9 +54,9 @@ export default function FavoriteHeaderButton({
|
||||
|
||||
const toggleFavorite = () => {
|
||||
if (isFavorite)
|
||||
useRemoveFavorite.mutate({ item, api: apiClient!})
|
||||
useRemoveFavorite.mutate({ item })
|
||||
else
|
||||
useSetFavorite.mutate({ item, api: apiClient! })
|
||||
useSetFavorite.mutate({ item })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -7,12 +7,12 @@ import { BaseItemDto, ImageType } from "@jellyfin/sdk/lib/generated-client/model
|
||||
import { Colors } from "@/enums/colors";
|
||||
import { CachedImage } from "@georstat/react-native-image-cache";
|
||||
import { getImageApi } from "@jellyfin/sdk/lib/utils/api/image-api";
|
||||
import { useApiClientContext } from "@/components/jellyfin-api-provider";
|
||||
import { queryConfig } from "@/api/queries/query.config";
|
||||
import { useSafeAreaFrame } from "react-native-safe-area-context";
|
||||
import Icon from "../helpers/icon";
|
||||
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||
import { StackParamList } from "@/components/types";
|
||||
import Client from "@/api/client";
|
||||
|
||||
interface TrackProps {
|
||||
track: BaseItemDto;
|
||||
@@ -42,7 +42,6 @@ export default function Track({
|
||||
}) : React.JSX.Element {
|
||||
|
||||
const { width } = useSafeAreaFrame();
|
||||
const { apiClient } = useApiClientContext();
|
||||
const { nowPlaying, usePlayNewQueue } = usePlayerContext();
|
||||
|
||||
const isPlaying = nowPlaying?.item.Id === track.Id;
|
||||
@@ -76,7 +75,7 @@ export default function Track({
|
||||
>
|
||||
{ showArtwork ? (
|
||||
<CachedImage
|
||||
source={getImageApi(apiClient!)
|
||||
source={getImageApi(Client.api!)
|
||||
.getItemImageUrlById(
|
||||
track.AlbumId ?? "",
|
||||
ImageType.Primary,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { AvatarProps as TamaguiAvatarProps } from "tamagui";
|
||||
import { Avatar as TamaguiAvatar, YStack } from "tamagui"
|
||||
import { Text } from "./text"
|
||||
import { useApiClientContext } from "@/components/jellyfin-api-provider";
|
||||
import { Colors } from "react-native/Libraries/NewAppScreen";
|
||||
import Client from "@/api/client";
|
||||
|
||||
interface AvatarProps extends TamaguiAvatarProps {
|
||||
itemId: string;
|
||||
@@ -11,8 +11,6 @@ interface AvatarProps extends TamaguiAvatarProps {
|
||||
|
||||
export default function Avatar(props: AvatarProps): React.JSX.Element {
|
||||
|
||||
const { server } = useApiClientContext();
|
||||
|
||||
return (
|
||||
<YStack alignItems="center" marginHorizontal={10}>
|
||||
<TamaguiAvatar
|
||||
@@ -20,7 +18,7 @@ export default function Avatar(props: AvatarProps): React.JSX.Element {
|
||||
borderRadius={!!!props.circular ? 4 : 'unset'}
|
||||
{...props}
|
||||
>
|
||||
<TamaguiAvatar.Image src={`${server!.url}/Items/${props.itemId!}/Images/Primary`} />
|
||||
<TamaguiAvatar.Image src={`${Client.server!.url}/Items/${props.itemId!}/Images/Primary`} />
|
||||
<TamaguiAvatar.Fallback backgroundColor={Colors.Secondary}/>
|
||||
</TamaguiAvatar>
|
||||
{ props.children && (
|
||||
|
||||
@@ -1,12 +1,33 @@
|
||||
import Client from "@/api/client";
|
||||
import { useItemImage } from "@/api/queries/image";
|
||||
import { Blurhash } from "react-native-blurhash";
|
||||
import { ImageSourcePropType } from "react-native/Libraries/Image/Image";
|
||||
import { Image, View } from "tamagui";
|
||||
|
||||
const BlurhashLoading = (props: any) => {
|
||||
return (
|
||||
<Blurhash blurhash={props}>
|
||||
|
||||
</Blurhash>
|
||||
)
|
||||
interface BlurhashLoadingProps {
|
||||
itemId: string;
|
||||
blurhash: string;
|
||||
size: number
|
||||
}
|
||||
|
||||
export default BlurhashLoading;
|
||||
export default function BlurhashLoading(props: BlurhashLoadingProps) : React.JSX.Element {
|
||||
|
||||
const { data: image, isSuccess } = useItemImage(Client.api!, props.itemId);
|
||||
|
||||
return (
|
||||
<View minHeight={props.size}>
|
||||
|
||||
{ isSuccess ? (
|
||||
<Image
|
||||
src={image}
|
||||
style={{
|
||||
height: props.size,
|
||||
width: props.size,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Blurhash blurhash={props.blurhash} style={{ flex: 1 }} />
|
||||
)
|
||||
}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { } from "react";
|
||||
import type { CardProps as TamaguiCardProps } from "tamagui"
|
||||
import { H5, Card as TamaguiCard, View } from "tamagui";
|
||||
import { useApiClientContext } from "../../jellyfin-api-provider";
|
||||
import { getImageApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { ImageType } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { CachedImage } from "@georstat/react-native-image-cache";
|
||||
@@ -9,6 +8,7 @@ import invert from "invert-color"
|
||||
import { Blurhash } from "react-native-blurhash"
|
||||
import { queryConfig } from "../../../api/queries/query.config";
|
||||
import { Text } from "./text";
|
||||
import Client from "@/api/client";
|
||||
|
||||
interface CardProps extends TamaguiCardProps {
|
||||
artistName?: string;
|
||||
@@ -21,14 +21,12 @@ interface CardProps extends TamaguiCardProps {
|
||||
|
||||
export function ItemCard(props: CardProps) {
|
||||
|
||||
const { apiClient } = useApiClientContext();
|
||||
|
||||
const dimensions = props.width && typeof(props.width) === "number" ? { width: props.width, height: props.width } : { width: 150, height: 150 };
|
||||
|
||||
const cardTextColor = props.blurhash ? invert(Blurhash.getAverageColor(props.blurhash)!, true) : undefined;
|
||||
|
||||
const logoDimensions = props.width && typeof(props.width) === "number" ? { width: props.width / 2, height: props.width / 2 }: { width: 100, height: 100 };
|
||||
const cardLogoSource = getImageApi(apiClient!).getItemImageUrlById(props.itemId, ImageType.Logo);
|
||||
const cardLogoSource = getImageApi(Client.api!).getItemImageUrlById(props.itemId, ImageType.Logo);
|
||||
|
||||
return (
|
||||
<View
|
||||
@@ -50,7 +48,7 @@ export function ItemCard(props: CardProps) {
|
||||
</TamaguiCard.Header>
|
||||
<TamaguiCard.Footer padded>
|
||||
<CachedImage
|
||||
source={getImageApi(apiClient!)
|
||||
source={getImageApi(Client.api!)
|
||||
.getItemImageUrlById(
|
||||
props.itemId,
|
||||
ImageType.Logo,
|
||||
@@ -70,7 +68,7 @@ export function ItemCard(props: CardProps) {
|
||||
</TamaguiCard.Footer>
|
||||
<TamaguiCard.Background>
|
||||
<CachedImage
|
||||
source={getImageApi(apiClient!)
|
||||
source={getImageApi(Client.api!)
|
||||
.getItemImageUrlById(
|
||||
props.itemId,
|
||||
ImageType.Primary,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useUserPlaylists } from "@/api/queries/playlist";
|
||||
import { ItemCard } from "@/components/Global/helpers/item-card";
|
||||
import { H2 } from "@/components/Global/helpers/text";
|
||||
import { useApiClientContext } from "@/components/jellyfin-api-provider";
|
||||
import { ProvidedHomeProps } from "@/components/types";
|
||||
import React from "react";
|
||||
import { FlatList } from "react-native";
|
||||
@@ -9,9 +8,7 @@ import { View } from "tamagui";
|
||||
|
||||
export default function Playlists({ navigation }: ProvidedHomeProps) : React.JSX.Element {
|
||||
|
||||
const { apiClient, user, library } = useApiClientContext();
|
||||
|
||||
const { data: playlists } = useUserPlaylists(apiClient!, user!.id, library!.playlistLibraryId);
|
||||
const { data: playlists } = useUserPlaylists();
|
||||
|
||||
return (
|
||||
<View>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { createContext, ReactNode, useContext, useState } from "react";
|
||||
import { useRecentlyPlayed, useRecentlyPlayedArtists } from "../../api/queries/recently-played";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { useQueries } from "@tanstack/react-query";
|
||||
|
||||
interface HomeContext {
|
||||
refreshing: boolean;
|
||||
|
||||
@@ -3,7 +3,6 @@ import _ from "lodash";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { MMKVStorageKeys } from "../../../enums/mmkv-storage-keys";
|
||||
import { JellifyServer } from "../../../types/JellifyServer";
|
||||
import { useApiClientContext } from "../../jellyfin-api-provider";
|
||||
import { Spacer, Spinner, View, XStack, ZStack } from "tamagui";
|
||||
import { SwitchWithLabel } from "../../Global/helpers/switch-with-label";
|
||||
import { H1 } from "../../Global/helpers/text";
|
||||
@@ -15,22 +14,21 @@ import { JellyfinInfo } from "../../../api/info";
|
||||
import { Jellyfin } from "@jellyfin/sdk/lib/jellyfin";
|
||||
import { getSystemApi } from "@jellyfin/sdk/lib/utils/api/system-api";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import Client from "@/api/client";
|
||||
|
||||
export default function ServerAddress(): React.JSX.Element {
|
||||
|
||||
const { setServer } = useApiClientContext();
|
||||
|
||||
const [useHttps, setUseHttps] = useState<boolean>(true);
|
||||
const [serverAddress, setServerAddress] = useState<string | undefined>(undefined);
|
||||
|
||||
const useServerMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
let jellyfin = new JellyfinInfo(JellyfinInfo);
|
||||
let jellyfin = new Jellyfin(JellyfinInfo);
|
||||
|
||||
if (!!!serverAddress)
|
||||
throw new Error("Server address was empty");
|
||||
|
||||
let api = jellyfin.createApi(`${useHttps ? https : http}${serverAddress}`);
|
||||
let api = jellyfin.createApi(`${useHttps ? https : http}${serverAddress}`);
|
||||
|
||||
return getSystemApi(api).getPublicSystemInfo();
|
||||
},
|
||||
@@ -41,7 +39,7 @@ export default function ServerAddress(): React.JSX.Element {
|
||||
console.debug("REMOVE THIS::onSuccess variable", publicSystemInfoResponse.data);
|
||||
console.log(`Connected to Jellyfin ${publicSystemInfoResponse.data.Version!}`);
|
||||
|
||||
let jellifyServer: JellifyServer = {
|
||||
const server: JellifyServer = {
|
||||
url: `${useHttps ? https : http}${serverAddress!}`,
|
||||
address: serverAddress!,
|
||||
name: publicSystemInfoResponse.data.ServerName!,
|
||||
@@ -49,11 +47,11 @@ export default function ServerAddress(): React.JSX.Element {
|
||||
startUpComplete: publicSystemInfoResponse.data.StartupWizardCompleted!
|
||||
}
|
||||
|
||||
setServer(jellifyServer);
|
||||
Client.setPublicApiClient(server);
|
||||
},
|
||||
onError: async (error: Error) => {
|
||||
console.error("An error occurred connecting to the Jellyfin instance", error);
|
||||
return storage.set(MMKVStorageKeys.Server, "");
|
||||
Client.signOut();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from "react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useApiClientContext } from "../../jellyfin-api-provider";
|
||||
import _ from "lodash";
|
||||
import { JellyfinCredentials } from "../../../api/types/jellyfin-credentials";
|
||||
import { Spinner, View, YStack, ZStack } from "tamagui";
|
||||
@@ -9,16 +8,15 @@ import { H1 } from "../../Global/helpers/text";
|
||||
import Button from "../../Global/helpers/button";
|
||||
import Input from "../../Global/helpers/input";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import Client from "@/api/client";
|
||||
|
||||
export default function ServerAuthentication(): React.JSX.Element {
|
||||
const { username, setUsername } = useAuthenticationContext();
|
||||
const [password, setPassword] = React.useState<string | undefined>('');
|
||||
|
||||
const { server, setServer, setUser, apiClient } = useApiClientContext();
|
||||
|
||||
const useApiMutation = useMutation({
|
||||
mutationFn: async (credentials: JellyfinCredentials) => {
|
||||
return await apiClient!.authenticateUserByName(credentials.username, credentials.password!);
|
||||
return await Client.api!.authenticateUserByName(credentials.username, credentials.password!);
|
||||
},
|
||||
onSuccess: async (authResult) => {
|
||||
|
||||
@@ -33,7 +31,7 @@ export default function ServerAuthentication(): React.JSX.Element {
|
||||
return Promise.reject(new Error("Unable to login"));
|
||||
|
||||
console.log(`Successfully signed in to server`)
|
||||
return setUser({
|
||||
return Client.setUser({
|
||||
id: authResult.data.User!.Id!,
|
||||
name: authResult.data.User!.Name!,
|
||||
accessToken: (authResult.data.AccessToken as string)
|
||||
@@ -41,19 +39,16 @@ export default function ServerAuthentication(): React.JSX.Element {
|
||||
},
|
||||
onError: async (error: Error) => {
|
||||
console.error("An error occurred connecting to the Jellyfin instance", error);
|
||||
return Promise.reject(`An error occured signing into ${server!.name}`);
|
||||
return Promise.reject(`An error occured signing into ${Client.server!.name}`);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<H1>
|
||||
{ `Sign in to ${server?.name ?? "Jellyfin"}`}
|
||||
{ `Sign in to ${Client.server!.name ?? "Jellyfin"}`}
|
||||
</H1>
|
||||
<Button
|
||||
onPress={() => {
|
||||
setServer(undefined);
|
||||
}}>
|
||||
<Button onPress={() => Client.switchServer()}>
|
||||
Switch Server
|
||||
</Button>
|
||||
|
||||
@@ -81,7 +76,7 @@ export default function ServerAuthentication(): React.JSX.Element {
|
||||
onPress={() => {
|
||||
|
||||
if (!_.isUndefined(username)) {
|
||||
console.log(`Signing in to ${server!.name}`);
|
||||
console.log(`Signing in to ${Client.server!.name}`);
|
||||
useApiMutation.mutate({ username, password });
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useApiClientContext } from "../../jellyfin-api-provider";
|
||||
import { Spinner, Text, ToggleGroup, View } from "tamagui";
|
||||
import { useAuthenticationContext } from "../provider";
|
||||
import { H1, Label } from "../../Global/helpers/text";
|
||||
@@ -7,27 +6,14 @@ import Button from "../../Global/helpers/button";
|
||||
import _ from "lodash";
|
||||
import { useMusicLibraries, usePlaylistLibrary } from "@/api/queries/libraries";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import Client from "@/api/client";
|
||||
|
||||
export default function ServerLibrary(): React.JSX.Element {
|
||||
|
||||
const { libraryId, setLibraryId } = useAuthenticationContext();
|
||||
const { apiClient, setUser, setLibrary } = useApiClientContext();
|
||||
|
||||
const { data : libraries, isError, isPending, refetch: refetchMusicLibraries } = useMusicLibraries(apiClient!);
|
||||
const { data : playlistLibrary, refetch: refetchPlaylistLibrary } = usePlaylistLibrary(apiClient!);
|
||||
|
||||
useEffect(() => {
|
||||
refetchMusicLibraries();
|
||||
refetchPlaylistLibrary();
|
||||
}, [
|
||||
apiClient
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
console.log(libraries)
|
||||
}, [
|
||||
libraries
|
||||
])
|
||||
const { data : libraries, isError, isPending, refetch: refetchMusicLibraries } = useMusicLibraries();
|
||||
const { data : playlistLibrary, refetch: refetchPlaylistLibrary } = usePlaylistLibrary();
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
@@ -59,19 +45,18 @@ export default function ServerLibrary(): React.JSX.Element {
|
||||
|
||||
<Button disabled={!!!libraryId}
|
||||
onPress={() => {
|
||||
setLibrary({
|
||||
Client.setLibrary({
|
||||
musicLibraryId: libraryId!,
|
||||
musicLibraryName: libraries?.filter((library) => library.Id == libraryId)[0].Name ?? "No library name",
|
||||
musicLibraryPrimaryImageId: libraries?.filter((library) => library.Id == libraryId)[0].ImageTags!.Primary,
|
||||
playlistLibraryId: playlistLibrary!.Id!,
|
||||
playlistLibraryPrimaryImageId: playlistLibrary!.ImageTags!.Primary,
|
||||
|
||||
})
|
||||
});
|
||||
}}>
|
||||
Let's Go!
|
||||
</Button>
|
||||
|
||||
<Button onPress={() => setUser(undefined)}>
|
||||
<Button onPress={() => Client.switchUser()}>
|
||||
Switch User
|
||||
</Button>
|
||||
</SafeAreaView>
|
||||
|
||||
@@ -39,7 +39,7 @@ export function Miniplayer({ navigation }: { navigation : NavigationHelpers<Para
|
||||
alignContent="center"
|
||||
flex={1}>
|
||||
<CachedImage
|
||||
source={getImageApi(apiClient!)
|
||||
source={getImageApi(Client.api!)
|
||||
.getItemImageUrlById(
|
||||
nowPlaying!.item.AlbumId ?? "",
|
||||
ImageType.Primary,
|
||||
|
||||
@@ -77,7 +77,7 @@ export default function PlayerScreen({ navigation }: { navigation: NativeStackNa
|
||||
}}
|
||||
>
|
||||
<CachedImage
|
||||
source={getImageApi(apiClient!)
|
||||
source={getImageApi(Client.api!)
|
||||
.getItemImageUrlById(
|
||||
nowPlaying!.item.AlbumId ?? "",
|
||||
ImageType.Primary,
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function Playlist(props: PlaylistProps): React.JSX.Element {
|
||||
|
||||
const { nowPlaying, nowPlayingIsFavorite } = usePlayerContext();
|
||||
|
||||
const { data: tracks, isLoading, refetch } = useItemTracks(props.playlist.Id!, apiClient!);
|
||||
const { data: tracks, isLoading, refetch } = useItemTracks(props.playlist.Id!);
|
||||
|
||||
useEffect(() => {
|
||||
refetch();
|
||||
@@ -39,7 +39,7 @@ export default function Playlist(props: PlaylistProps): React.JSX.Element {
|
||||
<ScrollView contentInsetAdjustmentBehavior="automatic">
|
||||
<YStack alignItems="center">
|
||||
<CachedImage
|
||||
source={getImageApi(apiClient!)
|
||||
source={getImageApi(Client.api!)
|
||||
.getItemImageUrlById(
|
||||
props.playlist.Id!,
|
||||
ImageType.Primary,
|
||||
|
||||
@@ -14,14 +14,14 @@ export default function ServerDetails() : React.JSX.Element {
|
||||
<H5>Access Token</H5>
|
||||
<XStack>
|
||||
<Icon name="hand-coin-outline" />
|
||||
<Text>{apiClient!.accessToken}</Text>
|
||||
<Text>{Client.api!!.accessToken}</Text>
|
||||
</XStack>
|
||||
</YStack>
|
||||
<YStack>
|
||||
<H5>Jellyfin Server</H5>
|
||||
<XStack>
|
||||
<Icon name="server-network" />
|
||||
<Text>{apiClient!.basePath}</Text>
|
||||
<Text>{Client.api!.basePath}</Text>
|
||||
</XStack>
|
||||
</YStack>
|
||||
</YStack>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||
|
||||
export default function Tracks({ navigation }: { navigation: NativeStackNavigationProp<StackParamList> }) : React.JSX.Element {
|
||||
const { apiClient, library } = useApiClientContext();
|
||||
const { data: tracks, refetch, isPending } = useFavoriteTracks(apiClient!, library!.musicLibraryId);
|
||||
const { data: tracks, refetch, isPending } = useFavoriteTracks();
|
||||
|
||||
const { width } = useSafeAreaFrame();
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import _ from "lodash";
|
||||
import { JellyfinApiClientProvider, useApiClientContext } from "./jellyfin-api-provider";
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { NavigationContainer } from "@react-navigation/native";
|
||||
import Navigation from "./navigation";
|
||||
import Login from "./Login/component";
|
||||
@@ -10,29 +9,31 @@ import { JellifyDarkTheme, JellifyLightTheme } from "./theme";
|
||||
import { PlayerProvider } from "../player/provider";
|
||||
import { useColorScheme } from "react-native";
|
||||
import { PortalProvider } from "tamagui";
|
||||
import Client from "@/api/client";
|
||||
|
||||
export default function Jellify(): React.JSX.Element {
|
||||
|
||||
return (
|
||||
<JellyfinApiClientProvider>
|
||||
<PortalProvider shouldAddRootHost>
|
||||
<App />
|
||||
</PortalProvider>
|
||||
</JellyfinApiClientProvider>
|
||||
<PortalProvider shouldAddRootHost>
|
||||
<App />
|
||||
</PortalProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function App(): React.JSX.Element {
|
||||
|
||||
// If library hasn't been set, we haven't completed the auth flow
|
||||
const { server, library } = useApiClientContext();
|
||||
|
||||
const isDarkMode = useColorScheme() === "dark";
|
||||
|
||||
useEffect(() => {
|
||||
console.debug("Client instance changed")
|
||||
}, [
|
||||
Client.instance
|
||||
])
|
||||
|
||||
return (
|
||||
<NavigationContainer theme={isDarkMode ? JellifyDarkTheme : JellifyLightTheme}>
|
||||
<SafeAreaProvider>
|
||||
{ server && library ? (
|
||||
{ Client.user && Client.user ? (
|
||||
<PlayerProvider>
|
||||
<Navigation />
|
||||
</PlayerProvider>
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
import { Api } from '@jellyfin/sdk';
|
||||
import React, { createContext, ReactNode, SetStateAction, useContext, useEffect, useState } from 'react';
|
||||
import { useApi } from '../api/queries';
|
||||
import { isUndefined } from 'lodash';
|
||||
import { storage } from '../constants/storage';
|
||||
import { MMKVStorageKeys } from '../enums/mmkv-storage-keys';
|
||||
import { JellifyServer } from '../types/JellifyServer';
|
||||
import { JellifyLibrary } from '../types/JellifyLibrary';
|
||||
import { JellifyUser } from '../types/JellifyUser';
|
||||
import uuid from 'react-native-uuid';
|
||||
import Client from '@/api/client';
|
||||
|
||||
interface JellyfinApiClientContext {
|
||||
apiClient: Api | undefined;
|
||||
sessionId: string;
|
||||
server: JellifyServer | undefined;
|
||||
setServer: React.Dispatch<SetStateAction<JellifyServer | undefined>>;
|
||||
user: JellifyUser | undefined;
|
||||
setUser: React.Dispatch<SetStateAction<JellifyUser | undefined>>;
|
||||
library: JellifyLibrary | undefined;
|
||||
setLibrary: React.Dispatch<SetStateAction<JellifyLibrary | undefined>>;
|
||||
signOut: () => void
|
||||
}
|
||||
|
||||
const JellyfinApiClientContextInitializer = () => {
|
||||
|
||||
const [apiClient, setApiClient] = useState<Api | undefined>(Client.instance.api);
|
||||
const [sessionId, setSessionId] = useState<string>(Client.instance.sessionId);
|
||||
const [user, setUser] = useState<JellifyUser | undefined>(Client.instance.user);
|
||||
const [server, setServer] = useState<JellifyServer | undefined>(Client.instance.server);
|
||||
const [library, setLibrary] = useState<JellifyLibrary | undefined>(Client.instance.library);
|
||||
|
||||
const signOut = () => {
|
||||
console.debug("Signing out of Jellify");
|
||||
|
||||
Client.signOut();
|
||||
|
||||
setUser(undefined);
|
||||
setServer(undefined);
|
||||
setLibrary(undefined);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (server && user)
|
||||
Client.setPrivateApiClient(server, user)
|
||||
else if (server)
|
||||
Client.setPublicApiClient(server)
|
||||
else
|
||||
Client.signOut();
|
||||
}, [
|
||||
server,
|
||||
user
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (server) {
|
||||
console.debug("Storing new server configuration")
|
||||
storage.set(MMKVStorageKeys.Server, JSON.stringify(server))
|
||||
}
|
||||
else {
|
||||
console.debug("Deleting server configuration from storage");
|
||||
storage.delete(MMKVStorageKeys.Server)
|
||||
}
|
||||
}, [
|
||||
server
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
console.debug("Storing new user profile")
|
||||
storage.set(MMKVStorageKeys.User, JSON.stringify(user));
|
||||
}
|
||||
else {
|
||||
console.debug("Deleting access token from storage");
|
||||
storage.delete(MMKVStorageKeys.User);
|
||||
}
|
||||
}, [
|
||||
user
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
console.debug("Library changed")
|
||||
if (library) {
|
||||
console.debug("Setting library");
|
||||
storage.set(MMKVStorageKeys.Library, JSON.stringify(library));
|
||||
} else
|
||||
storage.delete(MMKVStorageKeys.Library)
|
||||
}, [
|
||||
library
|
||||
])
|
||||
|
||||
return {
|
||||
apiClient,
|
||||
sessionId,
|
||||
server,
|
||||
setServer,
|
||||
user,
|
||||
setUser,
|
||||
library,
|
||||
setLibrary,
|
||||
signOut
|
||||
};
|
||||
}
|
||||
|
||||
export const JellyfinApiClientContext =
|
||||
createContext<JellyfinApiClientContext>({
|
||||
apiClient: undefined,
|
||||
sessionId: "",
|
||||
server: undefined,
|
||||
setServer: () => {},
|
||||
user: undefined,
|
||||
setUser: () => {},
|
||||
library: undefined,
|
||||
setLibrary: () => {},
|
||||
signOut: () => {}
|
||||
});
|
||||
|
||||
export const JellyfinApiClientProvider: ({ children }: {
|
||||
children: ReactNode;
|
||||
}) => React.JSX.Element = ({ children }: { children: ReactNode }) => {
|
||||
const {
|
||||
apiClient,
|
||||
sessionId,
|
||||
server,
|
||||
setServer,
|
||||
user,
|
||||
setUser,
|
||||
library,
|
||||
setLibrary,
|
||||
signOut
|
||||
} = JellyfinApiClientContextInitializer();
|
||||
|
||||
// Add your logic to check if credentials are stored and initialize the API client here.
|
||||
|
||||
return (
|
||||
<JellyfinApiClientContext.Provider value={{
|
||||
apiClient,
|
||||
sessionId,
|
||||
server,
|
||||
setServer,
|
||||
user,
|
||||
setUser,
|
||||
library,
|
||||
setLibrary,
|
||||
signOut
|
||||
}}>
|
||||
{children}
|
||||
</JellyfinApiClientContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useApiClientContext = () => useContext(JellyfinApiClientContext)
|
||||
@@ -17,6 +17,7 @@ import { QueuingType } from "@/enums/queuing-type";
|
||||
import { trigger } from "react-native-haptic-feedback";
|
||||
import { getQueue, pause, seekTo, skip, skipToNext, skipToPrevious } from "react-native-track-player/lib/src/trackPlayer";
|
||||
import { convertRunTimeTicksToSeconds } from "@/helpers/runtimeticks";
|
||||
import Client from "@/api/client";
|
||||
|
||||
interface PlayerContext {
|
||||
showPlayer: boolean;
|
||||
@@ -41,8 +42,7 @@ const PlayerContextInitializer = () => {
|
||||
|
||||
const queueJson = storage.getString(MMKVStorageKeys.PlayQueue);
|
||||
|
||||
const { apiClient, sessionId } = useApiClientContext();
|
||||
const playStateApi = getPlaystateApi(apiClient!)
|
||||
const playStateApi = getPlaystateApi(Client.api!)
|
||||
|
||||
//#region State
|
||||
const [showPlayer, setShowPlayer] = useState<boolean>(false);
|
||||
@@ -147,11 +147,11 @@ const PlayerContextInitializer = () => {
|
||||
setIsSkipping(true);
|
||||
|
||||
// Optimistically set now playing
|
||||
setNowPlaying(mapDtoToTrack(apiClient!, sessionId, mutation.tracklist[mutation.index ?? 0], QueuingType.FromSelection));
|
||||
setNowPlaying(mapDtoToTrack(Client.api!, sessionId, mutation.tracklist[mutation.index ?? 0], QueuingType.FromSelection));
|
||||
|
||||
await resetQueue(false);
|
||||
await addToQueue(mutation.tracklist.map((track) => {
|
||||
return mapDtoToTrack(apiClient!, sessionId, track, QueuingType.FromSelection)
|
||||
return mapDtoToTrack(Co!, sessionId, track, QueuingType.FromSelection)
|
||||
}));
|
||||
|
||||
setQueueName(mutation.queueName);
|
||||
|
||||
Reference in New Issue
Block a user