diff --git a/app/(auth)/(tabs)/(home)/index.tsx b/app/(auth)/(tabs)/(home)/index.tsx
index d546190f..cc02e39e 100644
--- a/app/(auth)/(tabs)/(home)/index.tsx
+++ b/app/(auth)/(tabs)/(home)/index.tsx
@@ -4,6 +4,7 @@ import { LargeMovieCarousel } from "@/components/home/LargeMovieCarousel";
import { ScrollingCollectionList } from "@/components/home/ScrollingCollectionList";
import { Loader } from "@/components/Loader";
import { MediaListSection } from "@/components/medialists/MediaListSection";
+import { TAB_HEIGHT } from "@/constants/Values";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { useSettings } from "@/utils/atoms/settings";
import { Ionicons } from "@expo/vector-icons";
@@ -25,7 +26,6 @@ import {
ActivityIndicator,
Platform,
RefreshControl,
- SafeAreaView,
ScrollView,
View,
} from "react-native";
@@ -139,18 +139,24 @@ export default function index() {
const refetch = useCallback(async () => {
setLoading(true);
- await queryClient.refetchQueries({ queryKey: ["userViews"] });
- await queryClient.refetchQueries({ queryKey: ["resumeItems"] });
- await queryClient.refetchQueries({ queryKey: ["nextUp-all"] });
- await queryClient.refetchQueries({ queryKey: ["recentlyAddedInMovies"] });
- await queryClient.refetchQueries({ queryKey: ["recentlyAddedInTVShows"] });
- await queryClient.refetchQueries({ queryKey: ["suggestions"] });
- await queryClient.refetchQueries({
- queryKey: ["sf_promoted"],
- });
- await queryClient.refetchQueries({
- queryKey: ["sf_carousel"],
- });
+ await queryClient.invalidateQueries();
+ // await queryClient.invalidateQueries({ queryKey: ["userViews"] });
+ // await queryClient.invalidateQueries({ queryKey: ["resumeItems"] });
+ // await queryClient.invalidateQueries({ queryKey: ["continueWatching"] });
+ // await queryClient.invalidateQueries({ queryKey: ["nextUp-all"] });
+ // await queryClient.invalidateQueries({
+ // queryKey: ["recentlyAddedInMovies"],
+ // });
+ // await queryClient.invalidateQueries({
+ // queryKey: ["recentlyAddedInTVShows"],
+ // });
+ // await queryClient.invalidateQueries({ queryKey: ["suggestions"] });
+ // await queryClient.invalidateQueries({
+ // queryKey: ["sf_promoted"],
+ // });
+ // await queryClient.invalidateQueries({
+ // queryKey: ["sf_carousel"],
+ // });
setLoading(false);
}, [queryClient, user?.Id]);
@@ -344,15 +350,33 @@ export default function index() {
}
key={"home"}
contentContainerStyle={{
+ flexDirection: "column",
paddingLeft: insets.left,
paddingRight: insets.right,
- paddingBottom:
- Platform.OS === "android" ? insets.bottom + 65 : insets.bottom,
+ paddingTop: 8,
+ rowGap: 8,
+ }}
+ style={{
+ marginBottom: TAB_HEIGHT,
}}
- className="flex flex-col space-y-4"
>
+
+ (
+ await getItemsApi(api).getResumeItems({
+ userId: user?.Id,
+ enableImageTypes: ["Primary", "Backdrop", "Thumb"],
+ })
+ ).data.Items || []
+ }
+ orientation={"horizontal"}
+ />
+
-
- (
- await getItemsApi(api).getResumeItems({
- userId: user?.Id,
- enableImageTypes: ["Primary", "Backdrop", "Thumb"],
- })
- ).data.Items || []
- }
- orientation={"horizontal"}
- />
-
{sections.map((section, index) => {
if (section.type === "ScrollingCollectionList") {
return (
diff --git a/app/(auth)/(tabs)/(home)/settings.tsx b/app/(auth)/(tabs)/(home)/settings.tsx
index d1fdf0f9..7077a16f 100644
--- a/app/(auth)/(tabs)/(home)/settings.tsx
+++ b/app/(auth)/(tabs)/(home)/settings.tsx
@@ -79,6 +79,7 @@ export default function settings() {
+
diff --git a/app/(auth)/(tabs)/(search)/index.tsx b/app/(auth)/(tabs)/(search)/index.tsx
index ab64ee3c..456dae9c 100644
--- a/app/(auth)/(tabs)/(search)/index.tsx
+++ b/app/(auth)/(tabs)/(search)/index.tsx
@@ -8,6 +8,7 @@ import { Loader } from "@/components/Loader";
import AlbumCover from "@/components/posters/AlbumCover";
import MoviePoster from "@/components/posters/MoviePoster";
import SeriesPoster from "@/components/posters/SeriesPoster";
+import { TAB_HEIGHT } from "@/constants/Values";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { useSettings } from "@/utils/atoms/settings";
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
@@ -226,8 +227,11 @@ export default function search() {
paddingLeft: insets.left,
paddingRight: insets.right,
}}
+ style={{
+ marginBottom: TAB_HEIGHT,
+ }}
>
-
+
{Platform.OS === "android" && (
= ({
item,
- width = 176,
useEpisodePoster = false,
}) => {
const [api] = useAtom(apiAtom);
@@ -47,21 +47,11 @@ const ContinueWatchingPoster: React.FC = ({
if (!url)
return (
-
+
);
return (
-
+
> = ({
>
{title}
- {subTitle && {subTitle}}
+ {subTitle && (
+
+ {subTitle}
+
+ )}
{iconAfter}
diff --git a/components/PlayedStatus.tsx b/components/PlayedStatus.tsx
index b3b55ee9..375cf22b 100644
--- a/components/PlayedStatus.tsx
+++ b/components/PlayedStatus.tsx
@@ -21,11 +21,17 @@ export const PlayedStatus: React.FC = ({ item, ...props }) => {
const invalidateQueries = () => {
queryClient.invalidateQueries({
- queryKey: ["item"],
+ queryKey: ["item", item.Id],
});
queryClient.invalidateQueries({
queryKey: ["resumeItems"],
});
+ queryClient.invalidateQueries({
+ queryKey: ["continueWatching"],
+ });
+ queryClient.invalidateQueries({
+ queryKey: ["nextUp-all"],
+ });
queryClient.invalidateQueries({
queryKey: ["nextUp"],
});
diff --git a/components/common/HorrizontalScroll.tsx b/components/common/HorrizontalScroll.tsx
index bd885fec..6679453f 100644
--- a/components/common/HorrizontalScroll.tsx
+++ b/components/common/HorrizontalScroll.tsx
@@ -98,7 +98,6 @@ export const HorizontalScroll = forwardRef<
)}
- {...props}
/>
);
}
diff --git a/components/home/ScrollingCollectionList.tsx b/components/home/ScrollingCollectionList.tsx
index e8420d72..433f9877 100644
--- a/components/home/ScrollingCollectionList.tsx
+++ b/components/home/ScrollingCollectionList.tsx
@@ -6,12 +6,13 @@ import {
type QueryFunction,
type QueryKey,
} from "@tanstack/react-query";
-import { View, ViewProps } from "react-native";
+import { ScrollView, View, ViewProps } from "react-native";
import ContinueWatchingPoster from "../ContinueWatchingPoster";
import { ItemCardText } from "../ItemCardText";
import { HorizontalScroll } from "../common/HorrizontalScroll";
import { TouchableItemRouter } from "../common/TouchableItemRouter";
import SeriesPoster from "../posters/SeriesPoster";
+import { FlashList } from "@shopify/flash-list";
interface Props extends ViewProps {
title?: string | null;
@@ -39,40 +40,56 @@ export const ScrollingCollectionList: React.FC = ({
if (disabled || !title) return null;
return (
-
+
{title}
- (
-
- {item.Type === "Episode" && orientation === "horizontal" && (
-
- )}
- {item.Type === "Episode" && orientation === "vertical" && (
-
- )}
- {item.Type === "Movie" && orientation === "horizontal" && (
-
- )}
- {item.Type === "Movie" && orientation === "vertical" && (
-
- )}
- {item.Type === "Series" && }
-
-
- )}
- />
+ {isLoading ? (
+
+ {[1, 2, 3].map((i) => (
+
+
+
+
+
+ ))}
+
+ ) : (
+
+
+ {data?.map((item, index) => (
+
+ {item.Type === "Episode" && orientation === "horizontal" && (
+
+ )}
+ {item.Type === "Episode" && orientation === "vertical" && (
+
+ )}
+ {item.Type === "Movie" && orientation === "horizontal" && (
+
+ )}
+ {item.Type === "Movie" && orientation === "vertical" && (
+
+ )}
+ {item.Type === "Series" && }
+
+
+ ))}
+
+
+ )}
);
};
diff --git a/components/posters/MoviePoster.tsx b/components/posters/MoviePoster.tsx
index 056e2c30..46776fb7 100644
--- a/components/posters/MoviePoster.tsx
+++ b/components/posters/MoviePoster.tsx
@@ -36,7 +36,7 @@ const MoviePoster: React.FC = ({
}, [item]);
return (
-
+
= ({
width: "100%",
}}
/>
-
{showProgress && progress > 0 && (
diff --git a/components/posters/SeriesPoster.tsx b/components/posters/SeriesPoster.tsx
index dbadcdce..e551624a 100644
--- a/components/posters/SeriesPoster.tsx
+++ b/components/posters/SeriesPoster.tsx
@@ -32,7 +32,7 @@ const SeriesPoster: React.FC = ({ item }) => {
}, [item]);
return (
-
+
= ({ item }) => {
cachePolicy={"memory-disk"}
contentFit="cover"
style={{
- aspectRatio: "10/15",
+ height: "100%",
width: "100%",
}}
/>
diff --git a/components/settings/SettingToggles.tsx b/components/settings/SettingToggles.tsx
index 5563659e..c5ba0240 100644
--- a/components/settings/SettingToggles.tsx
+++ b/components/settings/SettingToggles.tsx
@@ -1,5 +1,9 @@
import { useDownload } from "@/providers/DownloadProvider";
-import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
+import {
+ apiAtom,
+ getOrSetDeviceId,
+ userAtom,
+} from "@/providers/JellyfinProvider";
import { ScreenOrientationEnum, useSettings } from "@/utils/atoms/settings";
import {
BACKGROUND_FETCH_TASK,
@@ -28,6 +32,8 @@ import { Input } from "../common/Input";
import { Text } from "../common/Text";
import { Loader } from "../Loader";
import { MediaToggles } from "./MediaToggles";
+import axios from "axios";
+import { getStatistics } from "@/utils/optimize-server";
interface Props extends ViewProps {}
@@ -44,6 +50,21 @@ export const SettingToggles: React.FC = ({ ...props }) => {
const queryClient = useQueryClient();
+ const { data: optimizeServerStatistics } = useQuery({
+ queryKey: ["optimize-server", settings?.optimizedVersionsServerUrl],
+ queryFn: async () =>
+ getStatistics({
+ url: settings?.optimizedVersionsServerUrl,
+ authHeader: api?.accessToken,
+ deviceId: await getOrSetDeviceId(),
+ }),
+ refetchInterval: 1000,
+ staleTime: 0,
+ enabled:
+ !!settings?.optimizedVersionsServerUrl &&
+ settings.optimizedVersionsServerUrl.length > 0,
+ });
+
/********************
* Background task
*******************/
@@ -568,11 +589,24 @@ export const SettingToggles: React.FC = ({ ...props }) => {
>
- Optimized versions server
+
+
+ Optimized versions server
+
+
+
Set the URL for the optimized versions server for downloads.
+
= ({ ...props }) => {
color="purple"
className="h-12 mt-2"
onPress={() => {
- toast.success("Saved");
+ toast.info("Saved");
updateSettings({
optimizedVersionsServerUrl:
optimizedVersionsServerUrl.length === 0
diff --git a/constants/Values.ts b/constants/Values.ts
new file mode 100644
index 00000000..4c3e4d81
--- /dev/null
+++ b/constants/Values.ts
@@ -0,0 +1,3 @@
+import { Platform } from "react-native";
+
+export const TAB_HEIGHT = Platform.OS === "android" ? 58 : 74;
diff --git a/utils/optimize-server.ts b/utils/optimize-server.ts
index f52ad799..443bdf59 100644
--- a/utils/optimize-server.ts
+++ b/utils/optimize-server.ts
@@ -3,9 +3,9 @@ import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
import axios from "axios";
interface IJobInput {
- deviceId: string;
- authHeader: string;
- url: string;
+ deviceId?: string | null;
+ authHeader?: string | null;
+ url?: string | null;
}
export interface JobStatus {
@@ -88,6 +88,10 @@ export async function cancelJobById({
}
export async function cancelAllJobs({ authHeader, url, deviceId }: IJobInput) {
+ if (!deviceId) return false;
+ if (!authHeader) return false;
+ if (!url) return false;
+
try {
await getAllJobsByDeviceId({
deviceId,
@@ -109,3 +113,41 @@ export async function cancelAllJobs({ authHeader, url, deviceId }: IJobInput) {
return true;
}
+
+/**
+ * Fetches statistics for a specific device.
+ *
+ * @param {IJobInput} params - The parameters for the API request.
+ * @param {string} params.deviceId - The ID of the device to fetch statistics for.
+ * @param {string} params.authHeader - The authorization header for the API request.
+ * @param {string} params.url - The base URL for the API endpoint.
+ *
+ * @returns {Promise} A promise that resolves to the statistics data or null if the request fails.
+ *
+ * @throws {Error} Throws an error if any required parameter is missing.
+ */
+export async function getStatistics({
+ authHeader,
+ url,
+ deviceId,
+}: IJobInput): Promise {
+ if (!deviceId || !authHeader || !url) {
+ return null;
+ }
+
+ try {
+ const statusResponse = await axios.get(`${url}statistics`, {
+ headers: {
+ Authorization: authHeader,
+ },
+ params: {
+ deviceId,
+ },
+ });
+
+ return statusResponse.data;
+ } catch (error) {
+ console.error("Failed to fetch statistics:", error);
+ return null;
+ }
+}