mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-04-21 16:24:41 +01:00
chore: Apply linting rules and add git hok (#611)
Co-authored-by: Fredrik Burmester <fredrik.burmester@gmail.com>
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||
import { getTvShowsApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useMemo } from "react";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface AdjacentEpisodesProps {
|
||||
item?: BaseItemDto | null;
|
||||
|
||||
@@ -5,11 +5,11 @@ import {
|
||||
useSharedValue,
|
||||
} from "react-native-reanimated";
|
||||
|
||||
export const useControlsVisibility = (timeout: number = 3000) => {
|
||||
export const useControlsVisibility = (timeout = 3000) => {
|
||||
const opacity = useSharedValue(1);
|
||||
|
||||
const hideControlsTimerRef = useRef<ReturnType<typeof setTimeout> | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
|
||||
const showControls = useCallback(() => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAtom } from "jotai";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { getAuthHeaders } from "@/utils/jellyfin/jellyfin";
|
||||
import { writeToLog } from "@/utils/log";
|
||||
import { msToSeconds, secondsToMs } from "@/utils/time";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAtom } from "jotai";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useHaptic } from "./useHaptic";
|
||||
|
||||
interface CreditTimestamps {
|
||||
@@ -25,7 +25,7 @@ export const useCreditSkipper = (
|
||||
currentTime: number,
|
||||
seek: (time: number) => void,
|
||||
play: () => void,
|
||||
isVlc: boolean = false
|
||||
isVlc = false,
|
||||
) => {
|
||||
const [api] = useAtom(apiAtom);
|
||||
const [showSkipCreditButton, setShowSkipCreditButton] = useState(false);
|
||||
@@ -54,7 +54,7 @@ export const useCreditSkipper = (
|
||||
`${api.basePath}/Episode/${itemId}/Timestamps`,
|
||||
{
|
||||
headers: getAuthHeaders(api),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (res?.status !== 200) {
|
||||
@@ -71,7 +71,7 @@ export const useCreditSkipper = (
|
||||
if (creditTimestamps) {
|
||||
setShowSkipCreditButton(
|
||||
currentTime > creditTimestamps.Credits.Start &&
|
||||
currentTime < creditTimestamps.Credits.End
|
||||
currentTime < creditTimestamps.Credits.End,
|
||||
);
|
||||
}
|
||||
}, [creditTimestamps, currentTime]);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Bitrate, BITRATES } from "@/components/BitrateSelector";
|
||||
import { Settings } from "@/utils/atoms/settings";
|
||||
import { BITRATES, Bitrate } from "@/components/BitrateSelector";
|
||||
import type { Settings } from "@/utils/atoms/settings";
|
||||
import {
|
||||
BaseItemDto,
|
||||
type BaseItemDto,
|
||||
MediaSourceInfo,
|
||||
} from "@jellyfin/sdk/lib/generated-client";
|
||||
import { useMemo } from "react";
|
||||
@@ -9,7 +9,7 @@ import { useMemo } from "react";
|
||||
// Used only for initial play settings.
|
||||
const useDefaultPlaySettings = (
|
||||
item: BaseItemDto,
|
||||
settings: Settings | null
|
||||
settings: Settings | null,
|
||||
) => {
|
||||
const playSettings = useMemo(() => {
|
||||
// 1. Get first media source
|
||||
@@ -21,11 +21,11 @@ const useDefaultPlaySettings = (
|
||||
(x) =>
|
||||
x.Type === "Audio" &&
|
||||
x.Language ===
|
||||
settings?.defaultAudioLanguage?.ThreeLetterISOLanguageName
|
||||
settings?.defaultAudioLanguage?.ThreeLetterISOLanguageName,
|
||||
)?.Index;
|
||||
|
||||
const firstAudioIndex = mediaSource?.MediaStreams?.find(
|
||||
(x) => x.Type === "Audio"
|
||||
(x) => x.Type === "Audio",
|
||||
)?.Index;
|
||||
|
||||
// 4. Get default bitrate from settings or fallback to max
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { usePlaySettings } from "@/providers/PlaySettingsProvider";
|
||||
import { writeToLog } from "@/utils/log";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||
import * as FileSystem from "expo-file-system";
|
||||
import { useRouter } from "expo-router";
|
||||
import { useCallback } from "react";
|
||||
@@ -41,7 +41,7 @@ export const useDownloadedFileOpener = () => {
|
||||
console.error("Error opening file:", error);
|
||||
}
|
||||
},
|
||||
[setOfflineSettings, setPlayUrl, router]
|
||||
[setOfflineSettings, setPlayUrl, router],
|
||||
);
|
||||
|
||||
return { openFile };
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||
import { getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useAtom } from "jotai";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
export const useFavorite = (item: BaseItemDto) => {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -26,7 +26,7 @@ export const useFavorite = (item: BaseItemDto) => {
|
||||
...newData,
|
||||
UserData: { ...old.UserData, ...newData.UserData },
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
const Haptics = !Platform.isTV ? require("expo-haptics") : null;
|
||||
|
||||
export type HapticFeedbackType =
|
||||
@@ -25,7 +25,7 @@ export const useHaptic = (feedbackType: HapticFeedbackType = "selection") => {
|
||||
? () => {}
|
||||
: () => Haptics.impactAsync(type);
|
||||
},
|
||||
[]
|
||||
[],
|
||||
);
|
||||
const createNotificationFeedback = useCallback(
|
||||
(type: typeof Haptics.NotificationFeedbackType) => {
|
||||
@@ -33,7 +33,7 @@ export const useHaptic = (feedbackType: HapticFeedbackType = "selection") => {
|
||||
? () => {}
|
||||
: () => Haptics.notificationAsync(type);
|
||||
},
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
const hapticHandlers = useMemo(
|
||||
@@ -46,14 +46,14 @@ export const useHaptic = (feedbackType: HapticFeedbackType = "selection") => {
|
||||
? () => {}
|
||||
: Haptics.selectionAsync,
|
||||
success: createNotificationFeedback(
|
||||
Haptics.NotificationFeedbackType.Success
|
||||
Haptics.NotificationFeedbackType.Success,
|
||||
),
|
||||
warning: createNotificationFeedback(
|
||||
Haptics.NotificationFeedbackType.Warning
|
||||
Haptics.NotificationFeedbackType.Warning,
|
||||
),
|
||||
error: createNotificationFeedback(Haptics.NotificationFeedbackType.Error),
|
||||
}),
|
||||
[createHapticHandler, createNotificationFeedback]
|
||||
[createHapticHandler, createNotificationFeedback],
|
||||
);
|
||||
|
||||
if (settings?.disableHapticFeedback) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "@/utils/atoms/primaryColor";
|
||||
import { getItemImage } from "@/utils/getItemImage";
|
||||
import { storage } from "@/utils/mmkv";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Platform } from "react-native";
|
||||
@@ -70,40 +70,48 @@ export const useImageColors = ({
|
||||
fallback: "#fff",
|
||||
cache: false,
|
||||
})
|
||||
.then((colors: { platform: string; dominant: string; vibrant: string; detail: string; primary: string; }) => {
|
||||
let primary: string = "#fff";
|
||||
let text: string = "#000";
|
||||
let backup: string = "#fff";
|
||||
.then(
|
||||
(colors: {
|
||||
platform: string;
|
||||
dominant: string;
|
||||
vibrant: string;
|
||||
detail: string;
|
||||
primary: string;
|
||||
}) => {
|
||||
let primary = "#fff";
|
||||
let text = "#000";
|
||||
let backup = "#fff";
|
||||
|
||||
// Select the appropriate color based on the platform
|
||||
if (colors.platform === "android") {
|
||||
primary = colors.dominant;
|
||||
backup = colors.vibrant;
|
||||
} else if (colors.platform === "ios") {
|
||||
primary = colors.detail;
|
||||
backup = colors.primary;
|
||||
}
|
||||
// Select the appropriate color based on the platform
|
||||
if (colors.platform === "android") {
|
||||
primary = colors.dominant;
|
||||
backup = colors.vibrant;
|
||||
} else if (colors.platform === "ios") {
|
||||
primary = colors.detail;
|
||||
backup = colors.primary;
|
||||
}
|
||||
|
||||
// Adjust the primary color if it's too close to black
|
||||
if (primary && isCloseToBlack(primary)) {
|
||||
if (backup && !isCloseToBlack(backup)) primary = backup;
|
||||
primary = adjustToNearBlack(primary);
|
||||
}
|
||||
// Adjust the primary color if it's too close to black
|
||||
if (primary && isCloseToBlack(primary)) {
|
||||
if (backup && !isCloseToBlack(backup)) primary = backup;
|
||||
primary = adjustToNearBlack(primary);
|
||||
}
|
||||
|
||||
// Calculate the text color based on the primary color
|
||||
if (primary) text = calculateTextColor(primary);
|
||||
// Calculate the text color based on the primary color
|
||||
if (primary) text = calculateTextColor(primary);
|
||||
|
||||
setPrimaryColor({
|
||||
primary,
|
||||
text,
|
||||
});
|
||||
setPrimaryColor({
|
||||
primary,
|
||||
text,
|
||||
});
|
||||
|
||||
// Cache the colors in storage
|
||||
if (source.uri && primary) {
|
||||
storage.set(`${source.uri}-primary`, primary);
|
||||
storage.set(`${source.uri}-text`, text);
|
||||
}
|
||||
})
|
||||
// Cache the colors in storage
|
||||
if (source.uri && primary) {
|
||||
storage.set(`${source.uri}-primary`, primary);
|
||||
storage.set(`${source.uri}-text`, text);
|
||||
}
|
||||
},
|
||||
)
|
||||
.catch((error: any) => {
|
||||
console.error("Error getting colors", error);
|
||||
});
|
||||
|
||||
@@ -62,7 +62,7 @@ const useImageStorage = () => {
|
||||
console.warn("Error saving image:", error);
|
||||
}
|
||||
},
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
const loadImage = useCallback(async (key: string) => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAtom } from "jotai";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { getAuthHeaders } from "@/utils/jellyfin/jellyfin";
|
||||
import { writeToLog } from "@/utils/log";
|
||||
import { msToSeconds, secondsToMs } from "@/utils/time";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAtom } from "jotai";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useHaptic } from "./useHaptic";
|
||||
|
||||
interface IntroTimestamps {
|
||||
@@ -26,7 +26,7 @@ export const useIntroSkipper = (
|
||||
currentTime: number,
|
||||
seek: (ticks: number) => void,
|
||||
play: () => void,
|
||||
isVlc: boolean = false
|
||||
isVlc = false,
|
||||
) => {
|
||||
const [api] = useAtom(apiAtom);
|
||||
const [showSkipButton, setShowSkipButton] = useState(false);
|
||||
@@ -54,7 +54,7 @@ export const useIntroSkipper = (
|
||||
`${api.basePath}/Episode/${itemId}/IntroTimestamps`,
|
||||
{
|
||||
headers: getAuthHeaders(api),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (res?.status !== 200) {
|
||||
@@ -71,7 +71,7 @@ export const useIntroSkipper = (
|
||||
if (introTimestamps) {
|
||||
setShowSkipButton(
|
||||
currentTime > introTimestamps.ShowSkipPromptAt &&
|
||||
currentTime < introTimestamps.HideSkipPromptAt
|
||||
currentTime < introTimestamps.HideSkipPromptAt,
|
||||
);
|
||||
}
|
||||
}, [introTimestamps, currentTime]);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import dgram from "react-native-udp";
|
||||
|
||||
const JELLYFIN_DISCOVERY_PORT = 7359;
|
||||
@@ -53,7 +53,7 @@ export const useJellyfinDiscovery = () => {
|
||||
return;
|
||||
}
|
||||
console.log("Discovery message sent successfully");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
discoveryTimeout = setTimeout(() => {
|
||||
|
||||
@@ -1,45 +1,52 @@
|
||||
import axios, { AxiosError, AxiosInstance } from "axios";
|
||||
import {MovieResult, Results, TvResult} from "@/utils/jellyseerr/server/models/Search";
|
||||
import type { User as JellyseerrUser } from "@/utils/jellyseerr/server/entity/User";
|
||||
import type {
|
||||
MovieResult,
|
||||
Results,
|
||||
TvResult,
|
||||
} from "@/utils/jellyseerr/server/models/Search";
|
||||
import { storage } from "@/utils/mmkv";
|
||||
import { inRange } from "lodash";
|
||||
import { User as JellyseerrUser } from "@/utils/jellyseerr/server/entity/User";
|
||||
import axios, { type AxiosError, type AxiosInstance } from "axios";
|
||||
import { atom } from "jotai";
|
||||
import { useAtom } from "jotai/index";
|
||||
import { inRange } from "lodash";
|
||||
import "@/augmentations";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import { toast } from "sonner-native";
|
||||
import type { RTRating } from "@/utils/jellyseerr/server/api/rating/rottentomatoes";
|
||||
import {
|
||||
IssueStatus,
|
||||
type IssueType,
|
||||
} from "@/utils/jellyseerr/server/constants/issue";
|
||||
import {
|
||||
MediaRequestStatus,
|
||||
MediaType,
|
||||
} from "@/utils/jellyseerr/server/constants/media";
|
||||
import MediaRequest from "@/utils/jellyseerr/server/entity/MediaRequest";
|
||||
import {MediaRequestBody, RequestResultsResponse} from "@/utils/jellyseerr/server/interfaces/api/requestInterfaces";
|
||||
import { MovieDetails } from "@/utils/jellyseerr/server/models/Movie";
|
||||
import {
|
||||
SeasonWithEpisodes,
|
||||
TvDetails,
|
||||
} from "@/utils/jellyseerr/server/models/Tv";
|
||||
import {
|
||||
IssueStatus,
|
||||
IssueType,
|
||||
} from "@/utils/jellyseerr/server/constants/issue";
|
||||
import Issue from "@/utils/jellyseerr/server/entity/Issue";
|
||||
import { RTRating } from "@/utils/jellyseerr/server/api/rating/rottentomatoes";
|
||||
import { writeErrorLog } from "@/utils/log";
|
||||
import DiscoverSlider from "@/utils/jellyseerr/server/entity/DiscoverSlider";
|
||||
import { t } from "i18next";
|
||||
import {
|
||||
import type DiscoverSlider from "@/utils/jellyseerr/server/entity/DiscoverSlider";
|
||||
import type Issue from "@/utils/jellyseerr/server/entity/Issue";
|
||||
import type MediaRequest from "@/utils/jellyseerr/server/entity/MediaRequest";
|
||||
import type { GenreSliderItem } from "@/utils/jellyseerr/server/interfaces/api/discoverInterfaces";
|
||||
import type {
|
||||
MediaRequestBody,
|
||||
RequestResultsResponse,
|
||||
} from "@/utils/jellyseerr/server/interfaces/api/requestInterfaces";
|
||||
import type {
|
||||
ServiceCommonServer,
|
||||
ServiceCommonServerWithDetails,
|
||||
} from "@/utils/jellyseerr/server/interfaces/api/serviceInterfaces";
|
||||
import type { UserResultsResponse } from "@/utils/jellyseerr/server/interfaces/api/userInterfaces";
|
||||
import type { MovieDetails } from "@/utils/jellyseerr/server/models/Movie";
|
||||
import type {
|
||||
CombinedCredit,
|
||||
PersonDetails,
|
||||
} from "@/utils/jellyseerr/server/models/Person";
|
||||
import type {
|
||||
SeasonWithEpisodes,
|
||||
TvDetails,
|
||||
} from "@/utils/jellyseerr/server/models/Tv";
|
||||
import { writeErrorLog } from "@/utils/log";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import {GenreSliderItem} from "@/utils/jellyseerr/server/interfaces/api/discoverInterfaces";
|
||||
import {UserResultsResponse} from "@/utils/jellyseerr/server/interfaces/api/userInterfaces";
|
||||
import {
|
||||
ServiceCommonServer,
|
||||
ServiceCommonServerWithDetails
|
||||
} from "@/utils/jellyseerr/server/interfaces/api/serviceInterfaces";
|
||||
import { t } from "i18next";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { toast } from "sonner-native";
|
||||
|
||||
interface SearchParams {
|
||||
query: string;
|
||||
@@ -134,15 +141,16 @@ export class JellyseerrApi {
|
||||
const { status, headers, data } = response;
|
||||
if (inRange(status, 200, 299)) {
|
||||
if (data.version < "2.0.0") {
|
||||
const error =
|
||||
t("jellyseerr.toasts.jellyseer_does_not_meet_requirements");
|
||||
const error = t(
|
||||
"jellyseerr.toasts.jellyseer_does_not_meet_requirements",
|
||||
);
|
||||
toast.error(error);
|
||||
throw Error(error);
|
||||
}
|
||||
|
||||
storage.setAny(
|
||||
JELLYSEERR_COOKIES,
|
||||
headers["set-cookie"]?.flatMap((c) => c.split("; ")) ?? []
|
||||
headers["set-cookie"]?.flatMap((c) => c.split("; ")) ?? [],
|
||||
);
|
||||
return {
|
||||
isValid: true,
|
||||
@@ -154,7 +162,7 @@ export class JellyseerrApi {
|
||||
`Jellyseerr returned a ${status} for url:\n` +
|
||||
response.config.url +
|
||||
"\n" +
|
||||
JSON.stringify(response.data)
|
||||
JSON.stringify(response.data),
|
||||
);
|
||||
return {
|
||||
isValid: false,
|
||||
@@ -190,14 +198,14 @@ export class JellyseerrApi {
|
||||
async discoverSettings(): Promise<DiscoverSlider[]> {
|
||||
return this.axios
|
||||
?.get<DiscoverSlider[]>(
|
||||
Endpoints.API_V1 + Endpoints.SETTINGS + Endpoints.DISCOVER
|
||||
Endpoints.API_V1 + Endpoints.SETTINGS + Endpoints.DISCOVER,
|
||||
)
|
||||
.then(({ data }) => data);
|
||||
}
|
||||
|
||||
async discover(
|
||||
endpoint: DiscoverEndpoint | string,
|
||||
params: any
|
||||
params: any,
|
||||
): Promise<SearchResults> {
|
||||
return this.axios
|
||||
?.get<SearchResults>(Endpoints.API_V1 + endpoint, { params })
|
||||
@@ -206,18 +214,23 @@ export class JellyseerrApi {
|
||||
|
||||
async getGenreSliders(
|
||||
endpoint: Endpoints.TV | Endpoints.MOVIE,
|
||||
params: any = undefined
|
||||
params: any = undefined,
|
||||
): Promise<GenreSliderItem[]> {
|
||||
return this.axios
|
||||
?.get<GenreSliderItem[]>(Endpoints.API_V1 + Endpoints.DISCOVER + Endpoints.GENRE_SLIDER + endpoint, { params })
|
||||
?.get<GenreSliderItem[]>(
|
||||
Endpoints.API_V1 +
|
||||
Endpoints.DISCOVER +
|
||||
Endpoints.GENRE_SLIDER +
|
||||
endpoint,
|
||||
{ params },
|
||||
)
|
||||
.then(({ data }) => data);
|
||||
}
|
||||
|
||||
async search(params: SearchParams): Promise<SearchResults> {
|
||||
return this.axios?.get<SearchResults>(
|
||||
Endpoints.API_V1 + Endpoints.SEARCH,
|
||||
{ params }
|
||||
).then(({ data }) => data)
|
||||
return this.axios
|
||||
?.get<SearchResults>(Endpoints.API_V1 + Endpoints.SEARCH, { params })
|
||||
.then(({ data }) => data);
|
||||
}
|
||||
|
||||
async request(request: MediaRequestBody): Promise<MediaRequest> {
|
||||
@@ -232,15 +245,19 @@ export class JellyseerrApi {
|
||||
.then(({ data }) => data);
|
||||
}
|
||||
|
||||
async requests(params = {
|
||||
filter: "all",
|
||||
take: 10,
|
||||
sort: "modified",
|
||||
skip: 0
|
||||
}): Promise<RequestResultsResponse> {
|
||||
async requests(
|
||||
params = {
|
||||
filter: "all",
|
||||
take: 10,
|
||||
sort: "modified",
|
||||
skip: 0,
|
||||
},
|
||||
): Promise<RequestResultsResponse> {
|
||||
return this.axios
|
||||
?.get<RequestResultsResponse>(Endpoints.API_V1 + Endpoints.REQUEST, {params})
|
||||
.then(({data}) => data);
|
||||
?.get<RequestResultsResponse>(Endpoints.API_V1 + Endpoints.REQUEST, {
|
||||
params,
|
||||
})
|
||||
.then(({ data }) => data);
|
||||
}
|
||||
|
||||
async movieDetails(id: number) {
|
||||
@@ -265,7 +282,7 @@ export class JellyseerrApi {
|
||||
Endpoints.API_V1 +
|
||||
Endpoints.PERSON +
|
||||
`/${id}` +
|
||||
Endpoints.COMBINED_CREDITS
|
||||
Endpoints.COMBINED_CREDITS,
|
||||
)
|
||||
.then((response) => {
|
||||
return response?.data;
|
||||
@@ -275,7 +292,7 @@ export class JellyseerrApi {
|
||||
async movieRatings(id: number) {
|
||||
return this.axios
|
||||
?.get<RTRating>(
|
||||
`${Endpoints.API_V1}${Endpoints.MOVIE}/${id}${Endpoints.RATINGS}`
|
||||
`${Endpoints.API_V1}${Endpoints.MOVIE}/${id}${Endpoints.RATINGS}`,
|
||||
)
|
||||
.then(({ data }) => data);
|
||||
}
|
||||
@@ -291,7 +308,7 @@ export class JellyseerrApi {
|
||||
async tvRatings(id: number) {
|
||||
return this.axios
|
||||
?.get<RTRating>(
|
||||
`${Endpoints.API_V1}${Endpoints.TV}/${id}${Endpoints.RATINGS}`
|
||||
`${Endpoints.API_V1}${Endpoints.TV}/${id}${Endpoints.RATINGS}`,
|
||||
)
|
||||
.then(({ data }) => data);
|
||||
}
|
||||
@@ -299,7 +316,7 @@ export class JellyseerrApi {
|
||||
async tvSeason(id: number, seasonId: number) {
|
||||
return this.axios
|
||||
?.get<SeasonWithEpisodes>(
|
||||
`${Endpoints.API_V1}${Endpoints.TV}/${id}/season/${seasonId}`
|
||||
`${Endpoints.API_V1}${Endpoints.TV}/${id}/season/${seasonId}`,
|
||||
)
|
||||
.then((response) => {
|
||||
return response?.data;
|
||||
@@ -308,21 +325,18 @@ export class JellyseerrApi {
|
||||
|
||||
async user(params: any) {
|
||||
return this.axios
|
||||
?.get<UserResultsResponse>(`${Endpoints.API_V1}${Endpoints.USER}`, { params })
|
||||
.then(({data}) => data.results)
|
||||
?.get<UserResultsResponse>(`${Endpoints.API_V1}${Endpoints.USER}`, {
|
||||
params,
|
||||
})
|
||||
.then(({ data }) => data.results);
|
||||
}
|
||||
|
||||
imageProxy(
|
||||
path?: string,
|
||||
filter: string = "original",
|
||||
width: number = 1920,
|
||||
quality: number = 75
|
||||
) {
|
||||
imageProxy(path?: string, filter = "original", width = 1920, quality = 75) {
|
||||
return path
|
||||
? this.axios.defaults.baseURL +
|
||||
`/_next/image?` +
|
||||
new URLSearchParams(
|
||||
`url=https://image.tmdb.org/t/p/${filter}/${path}&w=${width}&q=${quality}`
|
||||
`url=https://image.tmdb.org/t/p/${filter}/${path}&w=${width}&q=${quality}`,
|
||||
).toString()
|
||||
: this.axios?.defaults.baseURL +
|
||||
`/images/overseerr_poster_not_found_logo_top.png`;
|
||||
@@ -345,16 +359,20 @@ export class JellyseerrApi {
|
||||
});
|
||||
}
|
||||
|
||||
async service(type: 'radarr' | 'sonarr') {
|
||||
async service(type: "radarr" | "sonarr") {
|
||||
return this.axios
|
||||
?.get<ServiceCommonServer[]>(Endpoints.API_V1 + Endpoints.SERVICE + `/${type}`)
|
||||
.then(({data}) => data);
|
||||
?.get<ServiceCommonServer[]>(
|
||||
Endpoints.API_V1 + Endpoints.SERVICE + `/${type}`,
|
||||
)
|
||||
.then(({ data }) => data);
|
||||
}
|
||||
|
||||
async serviceDetails(type: 'radarr' | 'sonarr', id: number) {
|
||||
async serviceDetails(type: "radarr" | "sonarr", id: number) {
|
||||
return this.axios
|
||||
?.get<ServiceCommonServerWithDetails>(Endpoints.API_V1 + Endpoints.SERVICE + `/${type}` + `/${id}`)
|
||||
.then(({data}) => data);
|
||||
?.get<ServiceCommonServerWithDetails>(
|
||||
Endpoints.API_V1 + Endpoints.SERVICE + `/${type}` + `/${id}`,
|
||||
)
|
||||
.then(({ data }) => data);
|
||||
}
|
||||
|
||||
private setInterceptors() {
|
||||
@@ -364,7 +382,7 @@ export class JellyseerrApi {
|
||||
if (cookies) {
|
||||
storage.setAny(
|
||||
JELLYSEERR_COOKIES,
|
||||
response.headers["set-cookie"]?.flatMap((c) => c.split("; "))
|
||||
response.headers["set-cookie"]?.flatMap((c) => c.split("; ")),
|
||||
);
|
||||
}
|
||||
return response;
|
||||
@@ -378,20 +396,20 @@ export class JellyseerrApi {
|
||||
`error: ${error.toString()}\n` +
|
||||
`url: ${error?.config?.url}\n` +
|
||||
`data:\n` +
|
||||
JSON.stringify(error.response?.data)
|
||||
JSON.stringify(error.response?.data),
|
||||
);
|
||||
if (error.status === 403) {
|
||||
clearJellyseerrStorageData();
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.axios.interceptors.request.use(
|
||||
async (config) => {
|
||||
const cookies = storage.get<string[]>(JELLYSEERR_COOKIES);
|
||||
if (cookies) {
|
||||
const headerName = this.axios.defaults.xsrfHeaderName!!;
|
||||
const headerName = this.axios.defaults.xsrfHeaderName!;
|
||||
const xsrfToken = cookies
|
||||
.find((c) => c.includes(headerName))
|
||||
?.split(headerName + "=")?.[1];
|
||||
@@ -403,7 +421,7 @@ export class JellyseerrApi {
|
||||
},
|
||||
(error) => {
|
||||
console.error("Jellyseerr request error", error);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -439,55 +457,74 @@ export const useJellyseerr = () => {
|
||||
switch (mediaRequest.status) {
|
||||
case MediaRequestStatus.PENDING:
|
||||
case MediaRequestStatus.APPROVED:
|
||||
toast.success(t("jellyseerr.toasts.requested_item", {item: title}));
|
||||
onSuccess?.()
|
||||
toast.success(
|
||||
t("jellyseerr.toasts.requested_item", { item: title }),
|
||||
);
|
||||
onSuccess?.();
|
||||
break;
|
||||
case MediaRequestStatus.DECLINED:
|
||||
toast.error(t("jellyseerr.toasts.you_dont_have_permission_to_request"));
|
||||
toast.error(
|
||||
t("jellyseerr.toasts.you_dont_have_permission_to_request"),
|
||||
);
|
||||
break;
|
||||
case MediaRequestStatus.FAILED:
|
||||
toast.error(t("jellyseerr.toasts.something_went_wrong_requesting_media"));
|
||||
toast.error(
|
||||
t("jellyseerr.toasts.something_went_wrong_requesting_media"),
|
||||
);
|
||||
break;
|
||||
}
|
||||
});
|
||||
},
|
||||
[jellyseerrApi]
|
||||
[jellyseerrApi],
|
||||
);
|
||||
|
||||
const isJellyseerrResult = (
|
||||
items: any | null | undefined
|
||||
items: any | null | undefined,
|
||||
): items is Results => {
|
||||
return (
|
||||
items &&
|
||||
Object.hasOwn(items, "mediaType") &&
|
||||
Object.values(MediaType).includes(items["mediaType"])
|
||||
)
|
||||
Object.hasOwn(items, "mediaType") &&
|
||||
Object.values(MediaType).includes(items["mediaType"])
|
||||
);
|
||||
};
|
||||
|
||||
const getTitle = (item?: TvResult | TvDetails | MovieResult | MovieDetails) => {
|
||||
const getTitle = (
|
||||
item?: TvResult | TvDetails | MovieResult | MovieDetails,
|
||||
) => {
|
||||
return isJellyseerrResult(item)
|
||||
? (item.mediaType == MediaType.MOVIE ? item?.title : item?.name)
|
||||
: (item?.mediaInfo.mediaType == MediaType.MOVIE ? (item as MovieDetails)?.title : (item as TvDetails)?.name)
|
||||
? item.mediaType == MediaType.MOVIE
|
||||
? item?.title
|
||||
: item?.name
|
||||
: item?.mediaInfo.mediaType == MediaType.MOVIE
|
||||
? (item as MovieDetails)?.title
|
||||
: (item as TvDetails)?.name;
|
||||
};
|
||||
|
||||
const getYear = (item?: TvResult | TvDetails | MovieResult | MovieDetails) => {
|
||||
return new Date((
|
||||
isJellyseerrResult(item)
|
||||
? (item.mediaType == MediaType.MOVIE ? item?.releaseDate : item?.firstAirDate)
|
||||
: (item?.mediaInfo.mediaType == MediaType.MOVIE ? (item as MovieDetails)?.releaseDate : (item as TvDetails)?.firstAirDate))
|
||||
|| ""
|
||||
)?.getFullYear?.()
|
||||
const getYear = (
|
||||
item?: TvResult | TvDetails | MovieResult | MovieDetails,
|
||||
) => {
|
||||
return new Date(
|
||||
(isJellyseerrResult(item)
|
||||
? item.mediaType == MediaType.MOVIE
|
||||
? item?.releaseDate
|
||||
: item?.firstAirDate
|
||||
: item?.mediaInfo.mediaType == MediaType.MOVIE
|
||||
? (item as MovieDetails)?.releaseDate
|
||||
: (item as TvDetails)?.firstAirDate) || "",
|
||||
)?.getFullYear?.();
|
||||
};
|
||||
|
||||
const getMediaType = (item?: TvResult | TvDetails | MovieResult | MovieDetails): MediaType => {
|
||||
const getMediaType = (
|
||||
item?: TvResult | TvDetails | MovieResult | MovieDetails,
|
||||
): MediaType => {
|
||||
return isJellyseerrResult(item)
|
||||
? item.mediaType
|
||||
: item?.mediaInfo?.mediaType
|
||||
: item?.mediaInfo?.mediaType;
|
||||
};
|
||||
|
||||
const jellyseerrRegion = useMemo(
|
||||
() => jellyseerrUser?.settings?.region || "US",
|
||||
[jellyseerrUser]
|
||||
[jellyseerrUser],
|
||||
);
|
||||
|
||||
const jellyseerrLocale = useMemo(() => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { markAsNotPlayed } from "@/utils/jellyfin/playstate/markAsNotPlayed";
|
||||
import { markAsPlayed } from "@/utils/jellyfin/playstate/markAsPlayed";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useHaptic } from "./useHaptic";
|
||||
import { useAtom } from "jotai";
|
||||
import { useHaptic } from "./useHaptic";
|
||||
|
||||
export const useMarkAsPlayed = (items: BaseItemDto[]) => {
|
||||
const [api] = useAtom(apiAtom);
|
||||
@@ -24,10 +24,10 @@ export const useMarkAsPlayed = (items: BaseItemDto[]) => {
|
||||
];
|
||||
|
||||
items.forEach((item) => {
|
||||
if(!item.Id) return;
|
||||
if (!item.Id) return;
|
||||
queriesToInvalidate.push(["item", item.Id]);
|
||||
});
|
||||
|
||||
|
||||
queriesToInvalidate.forEach((queryKey) => {
|
||||
queryClient.invalidateQueries({ queryKey });
|
||||
});
|
||||
@@ -37,7 +37,7 @@ export const useMarkAsPlayed = (items: BaseItemDto[]) => {
|
||||
lightHapticFeedback();
|
||||
|
||||
items.forEach((item) => {
|
||||
// Optimistic update
|
||||
// Optimistic update
|
||||
queryClient.setQueryData(
|
||||
["item", item.Id],
|
||||
(oldData: BaseItemDto | undefined) => {
|
||||
@@ -51,17 +51,19 @@ export const useMarkAsPlayed = (items: BaseItemDto[]) => {
|
||||
};
|
||||
}
|
||||
return oldData;
|
||||
}
|
||||
},
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
try {
|
||||
// Process all items
|
||||
await Promise.all(items.map(item =>
|
||||
played
|
||||
? markAsPlayed({ api, item, userId: user?.Id })
|
||||
: markAsNotPlayed({ api, itemId: item?.Id, userId: user?.Id })
|
||||
));
|
||||
await Promise.all(
|
||||
items.map((item) =>
|
||||
played
|
||||
? markAsPlayed({ api, item, userId: user?.Id })
|
||||
: markAsNotPlayed({ api, itemId: item?.Id, userId: user?.Id }),
|
||||
),
|
||||
);
|
||||
|
||||
// Bulk invalidate
|
||||
queryClient.invalidateQueries({
|
||||
@@ -73,19 +75,21 @@ export const useMarkAsPlayed = (items: BaseItemDto[]) => {
|
||||
"episodes",
|
||||
"seasons",
|
||||
"home",
|
||||
...items.map(item => ["item", item.Id])
|
||||
].flat()
|
||||
...items.map((item) => ["item", item.Id]),
|
||||
].flat(),
|
||||
});
|
||||
} catch (error) {
|
||||
// Revert all optimistic updates on any failure
|
||||
items.forEach(item => {
|
||||
items.forEach((item) => {
|
||||
queryClient.setQueryData(
|
||||
["item", item.Id],
|
||||
(oldData: BaseItemDto | undefined) =>
|
||||
oldData ? {
|
||||
...oldData,
|
||||
UserData: { ...oldData.UserData, Played: played }
|
||||
} : oldData
|
||||
oldData
|
||||
? {
|
||||
...oldData,
|
||||
UserData: { ...oldData.UserData, Played: played },
|
||||
}
|
||||
: oldData,
|
||||
);
|
||||
});
|
||||
console.error("Error updating played status:", error);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import orientationToOrientationLock from "@/utils/OrientationLockConverter";
|
||||
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
|
||||
import orientationToOrientationLock from "@/utils/OrientationLockConverter";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
@@ -7,7 +7,7 @@ export const useOrientation = () => {
|
||||
const [orientation, setOrientation] = useState(
|
||||
Platform.isTV
|
||||
? ScreenOrientation.OrientationLock.LANDSCAPE
|
||||
: ScreenOrientation.OrientationLock.UNKNOWN
|
||||
: ScreenOrientation.OrientationLock.UNKNOWN,
|
||||
);
|
||||
|
||||
if (Platform.isTV) return { orientation, setOrientation };
|
||||
@@ -16,7 +16,7 @@ export const useOrientation = () => {
|
||||
const orientationSubscription =
|
||||
ScreenOrientation.addOrientationChangeListener((event) => {
|
||||
setOrientation(
|
||||
orientationToOrientationLock(event.orientationInfo.orientation)
|
||||
orientationToOrientationLock(event.orientationInfo.orientation),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useDownload } from "@/providers/DownloadProvider";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { getItemImage } from "@/utils/getItemImage";
|
||||
import { writeErrorLog, writeInfoLog, writeToLog } from "@/utils/log";
|
||||
import {
|
||||
import type {
|
||||
BaseItemDto,
|
||||
MediaSourceInfo,
|
||||
} from "@jellyfin/sdk/lib/generated-client/models";
|
||||
@@ -14,16 +14,16 @@ import { useRouter } from "expo-router";
|
||||
const FFMPEGKitReactNative = !Platform.isTV
|
||||
? require("ffmpeg-kit-react-native")
|
||||
: null;
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import useDownloadHelper from "@/utils/download";
|
||||
import type { JobStatus } from "@/utils/optimize-server";
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Platform } from "react-native";
|
||||
import { toast } from "sonner-native";
|
||||
import useImageStorage from "./useImageStorage";
|
||||
import useDownloadHelper from "@/utils/download";
|
||||
import { Api } from "@jellyfin/sdk";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import { JobStatus } from "@/utils/optimize-server";
|
||||
import { Platform } from "react-native";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type FFmpegSession = typeof FFMPEGKitReactNative.FFmpegSession;
|
||||
type Statistics = typeof FFMPEGKitReactNative.Statistics;
|
||||
@@ -107,7 +107,7 @@ export const useRemuxHlsToMp4 = () => {
|
||||
setProcesses((prev: any[]) => {
|
||||
return prev.filter(
|
||||
(process: { itemId: string | undefined }) =>
|
||||
process.itemId !== item.Id
|
||||
process.itemId !== item.Id,
|
||||
);
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -116,7 +116,7 @@ export const useRemuxHlsToMp4 = () => {
|
||||
|
||||
console.log("completeCallback ~ end");
|
||||
},
|
||||
[processes, setProcesses]
|
||||
[processes, setProcesses],
|
||||
);
|
||||
|
||||
const statisticsCallback = useCallback(
|
||||
@@ -146,13 +146,13 @@ export const useRemuxHlsToMp4 = () => {
|
||||
});
|
||||
});
|
||||
},
|
||||
[setProcesses, completeCallback]
|
||||
[setProcesses, completeCallback],
|
||||
);
|
||||
|
||||
const startRemuxing = useCallback(
|
||||
async (item: BaseItemDto, url: string, mediaSource: MediaSourceInfo) => {
|
||||
const cacheDir = await FileSystem.getInfoAsync(
|
||||
APP_CACHE_DOWNLOAD_DIRECTORY
|
||||
APP_CACHE_DOWNLOAD_DIRECTORY,
|
||||
);
|
||||
if (!cacheDir.exists) {
|
||||
await FileSystem.makeDirectoryAsync(APP_CACHE_DOWNLOAD_DIRECTORY, {
|
||||
@@ -178,7 +178,7 @@ export const useRemuxHlsToMp4 = () => {
|
||||
toast.dismiss();
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -201,25 +201,25 @@ export const useRemuxHlsToMp4 = () => {
|
||||
createFFmpegCommand(url, output).join(" "),
|
||||
(session: any) => completeCallback(session, item),
|
||||
undefined,
|
||||
(s: any) => statisticsCallback(s, item)
|
||||
(s: any) => statisticsCallback(s, item),
|
||||
);
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
console.error("Failed to remux:", error);
|
||||
writeErrorLog(
|
||||
`useRemuxHlsToMp4 ~ remuxing failed for item: ${item.Name},
|
||||
Error: ${error.message}, Stack: ${error.stack}`
|
||||
Error: ${error.message}, Stack: ${error.stack}`,
|
||||
);
|
||||
setProcesses((prev: any[]) => {
|
||||
return prev.filter(
|
||||
(process: { itemId: string | undefined }) =>
|
||||
process.itemId !== item.Id
|
||||
process.itemId !== item.Id,
|
||||
);
|
||||
});
|
||||
throw error; // Re-throw the error to propagate it to the caller
|
||||
}
|
||||
},
|
||||
[settings, processes, setProcesses, completeCallback, statisticsCallback]
|
||||
[settings, processes, setProcesses, completeCallback, statisticsCallback],
|
||||
);
|
||||
|
||||
const cancelRemuxing = useCallback(() => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { useAtom } from "jotai";
|
||||
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api/session-api";
|
||||
import { userAtom } from "@/providers/JellyfinProvider";
|
||||
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api/session-api";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAtom } from "jotai";
|
||||
import { Platform } from "react-native";
|
||||
const Notifications = !Platform.isTV ? require("expo-notifications") : null;
|
||||
|
||||
@@ -11,7 +11,10 @@ export interface useSessionsProps {
|
||||
activeWithinSeconds: number;
|
||||
}
|
||||
|
||||
export const useSessions = ({ refetchInterval = 5 * 1000, activeWithinSeconds = 360 }: useSessionsProps) => {
|
||||
export const useSessions = ({
|
||||
refetchInterval = 5 * 1000,
|
||||
activeWithinSeconds = 360,
|
||||
}: useSessionsProps) => {
|
||||
const [api] = useAtom(apiAtom);
|
||||
const [user] = useAtom(userAtom);
|
||||
|
||||
@@ -24,13 +27,17 @@ export const useSessions = ({ refetchInterval = 5 * 1000, activeWithinSeconds =
|
||||
const response = await getSessionApi(api).getSessions({
|
||||
activeWithinSeconds: activeWithinSeconds,
|
||||
});
|
||||
|
||||
|
||||
const result = response.data
|
||||
.filter((s) => s.NowPlayingItem)
|
||||
.sort((a, b) => (b.NowPlayingItem?.Name ?? "").localeCompare(a.NowPlayingItem?.Name ?? ""));
|
||||
|
||||
.sort((a, b) =>
|
||||
(b.NowPlayingItem?.Name ?? "").localeCompare(
|
||||
a.NowPlayingItem?.Name ?? "",
|
||||
),
|
||||
);
|
||||
|
||||
// Notifications.setBadgeCountAsync(result.length);
|
||||
return result
|
||||
return result;
|
||||
},
|
||||
refetchInterval: refetchInterval,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { ticksToMs } from "@/utils/time";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||
import { Image } from "expo-image";
|
||||
import { useAtom } from "jotai";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
@@ -107,7 +107,7 @@ export const useTrickplay = (item: BaseItemDto, enabled = true) => {
|
||||
setTrickPlayUrl(newTrickPlayUrl);
|
||||
return newTrickPlayUrl;
|
||||
},
|
||||
[trickplayInfo, item, api, enabled]
|
||||
[trickplayInfo, item, api, enabled],
|
||||
);
|
||||
|
||||
const prefetchAllTrickplayImages = useCallback(() => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect } from "react";
|
||||
import { Alert } from "react-native";
|
||||
import { useRouter } from "expo-router";
|
||||
import { useWebSocketContext } from "@/providers/WebSocketProvider";
|
||||
import { useRouter } from "expo-router";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Alert } from "react-native";
|
||||
|
||||
interface UseWebSocketProps {
|
||||
isPlaying: boolean;
|
||||
@@ -42,7 +42,7 @@ export const useWebSocket = ({
|
||||
console.log("Command ~ DisplayMessage");
|
||||
const title = json?.Data?.Arguments?.Header;
|
||||
const body = json?.Data?.Arguments?.Text;
|
||||
Alert.alert(t("player.message_from_server", {message: title}), body);
|
||||
Alert.alert(t("player.message_from_server", { message: title }), body);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user