mirror of
https://github.com/streamyfin/streamyfin.git
synced 2025-08-20 18:37:18 +02:00
# Add download size to offline media downloads
- Added getDownloadSize helper function to display media size in MB or GB when appropriate
This commit is contained in:
31
components/downloads/DownloadSize.tsx
Normal file
31
components/downloads/DownloadSize.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
|
import React, {useEffect, useMemo, useState} from "react";
|
||||||
|
import {Text} from "@/components/common/Text";
|
||||||
|
import useDownloadHelper from "@/utils/download";
|
||||||
|
|
||||||
|
interface DownloadSizeProps {
|
||||||
|
items: BaseItemDto[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DownloadSize: React.FC<DownloadSizeProps> = ({ items }) => {
|
||||||
|
const { getDownloadSize } = useDownloadHelper();
|
||||||
|
const [size, setSize] = useState<string | undefined>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getDownloadSize(...items).then(setSize)
|
||||||
|
},
|
||||||
|
[items]
|
||||||
|
);
|
||||||
|
|
||||||
|
const sizeText = useMemo(() => {
|
||||||
|
if (!size)
|
||||||
|
return "reading size..."
|
||||||
|
return size
|
||||||
|
}, [size])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Text className="text-xs text-neutral-500">{sizeText}</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import * as Haptics from "expo-haptics";
|
import * as Haptics from "expo-haptics";
|
||||||
import React, { useCallback, useMemo } from "react";
|
import React, {useCallback, useMemo} from "react";
|
||||||
import { TouchableOpacity, View } from "react-native";
|
import { TouchableOpacity, View } from "react-native";
|
||||||
import {
|
import {
|
||||||
ActionSheetProvider,
|
ActionSheetProvider,
|
||||||
@@ -8,12 +8,13 @@ import {
|
|||||||
} from "@expo/react-native-action-sheet";
|
} from "@expo/react-native-action-sheet";
|
||||||
|
|
||||||
import { useDownloadedFileOpener } from "@/hooks/useDownloadedFileOpener";
|
import { useDownloadedFileOpener } from "@/hooks/useDownloadedFileOpener";
|
||||||
import { useDownload } from "@/providers/DownloadProvider";
|
import {useDownload} from "@/providers/DownloadProvider";
|
||||||
import { storage } from "@/utils/mmkv";
|
import { storage } from "@/utils/mmkv";
|
||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import {Text} from "@/components/common/Text";
|
import {Text} from "@/components/common/Text";
|
||||||
import {runtimeTicksToSeconds} from "@/utils/time";
|
import {runtimeTicksToSeconds} from "@/utils/time";
|
||||||
|
import {DownloadSize} from "@/components/downloads/DownloadSize";
|
||||||
|
|
||||||
interface EpisodeCardProps {
|
interface EpisodeCardProps {
|
||||||
item: BaseItemDto;
|
item: BaseItemDto;
|
||||||
@@ -114,6 +115,7 @@ export const EpisodeCard: React.FC<EpisodeCardProps> = ({ item }) => {
|
|||||||
<Text className="text-xs text-neutral-500">
|
<Text className="text-xs text-neutral-500">
|
||||||
{runtimeTicksToSeconds(item.RunTimeTicks)}
|
{runtimeTicksToSeconds(item.RunTimeTicks)}
|
||||||
</Text>
|
</Text>
|
||||||
|
<DownloadSize items={[item]} />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Text numberOfLines={3} className="text-xs text-neutral-500 shrink">{item.Overview}</Text>
|
<Text numberOfLines={3} className="text-xs text-neutral-500 shrink">{item.Overview}</Text>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
} from "@expo/react-native-action-sheet";
|
} from "@expo/react-native-action-sheet";
|
||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import * as Haptics from "expo-haptics";
|
import * as Haptics from "expo-haptics";
|
||||||
import React, { useCallback, useMemo } from "react";
|
import React, {useCallback, useEffect, useMemo, useState} from "react";
|
||||||
import { TouchableOpacity, View } from "react-native";
|
import { TouchableOpacity, View } from "react-native";
|
||||||
|
|
||||||
import { useDownloadedFileOpener } from "@/hooks/useDownloadedFileOpener";
|
import { useDownloadedFileOpener } from "@/hooks/useDownloadedFileOpener";
|
||||||
@@ -13,6 +13,7 @@ import { storage } from "@/utils/mmkv";
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import { ItemCardText } from "../ItemCardText";
|
import { ItemCardText } from "../ItemCardText";
|
||||||
|
import {DownloadSize} from "@/components/downloads/DownloadSize";
|
||||||
|
|
||||||
interface MovieCardProps {
|
interface MovieCardProps {
|
||||||
item: BaseItemDto;
|
item: BaseItemDto;
|
||||||
@@ -97,6 +98,7 @@ export const MovieCard: React.FC<MovieCardProps> = ({ item }) => {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<ItemCardText item={item} />
|
<ItemCardText item={item} />
|
||||||
|
<DownloadSize items={[item]} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import {TouchableOpacity, View} from "react-native";
|
import {TouchableOpacity, View} from "react-native";
|
||||||
import { Text } from "../common/Text";
|
import { Text } from "../common/Text";
|
||||||
import React, {useMemo} from "react";
|
import React, {useEffect, useMemo, useState} from "react";
|
||||||
import {storage} from "@/utils/mmkv";
|
import {storage} from "@/utils/mmkv";
|
||||||
import {Image} from "expo-image";
|
import {Image} from "expo-image";
|
||||||
import {Ionicons} from "@expo/vector-icons";
|
import {Ionicons} from "@expo/vector-icons";
|
||||||
import {router} from "expo-router";
|
import {router} from "expo-router";
|
||||||
|
import {DownloadSize} from "@/components/downloads/DownloadSize";
|
||||||
|
|
||||||
export const SeriesCard: React.FC<{ items: BaseItemDto[] }> = ({items}) => {
|
export const SeriesCard: React.FC<{ items: BaseItemDto[] }> = ({items}) => {
|
||||||
const base64Image = useMemo(() => {
|
const base64Image = useMemo(() => {
|
||||||
@@ -45,6 +46,7 @@ export const SeriesCard: React.FC<{ items: BaseItemDto[] }> = ({items}) => {
|
|||||||
<View className="w-28 mt-2 flex flex-col">
|
<View className="w-28 mt-2 flex flex-col">
|
||||||
<Text numberOfLines={2} className="">{items[0].SeriesName}</Text>
|
<Text numberOfLines={2} className="">{items[0].SeriesName}</Text>
|
||||||
<Text className="text-xs opacity-50">{items[0].ProductionYear}</Text>
|
<Text className="text-xs opacity-50">{items[0].ProductionYear}</Text>
|
||||||
|
<DownloadSize items={items} />
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import useImageStorage from "@/hooks/useImageStorage";
|
|||||||
import {apiAtom} from "@/providers/JellyfinProvider";
|
import {apiAtom} from "@/providers/JellyfinProvider";
|
||||||
import {useAtom} from "jotai";
|
import {useAtom} from "jotai";
|
||||||
import {storage} from "@/utils/mmkv";
|
import {storage} from "@/utils/mmkv";
|
||||||
|
import {getDownloadedFileUrl} from "@/hooks/useDownloadedFileOpener";
|
||||||
|
import * as FileSystem from 'expo-file-system'
|
||||||
|
import {FileInfo} from "expo-file-system";
|
||||||
|
|
||||||
const useDownloadHelper = () => {
|
const useDownloadHelper = () => {
|
||||||
const [api] = useAtom(apiAtom);
|
const [api] = useAtom(apiAtom);
|
||||||
@@ -15,7 +18,29 @@ const useDownloadHelper = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { saveSeriesPrimaryImage }
|
const getDownloadSize = async (...items: BaseItemDto[]) => {
|
||||||
|
const sizes: number[] = [];
|
||||||
|
|
||||||
|
await Promise.all(items.map(item => {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
const url = await getDownloadedFileUrl(item.Id!);
|
||||||
|
if (url) {
|
||||||
|
const fileInfo: FileInfo = await FileSystem.getInfoAsync(url);
|
||||||
|
sizes.push(fileInfo.size);
|
||||||
|
resolve(sizes);
|
||||||
|
} else reject();
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
|
||||||
|
const size = sizes.reduce((sum, size) => sum + size, 0);
|
||||||
|
const gb = size / 1e+9;
|
||||||
|
|
||||||
|
if (gb >= 1)
|
||||||
|
return `${gb.toFixed(2)} GB`;
|
||||||
|
return `${(size / 1024 / 1024).toFixed(2)} MB`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { saveSeriesPrimaryImage, getDownloadSize }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useDownloadHelper;
|
export default useDownloadHelper;
|
||||||
Reference in New Issue
Block a user