This commit is contained in:
Fredrik Burmester
2025-02-16 16:58:24 +01:00
parent 1a2e044da6
commit d2ecfac44e
11 changed files with 210 additions and 537 deletions

View File

@@ -1,39 +1,103 @@
import { Text } from "@/components/common/Text";
import { DownloadInfo } from "@/modules/hls-downloader/src/HlsDownloader.types";
import { useNativeDownloads } from "@/providers/NativeDownloadProvider";
import { useRouter } from "expo-router";
import { useEffect } from "react";
import { TouchableOpacity, View } from "react-native";
import { ActivityIndicator, TouchableOpacity, View } from "react-native";
export default function index() {
const { downloadedFiles, getDownloadedItem, activeDownloads } =
useNativeDownloads();
const PROGRESSBAR_HEIGHT = 10;
const formatETA = (seconds: number): string => {
const pad = (n: number) => n.toString().padStart(2, "0");
const hrs = Math.floor(seconds / 3600);
const mins = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
return `${pad(hrs)}:${pad(mins)}:${pad(secs)}`;
};
const getETA = (download: DownloadInfo): string | null => {
console.log("getETA", download);
if (
!download.startTime ||
!download.bytesDownloaded ||
!download.bytesTotal
) {
console.log(download);
return null;
}
const elapsed = Date.now() / 100 - download.startTime; // seconds
console.log("Elapsed (s):", Number(download.startTime), Date.now(), elapsed);
if (elapsed <= 0 || download.bytesDownloaded <= 0) return null;
const speed = download.bytesDownloaded / elapsed; // bytes per second
const remainingBytes = download.bytesTotal - download.bytesDownloaded;
if (speed <= 0) return null;
const secondsLeft = remainingBytes / speed;
return formatETA(secondsLeft);
};
export default function Index() {
const { downloadedFiles, activeDownloads } = useNativeDownloads();
const router = useRouter();
const goToVideo = (item: any) => {
console.log(item);
// @ts-expect-error
router.push("/player/direct-player?offline=true&itemId=" + item.id);
};
useEffect(() => {
console.log(activeDownloads);
}, [activeDownloads]);
return (
<View className="p-4 space-y-2">
{activeDownloads.map((i) => (
<View>
<Text>{i.id}</Text>
</View>
))}
{activeDownloads.map((i) => {
const progress =
i.bytesTotal && i.bytesDownloaded
? i.bytesDownloaded / i.bytesTotal
: 0;
const eta = getETA(i);
return (
<View key={i.id}>
<Text>{i.metadata?.item?.Name}</Text>
{i.state === "PENDING" ? (
<ActivityIndicator size={"small"} color={"white"} />
) : i.state === "DOWNLOADING" ? (
<Text>
{i.bytesDownloaded} / {i.bytesTotal}
</Text>
) : null}
<View
className="bg-neutral-800"
style={{
height: PROGRESSBAR_HEIGHT,
borderRadius: 5,
overflow: "hidden",
marginVertical: 4,
}}
>
<View
className="bg-purple-600"
style={{
width: `${progress * 100}%`,
height: "100%",
}}
/>
</View>
{eta ? <Text>ETA: {eta}</Text> : <Text>Calculating...</Text>}
</View>
);
})}
{downloadedFiles.map((i) => (
<TouchableOpacity
key={i.id}
onPress={() => goToVideo(i)}
className="bg-neutral-800 p-4 rounded-lg"
>
<Text>{i.metadata.item.Name}</Text>
<Text>{i.metadata.item.Type}</Text>
<Text>{i.metadata.item?.Name}</Text>
<Text>{i.metadata.item?.Type}</Text>
</TouchableOpacity>
))}
</View>

View File

@@ -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 { Colors } from "@/constants/Colors";
import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import {
@@ -11,7 +12,7 @@ import {
useSplashScreenVisible,
} from "@/providers/SplashScreenProvider";
import { useSettings } from "@/utils/atoms/settings";
import { Ionicons } from "@expo/vector-icons";
import { Feather, Ionicons } from "@expo/vector-icons";
import { Api } from "@jellyfin/sdk";
import {
BaseItemDto,
@@ -26,7 +27,11 @@ import {
} from "@jellyfin/sdk/lib/utils/api";
import NetInfo from "@react-native-community/netinfo";
import { QueryFunction, useQuery } from "@tanstack/react-query";
import { useRouter } from "expo-router";
import {
useNavigation,
useNavigationContainerRef,
useRouter,
} from "expo-router";
import { useAtomValue } from "jotai";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -34,6 +39,7 @@ import {
ActivityIndicator,
RefreshControl,
ScrollView,
TouchableOpacity,
View,
} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
@@ -83,6 +89,23 @@ export default function index() {
setLoadingRetry(false);
}, []);
const navigation = useNavigation();
useEffect(() => {
navigation.setOptions({
headerLeft: () => (
<TouchableOpacity
onPress={() => {
router.push("/(auth)/downloads");
}}
className="p-2"
>
<Feather name="download" color={Colors.primary} size={22} />
</TouchableOpacity>
),
});
}, []);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener((state) => {
if (state.isConnected == false || state.isInternetReachable === false)

View File

@@ -21,9 +21,6 @@ import React, { lazy, useEffect } from "react";
import { ScrollView, TouchableOpacity, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { storage } from "@/utils/mmkv";
const DownloadSettings = lazy(
() => import("@/components/settings/DownloadSettings")
);
export default function settings() {
const router = useRouter();
@@ -72,8 +69,6 @@ export default function settings() {
<OtherSettings />
{!Platform.isTV && <DownloadSettings />}
<PluginSettings />
<AppLanguageSelector />

View File

@@ -1,89 +0,0 @@
import { Text } from "@/components/common/Text";
import { OptimizedServerForm } from "@/components/settings/OptimizedServerForm";
import { apiAtom } from "@/providers/JellyfinProvider";
import { useSettings } from "@/utils/atoms/settings";
import { getOrSetDeviceId } from "@/utils/device";
import { getStatistics } from "@/utils/optimize-server";
import { useMutation } from "@tanstack/react-query";
import { useNavigation } from "expo-router";
import { useAtom } from "jotai";
import { useEffect, useState } from "react";
import { ActivityIndicator, TouchableOpacity, View } from "react-native";
import { toast } from "sonner-native";
import { useTranslation } from "react-i18next";
import DisabledSetting from "@/components/settings/DisabledSetting";
export default function page() {
const navigation = useNavigation();
const { t } = useTranslation();
const [api] = useAtom(apiAtom);
const [settings, updateSettings, pluginSettings] = useSettings();
const [optimizedVersionsServerUrl, setOptimizedVersionsServerUrl] =
useState<string>(settings?.optimizedVersionsServerUrl || "");
const saveMutation = useMutation({
mutationFn: async (newVal: string) => {
if (newVal.length === 0 || !newVal.startsWith("http")) {
toast.error(t("home.settings.toasts.invalid_url"));
return;
}
const updatedUrl = newVal.endsWith("/") ? newVal : newVal + "/";
updateSettings({
optimizedVersionsServerUrl: updatedUrl,
});
return await getStatistics({
url: settings?.optimizedVersionsServerUrl,
authHeader: api?.accessToken,
deviceId: getOrSetDeviceId(),
});
},
onSuccess: (data) => {
if (data) {
toast.success(t("home.settings.toasts.connected"));
} else {
toast.error(t("home.settings.toasts.could_not_connect"));
}
},
onError: () => {
toast.error(t("home.settings.toasts.could_not_connect"));
},
});
const onSave = (newVal: string) => {
saveMutation.mutate(newVal);
};
useEffect(() => {
if (!pluginSettings?.optimizedVersionsServerUrl?.locked) {
navigation.setOptions({
title: t("home.settings.downloads.optimized_server"),
headerRight: () =>
saveMutation.isPending ? (
<ActivityIndicator size={"small"} color={"white"} />
) : (
<TouchableOpacity onPress={() => onSave(optimizedVersionsServerUrl)}>
<Text className="text-blue-500">{t("home.settings.downloads.save_button")}</Text>
</TouchableOpacity>
),
});
}
}, [navigation, optimizedVersionsServerUrl, saveMutation.isPending]);
return (
<DisabledSetting
disabled={pluginSettings?.optimizedVersionsServerUrl?.locked === true}
className="p-4"
>
<OptimizedServerForm
value={optimizedVersionsServerUrl}
onChangeValue={setOptimizedVersionsServerUrl}
/>
</DisabledSetting>
);
}