downloads refactor

This commit is contained in:
Jorrin
2024-04-06 16:53:54 +02:00
parent bf6bd7af2f
commit c61f18941e
19 changed files with 179 additions and 195 deletions

View File

@@ -1,4 +1,3 @@
import type { Asset } from "expo-media-library";
import React from "react";
import { Alert, Platform } from "react-native";
import { ScrollView } from "react-native-gesture-handler";
@@ -12,13 +11,16 @@ import type { ScrapeMedia } from "@movie-web/provider-utils";
import { DownloadItem } from "~/components/DownloadItem";
import ScreenLayout from "~/components/layout/ScreenLayout";
import { MWButton } from "~/components/ui/Button";
import { useDownloadManager } from "~/hooks/DownloadManagerContext";
import { useDownloadManager } from "~/contexts/DownloadManagerContext";
import { PlayerStatus } from "~/stores/player/slices/interface";
import { usePlayerStore } from "~/stores/player/store";
const DownloadsScreen: React.FC = () => {
const { startDownload, downloads } = useDownloadManager();
const resetVideo = usePlayerStore((state) => state.resetVideo);
const setAsset = usePlayerStore((state) => state.setAsset);
const setVideoSrc = usePlayerStore((state) => state.setVideoSrc);
const setIsLocalFile = usePlayerStore((state) => state.setIsLocalFile);
const setPlayerStatus = usePlayerStore((state) => state.setPlayerStatus);
const router = useRouter();
const theme = useTheme();
@@ -39,10 +41,14 @@ const DownloadsScreen: React.FC = () => {
}, [router]),
);
const handlePress = (asset?: Asset) => {
if (!asset) return;
const handlePress = (localPath?: string) => {
if (!localPath) return;
resetVideo();
setAsset(asset);
setIsLocalFile(true);
setPlayerStatus(PlayerStatus.READY);
setVideoSrc({
uri: localPath,
});
router.push({
pathname: "/videoPlayer",
});
@@ -112,8 +118,8 @@ const DownloadsScreen: React.FC = () => {
{downloads.map((item) => (
<DownloadItem
key={item.id}
{...item}
onPress={() => handlePress(item.asset)}
item={item}
onPress={() => handlePress(item.localPath)}
/>
))}
</ScrollView>

View File

@@ -118,8 +118,8 @@ export default function SettingsScreen() {
return (
<ScreenLayout>
<View padding={4}>
<YStack gap="$4">
<View>
<YStack gap="$8">
<YStack gap="$4">
<Text fontSize="$7" fontWeight="$bold">
Appearance
@@ -181,7 +181,7 @@ export default function SettingsScreen() {
</Text>
<DefaultQualitySelector qualityType="data" />
</XStack>
<XStack gap="$4" alignItems="center">
<XStack gap="$3" alignItems="center">
<Text fontWeight="$semibold" flexGrow={1}>
Allow downloads on mobile data
</Text>
@@ -451,7 +451,7 @@ export function DefaultQualitySelector(props: DefaultQualitySelectorProps) {
{...props}
>
<MWSelect.Trigger
maxWidth="$12"
maxWidth="$10"
iconAfter={
<FontAwesome name="chevron-down" color={theme.inputIconColor.val} />
}

View File

@@ -10,7 +10,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { TamaguiProvider, Theme, useTheme } from "tamagui";
import tamaguiConfig from "tamagui.config";
import { DownloadManagerProvider } from "~/hooks/DownloadManagerContext";
import { DownloadManagerProvider } from "~/contexts/DownloadManagerContext";
import { useThemeStore } from "~/stores/theme";
// @ts-expect-error - Without named import it causes an infinite loop
import _styles from "../../tamagui-web.css";

View File

@@ -11,7 +11,6 @@ import { usePlayerStore } from "~/stores/player/store";
export default function VideoPlayerWrapper() {
const playerStatus = usePlayerStore((state) => state.interface.playerStatus);
const asset = usePlayerStore((state) => state.asset);
const { presentFullscreenPlayer } = usePlayer();
const params = useLocalSearchParams();
@@ -32,10 +31,6 @@ export default function VideoPlayerWrapper() {
void presentFullscreenPlayer();
if (asset) {
return <VideoPlayer />;
}
if (download) {
return <ScraperProcess data={data} download />;
}

View File

@@ -10,9 +10,9 @@ export function BrandPill() {
flexDirection="row"
alignItems="center"
justifyContent="center"
paddingHorizontal="$2.5"
paddingVertical="$2"
gap={2}
paddingHorizontal="$3"
paddingVertical="$2.5"
gap="$2.5"
opacity={0.8}
backgroundColor="$pillBackground"
borderRadius={24}
@@ -24,11 +24,10 @@ export function BrandPill() {
>
<MovieWebSvg
fillColor={theme.tabBarIconFocused.val}
width={12}
height={12}
width={20}
height={20}
/>
<Text fontSize="$4" fontWeight="$bold" paddingRight={5} paddingLeft={3}>
{/* padding might need adjusting */}
<Text fontSize="$6" fontWeight="$bold">
movie-web
</Text>
</View>

View File

@@ -1,25 +1,17 @@
import type { Asset } from "expo-media-library";
import type { NativeSyntheticEvent } from "react-native";
import type { ContextMenuOnPressNativeEvent } from "react-native-context-menu-view";
import React from "react";
import ContextMenu from "react-native-context-menu-view";
import { TouchableOpacity } from "react-native-gesture-handler";
import { Progress, Spinner, Text, View } from "tamagui";
import { Image, Text, View, XStack, YStack } from "tamagui";
import { useDownloadManager } from "~/hooks/DownloadManagerContext";
import type { Download } from "~/contexts/DownloadManagerContext";
import { useDownloadManager } from "~/contexts/DownloadManagerContext";
import { MWProgress } from "./ui/Progress";
export interface DownloadItemProps {
id: string;
filename: string;
progress: number;
speed: number;
fileSize: number;
downloaded: number;
isFinished: boolean;
statusText?: string;
asset?: Asset;
isHLS?: boolean;
onPress: (asset?: Asset) => void;
item: Download;
onPress: (localPath?: string) => void;
}
enum ContextMenuActions {
@@ -27,6 +19,15 @@ enum ContextMenuActions {
Remove = "Remove",
}
const statusToTextMap: Record<Download["status"], string> = {
downloading: "Downloading",
finished: "Finished",
error: "Error",
merging: "Merging",
cancelled: "Cancelled",
importing: "Importing",
};
const formatBytes = (bytes: number, decimals = 2) => {
if (bytes === 0) return "0 Bytes";
const k = 1024;
@@ -36,64 +37,28 @@ const formatBytes = (bytes: number, decimals = 2) => {
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
};
export const DownloadItem: React.FC<DownloadItemProps> = ({
id,
filename,
progress,
speed,
fileSize,
downloaded,
isFinished,
statusText,
asset,
isHLS,
onPress,
}) => {
const percentage = progress * 100;
const formattedFileSize = formatBytes(fileSize);
const formattedDownloaded = formatBytes(downloaded);
export function DownloadItem(props: DownloadItemProps) {
const percentage = props.item.progress * 100;
const formattedFileSize = formatBytes(props.item.fileSize);
const formattedDownloaded = formatBytes(props.item.downloaded);
const { removeDownload, cancelDownload } = useDownloadManager();
const contextMenuActions = [
{
title: ContextMenuActions.Remove,
},
...(!isFinished ? [{ title: ContextMenuActions.Cancel }] : []),
...(props.item.status !== "finished"
? [{ title: ContextMenuActions.Cancel }]
: []),
];
const onContextMenuPress = (
e: NativeSyntheticEvent<ContextMenuOnPressNativeEvent>,
) => {
if (e.nativeEvent.name === ContextMenuActions.Cancel) {
void cancelDownload(id);
void cancelDownload(props.item.id);
} else if (e.nativeEvent.name === ContextMenuActions.Remove) {
removeDownload(id);
}
};
const renderStatus = () => {
if (statusText) {
return (
<View style={{ flexDirection: "row", alignItems: "center" }}>
<Spinner size="small" color="$loadingIndicator" />
<Text fontSize={12} color="gray" style={{ marginLeft: 8 }}>
{statusText}
</Text>
</View>
);
} else if (isFinished) {
return (
<Text fontSize={12} color="gray">
Finished
</Text>
);
} else {
if (isHLS) return null;
return (
<Text fontSize={12} color="gray">
{speed.toFixed(2)} MB/s
</Text>
);
removeDownload(props.item.id);
}
};
@@ -103,41 +68,63 @@ export const DownloadItem: React.FC<DownloadItemProps> = ({
onPress={onContextMenuPress}
previewBackgroundColor="transparent"
>
<TouchableOpacity onPress={() => onPress(asset)} activeOpacity={0.7}>
<View
marginBottom={16}
borderRadius={8}
borderColor="white"
padding={16}
>
<Text marginBottom={4} fontSize={16}>
{filename}
</Text>
<Progress
value={percentage}
height={10}
backgroundColor="$progressBackground"
>
<Progress.Indicator
animation="bounce"
backgroundColor="$progressFilled"
/>
</Progress>
<TouchableOpacity
onPress={() => props.onPress(props.item.localPath)}
onLongPress={() => {
return;
}}
activeOpacity={0.7}
>
<XStack gap="$4" alignItems="center">
<View
marginTop={8}
flexDirection="row"
alignItems="center"
justifyContent="space-between"
aspectRatio={9 / 14}
width={70}
maxHeight={180}
overflow="hidden"
borderRadius="$2"
>
<Text fontSize={12} color="gray">
{isHLS
? `${percentage.toFixed()}% - ${downloaded} of ${fileSize} segments`
: `${percentage.toFixed()}% - ${formattedDownloaded} of ${formattedFileSize}`}
</Text>
{renderStatus()}
<Image
source={{
uri: "https://image.tmdb.org/t/p/original//or06FN3Dka5tukK1e9sl16pB3iy.jpg",
}}
width="100%"
height="100%"
/>
</View>
</View>
<YStack gap="$2">
<XStack gap="$5">
<Text
fontWeight="$bold"
ellipse
maxWidth={props.item.type === "hls" ? "70%" : "40%"}
flexGrow={1}
>
{props.item.media.title}
</Text>
{props.item.type !== "hls" && (
<Text fontSize={12} color="gray">
{props.item.speed.toFixed(2)} MB/s
</Text>
)}
</XStack>
<MWProgress value={percentage} height={10}>
<MWProgress.Indicator />
</MWProgress>
<XStack alignItems="center" justifyContent="space-between">
<Text fontSize={12} color="gray">
{props.item.type === "hls"
? `${percentage.toFixed()}% - ${props.item.downloaded} of ${props.item.fileSize} segments`
: `${percentage.toFixed()}% - ${formattedDownloaded} of ${formattedFileSize}`}
</Text>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<Text fontSize={12} color="gray">
{statusToTextMap[props.item.status]}
</Text>
</View>
</XStack>
</YStack>
</XStack>
</TouchableOpacity>
</ContextMenu>
);
};
}

View File

@@ -18,21 +18,12 @@ export const ItemListSection = ({
}) => {
return (
<View>
<Text marginBottom={8} marginTop={16} fontWeight="500" fontSize={20}>
<Text marginBottom={8} marginTop={16} fontWeight="bold" fontSize="$8">
{title}
</Text>
<ScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ paddingHorizontal: 3 }}
>
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}>
{items.map((item, index) => (
<View
key={index}
width={itemWidth}
paddingHorizontal={padding / 2}
paddingBottom={padding}
>
<View key={index} width={itemWidth} paddingBottom={padding}>
<Item data={item} />
</View>
))}

View File

@@ -142,8 +142,8 @@ export default function Item({ data }: { data: ItemData }) {
{type === "tv" ? "Show" : "Movie"}
</Text>
<View
height={8}
width={8}
height={6}
width={6}
borderRadius={24}
backgroundColor="$ash100"
/>

View File

@@ -21,7 +21,7 @@ export function Header() {
<Circle
backgroundColor="$pillBackground"
size="$2.5"
size="$4.5"
pressStyle={{
opacity: 1,
scale: 1.05,
@@ -33,11 +33,11 @@ export function Header() {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy)
}
>
<MaterialIcons name="discord" size={20} color="white" />
<MaterialIcons name="discord" size={32} color="white" />
</Circle>
<Circle
backgroundColor="$pillBackground"
size="$2.5"
size="$4.5"
pressStyle={{
opacity: 1,
scale: 1.05,
@@ -49,7 +49,7 @@ export function Header() {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy)
}
>
<FontAwesome6 name="github" size={20} color="white" />
<FontAwesome6 name="github" size={32} color="white" />
</Circle>
</View>
);

View File

@@ -1,4 +1,4 @@
import { View } from "tamagui";
import { ScrollView } from "tamagui";
import { LinearGradient } from "tamagui/linear-gradient";
import { Header } from "./Header";
@@ -12,7 +12,7 @@ export default function ScreenLayout({ children }: Props) {
<LinearGradient
flex={1}
paddingVertical="$4"
paddingHorizontal="$7"
paddingHorizontal="$4"
colors={[
"$shade900",
"$purple900",
@@ -26,9 +26,13 @@ export default function ScreenLayout({ children }: Props) {
flexGrow={1}
>
<Header />
<View paddingVertical="$4" flexGrow={1}>
<ScrollView
marginTop="$4"
flexGrow={1}
showsVerticalScrollIndicator={false}
>
{children}
</View>
</ScrollView>
</LinearGradient>
);
}

View File

@@ -14,9 +14,10 @@ import { SettingsSelector } from "./SettingsSelector";
import { SourceSelector } from "./SourceSelector";
import { mapMillisecondsToTime } from "./utils";
export const BottomControls = ({ isLocalAsset }: { isLocalAsset: boolean }) => {
export const BottomControls = () => {
const status = usePlayerStore((state) => state.status);
const setIsIdle = usePlayerStore((state) => state.setIsIdle);
const isLocalFile = usePlayerStore((state) => state.isLocalFile);
const [showRemaining, setShowRemaining] = useState(false);
const toggleTimeDisplay = useCallback(() => {
@@ -76,7 +77,7 @@ export const BottomControls = ({ isLocalAsset }: { isLocalAsset: boolean }) => {
gap={4}
paddingBottom={40}
>
{!isLocalAsset && (
{!isLocalFile && (
<>
<SeasonSelector />
<CaptionsSelector />

View File

@@ -4,13 +4,7 @@ import { BottomControls } from "./BottomControls";
import { Header } from "./Header";
import { MiddleControls } from "./MiddleControls";
export const ControlsOverlay = ({
isLoading,
isLocalAsset,
}: {
isLoading: boolean;
isLocalAsset: boolean;
}) => {
export const ControlsOverlay = ({ isLoading }: { isLoading: boolean }) => {
return (
<View
width="100%"
@@ -20,7 +14,7 @@ export const ControlsOverlay = ({
>
<Header />
{!isLoading && <MiddleControls />}
<BottomControls isLocalAsset={isLocalAsset} />
<BottomControls />
</View>
);
};

View File

@@ -3,7 +3,7 @@ import { useTheme } from "tamagui";
import { findHighestQuality } from "@movie-web/provider-utils";
import { useDownloadManager } from "~/hooks/DownloadManagerContext";
import { useDownloadManager } from "~/contexts/DownloadManagerContext";
import { convertMetaToScrapeMedia } from "~/lib/meta";
import { usePlayerStore } from "~/stores/player/store";
import { MWButton } from "../ui/Button";

View File

@@ -18,7 +18,7 @@ import {
import type { ItemData } from "../item/item";
import type { AudioTrack } from "./AudioTrackSelector";
import type { PlayerMeta } from "~/stores/player/slices/video";
import { useDownloadManager } from "~/hooks/DownloadManagerContext";
import { useDownloadManager } from "~/contexts/DownloadManagerContext";
import { useMeta } from "~/hooks/player/useMeta";
import { useScrape } from "~/hooks/player/useSourceScrape";
import { convertMetaToScrapeMedia } from "~/lib/meta";

View File

@@ -11,7 +11,6 @@ import Animated, {
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { ResizeMode, Video } from "expo-av";
import * as Haptics from "expo-haptics";
import * as MediaLibrary from "expo-media-library";
import * as NavigationBar from "expo-navigation-bar";
import { useRouter } from "expo-router";
import * as StatusBar from "expo-status-bar";
@@ -63,7 +62,6 @@ export const VideoPlayer = () => {
const stream = usePlayerStore((state) => state.interface.currentStream);
const selectedAudioTrack = useAudioTrackStore((state) => state.selectedTrack);
const videoRef = usePlayerStore((state) => state.videoRef);
const asset = usePlayerStore((state) => state.asset);
const setVideoRef = usePlayerStore((state) => state.setVideoRef);
const videoSrc = usePlayerStore((state) => state.videoSrc) ?? undefined;
const setVideoSrc = usePlayerStore((state) => state.setVideoSrc);
@@ -73,6 +71,7 @@ export const VideoPlayer = () => {
const toggleState = usePlayerStore((state) => state.toggleState);
const meta = usePlayerStore((state) => state.meta);
const setMeta = usePlayerStore((state) => state.setMeta);
const isLocalFile = usePlayerStore((state) => state.isLocalFile);
const { gestureControls, autoPlay } = usePlayerSettingsStore();
const { updateWatchHistory, removeFromWatchHistory, getWatchHistoryItem } =
@@ -151,15 +150,7 @@ export const VideoPlayer = () => {
useEffect(() => {
const initializePlayer = async () => {
if (asset) {
const assetInfo = await MediaLibrary.getAssetInfoAsync(asset);
if (!assetInfo.localUri) return;
setVideoSrc({
uri: assetInfo.localUri,
});
setIsLoading(false);
return;
}
if (videoSrc?.uri && isLocalFile) return;
if (!stream) {
await dismissFullscreenPlayer();
@@ -194,7 +185,6 @@ export const VideoPlayer = () => {
setIsLoading(false);
};
setIsLoading(true);
void initializePlayer();
const timeout = setTimeout(() => {
@@ -217,7 +207,7 @@ export const VideoPlayer = () => {
void synchronizePlayback();
};
}, [
asset,
isLocalFile,
dismissFullscreenPlayer,
hasStartedPlaying,
meta,
@@ -228,6 +218,7 @@ export const VideoPlayer = () => {
synchronizePlayback,
updateWatchHistory,
videoRef?.props.positionMillis,
videoSrc?.uri,
]);
const onVideoLoadStart = () => {
@@ -322,7 +313,7 @@ export const VideoPlayer = () => {
position="absolute"
/>
)}
<ControlsOverlay isLoading={isLoading} isLocalAsset={!!asset} />
<ControlsOverlay isLoading={isLoading} />
</View>
{showVolumeOverlay && <GestureOverlay value={volume} type="volume" />}
{showBrightnessOverlay && (

View File

@@ -0,0 +1,14 @@
import { Progress, styled, withStaticProperties } from "tamagui";
const MWProgressFrame = styled(Progress, {
backgroundColor: "$progressBackground",
});
const MWProgressIndicator = styled(Progress.Indicator, {
backgroundColor: "$progressFilled",
animation: "bounce",
});
export const MWProgress = withStaticProperties(MWProgressFrame, {
Indicator: MWProgressIndicator,
});

View File

@@ -23,23 +23,31 @@ import {
useNetworkSettingsStore,
} from "~/stores/settings";
export interface DownloadItem {
export interface Download {
id: string;
filename: string;
progress: number;
speed: number;
fileSize: number;
downloaded: number;
url: string;
type: "mp4" | "hls";
isFinished: boolean;
statusText?: string;
asset?: Asset;
isHLS?: boolean;
status:
| "downloading"
| "finished"
| "error"
| "merging"
| "cancelled"
| "importing";
localPath?: string;
media: ScrapeMedia;
downloadTask?: DownloadTask;
}
export interface DownloadContent {
media: Pick<ScrapeMedia, "title" | "releaseYear" | "type" | "tmdbId">;
downloads: Download[];
}
// @ts-expect-error - types are not up to date
setConfig({
isLogsEnabled: false,
@@ -47,7 +55,7 @@ setConfig({
});
interface DownloadManagerContextType {
downloads: DownloadItem[];
downloads: Download[];
startDownload: (
url: string,
type: "mp4" | "hls",
@@ -75,7 +83,7 @@ export const useDownloadManager = () => {
export const DownloadManagerProvider: React.FC<{ children: ReactNode }> = ({
children,
}) => {
const [downloads, setDownloads] = useState<DownloadItem[]>([]);
const [downloads, setDownloads] = useState<Download[]>([]);
const toastController = useToastController();
useEffect(() => {
@@ -172,17 +180,15 @@ export const DownloadManagerProvider: React.FC<{ children: ReactNode }> = ({
duration: 500,
});
const newDownload: DownloadItem = {
const newDownload: Download = {
id: `download-${Date.now()}-${Math.random().toString(16).slice(2)}`,
filename: url.split("/").pop() ?? "unknown",
progress: 0,
speed: 0,
fileSize: 0,
downloaded: 0,
type,
url,
isFinished: false,
isHLS: type === "hls",
status: "downloading",
media,
};
@@ -197,7 +203,7 @@ export const DownloadManagerProvider: React.FC<{ children: ReactNode }> = ({
}
};
const updateDownloadItem = (id: string, updates: Partial<DownloadItem>) => {
const updateDownloadItem = (id: string, updates: Partial<Download>) => {
setDownloads((currentDownloads) =>
currentDownloads.map((download) =>
download.id === id ? { ...download, ...updates } : download,
@@ -342,7 +348,7 @@ export const DownloadManagerProvider: React.FC<{ children: ReactNode }> = ({
return removeDownload(downloadId);
}
updateDownloadItem(downloadId, { statusText: "Merging" });
updateDownloadItem(downloadId, { status: "merging" });
const uri = await VideoManager.mergeVideos(
localSegmentPaths,
`${FileSystem.cacheDirectory}movie-web/output.mp4`,
@@ -394,15 +400,14 @@ export const DownloadManagerProvider: React.FC<{ children: ReactNode }> = ({
downloadId: string,
): Promise<Asset | void> => {
try {
updateDownloadItem(downloadId, { statusText: "Importing" });
updateDownloadItem(downloadId, { status: "importing" });
const asset = await MediaLibrary.createAssetAsync(fileUri);
await FileSystem.deleteAsync(fileUri);
updateDownloadItem(downloadId, {
statusText: undefined,
asset,
isFinished: true,
status: "finished",
localPath: asset.uri,
});
console.log("File saved to media library and original deleted");
toastController.show("Download finished", {

View File

@@ -1,5 +1,4 @@
import type { AVPlaybackSourceObject, AVPlaybackStatus, Video } from "expo-av";
import type { Asset } from "expo-media-library";
import type { MakeSlice } from "./types";
import { PlayerStatus } from "./interface";
@@ -31,13 +30,13 @@ export interface VideoSlice {
videoSrc: AVPlaybackSourceObject | null;
status: AVPlaybackStatus | null;
meta: PlayerMeta | null;
asset: Asset | null;
isLocalFile: boolean;
setVideoRef(ref: Video | null): void;
setVideoSrc(src: AVPlaybackSourceObject | null): void;
setStatus(status: AVPlaybackStatus | null): void;
setMeta(meta: PlayerMeta | null): void;
setAsset(asset: Asset | null): void;
setIsLocalFile(isLocalFile: boolean): void;
resetVideo(): void;
}
@@ -46,7 +45,7 @@ export const createVideoSlice: MakeSlice<VideoSlice> = (set) => ({
videoSrc: null,
status: null,
meta: null,
asset: null,
isLocalFile: false,
setVideoRef: (ref) => {
set({ videoRef: ref });
@@ -67,13 +66,11 @@ export const createVideoSlice: MakeSlice<VideoSlice> = (set) => ({
s.meta = meta;
});
},
setAsset: (asset) => {
set((s) => {
s.asset = asset;
});
setIsLocalFile: (isLocalFile) => {
set({ isLocalFile });
},
resetVideo() {
set({ videoRef: null, status: null, meta: null, asset: null });
set({ videoRef: null, status: null, meta: null, isLocalFile: false });
set((s) => {
s.interface.playerStatus = PlayerStatus.SCRAPING;
});

View File

@@ -7,7 +7,7 @@ import { createJSONStorage, persist } from "zustand/middleware";
import type { ScrapeMedia } from "@movie-web/provider-utils";
import type { ItemData } from "~/components/item/item";
import type { DownloadItem } from "~/hooks/DownloadManagerContext";
import type { Download } from "~/contexts/DownloadManagerContext";
import type { ThemeStoreOption } from "~/stores/theme";
const storage = new MMKV();
@@ -77,8 +77,8 @@ export const usePlayerSettingsStore = create<
);
interface DownloadHistoryStoreState {
downloads: DownloadItem[];
setDownloads: (downloads: DownloadItem[]) => void;
downloads: Download[];
setDownloads: (downloads: Download[]) => void;
}
export const useDownloadHistoryStore = create<
@@ -88,7 +88,7 @@ export const useDownloadHistoryStore = create<
persist(
(set) => ({
downloads: [],
setDownloads: (downloads: DownloadItem[]) => set({ downloads }),
setDownloads: (downloads: Download[]) => set({ downloads }),
}),
{
name: "download-history",