mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-31 04:04:21 -04:00
Create a basic download list
This commit is contained in:
parent
2e0a0e5eb0
commit
c0cf11ee79
@ -82,7 +82,6 @@ const Menu = <AsProps,>({
|
||||
<>
|
||||
<Trigger
|
||||
onPress={() => {
|
||||
if ("onPress" in props && typeof props.onPress === "function") props.onPress();
|
||||
setOpen(true);
|
||||
}}
|
||||
{...(props as any)}
|
||||
|
@ -18,21 +18,192 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useAtomValue } from "jotai";
|
||||
import { downloadAtom } from "./state";
|
||||
import { State, downloadAtom } from "./state";
|
||||
import { FlashList } from "@shopify/flash-list";
|
||||
import { View } from "react-native";
|
||||
import { P } from "@kyoo/primitives";
|
||||
import { ImageStyle, View } from "react-native";
|
||||
import {
|
||||
Alert,
|
||||
H6,
|
||||
IconButton,
|
||||
ImageBackground,
|
||||
Link,
|
||||
Menu,
|
||||
P,
|
||||
PressableFeedback,
|
||||
SubP,
|
||||
focusReset,
|
||||
ts,
|
||||
usePageStyle,
|
||||
} from "@kyoo/primitives";
|
||||
import { EpisodeLine, displayRuntime, episodeDisplayNumber } from "../details/episode";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { EmptyView } from "../fetch";
|
||||
import { percent, useYoshiki } from "yoshiki/native";
|
||||
import { KyooImage } from "@kyoo/models";
|
||||
import { Atom, useAtomValue } from "jotai";
|
||||
import DownloadForOffline from "@material-symbols/svg-400/rounded/download_for_offline.svg";
|
||||
import Downloading from "@material-symbols/svg-400/rounded/downloading.svg";
|
||||
import Error from "@material-symbols/svg-400/rounded/error.svg";
|
||||
import NotStarted from "@material-symbols/svg-400/rounded/not_started.svg";
|
||||
|
||||
const DownloadedItem = ({
|
||||
name,
|
||||
statusAtom,
|
||||
runtime,
|
||||
kind,
|
||||
image,
|
||||
...props
|
||||
}: {
|
||||
name: string;
|
||||
statusAtom: Atom<State>;
|
||||
runtime: number | null;
|
||||
kind: "episode" | "movie";
|
||||
image: KyooImage | null;
|
||||
}) => {
|
||||
const { css } = useYoshiki();
|
||||
const { t } = useTranslation();
|
||||
const { error, status, pause, resume, remove, play } = useAtomValue(statusAtom);
|
||||
|
||||
return (
|
||||
<PressableFeedback
|
||||
onPress={() => play?.()}
|
||||
{...css(
|
||||
{
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
fover: {
|
||||
self: focusReset,
|
||||
title: {
|
||||
textDecorationLine: "underline",
|
||||
},
|
||||
},
|
||||
},
|
||||
props,
|
||||
)}
|
||||
>
|
||||
<ImageBackground
|
||||
src={image}
|
||||
quality="low"
|
||||
alt=""
|
||||
gradient={false}
|
||||
hideLoad={false}
|
||||
layout={{
|
||||
width: percent(18),
|
||||
aspectRatio: 16 / 9,
|
||||
}}
|
||||
{...(css({ flexShrink: 0, m: ts(1) }) as { style: ImageStyle })}
|
||||
>
|
||||
{/* {(watchedPercent || watchedStatus === WatchStatusV.Completed) && ( */}
|
||||
{/* <> */}
|
||||
{/* <View */}
|
||||
{/* {...css({ */}
|
||||
{/* backgroundColor: (theme) => theme.overlay0, */}
|
||||
{/* width: percent(100), */}
|
||||
{/* height: ts(0.5), */}
|
||||
{/* position: "absolute", */}
|
||||
{/* bottom: 0, */}
|
||||
{/* })} */}
|
||||
{/* /> */}
|
||||
{/* <View */}
|
||||
{/* {...css({ */}
|
||||
{/* backgroundColor: (theme) => theme.accent, */}
|
||||
{/* width: percent(watchedPercent ?? 100), */}
|
||||
{/* height: ts(0.5), */}
|
||||
{/* position: "absolute", */}
|
||||
{/* bottom: 0, */}
|
||||
{/* })} */}
|
||||
{/* /> */}
|
||||
{/* </> */}
|
||||
{/* )} */}
|
||||
</ImageBackground>
|
||||
<View
|
||||
{...css({
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
})}
|
||||
>
|
||||
<View {...css({ flexGrow: 1, flexShrink: 1 })}>
|
||||
<H6 aria-level={undefined} {...css([{ flexShrink: 1 }, "title"])}>
|
||||
{name ?? t("show.episodeNoMetadata")}
|
||||
</H6>
|
||||
{status === "FAILED" && <P>{t("downloads.error", { error: error ?? "Unknow error" })}</P>}
|
||||
{runtime && status !== "FAILED" && <SubP>{displayRuntime(runtime)}</SubP>}
|
||||
</View>
|
||||
<Menu Trigger={IconButton} icon={downloadIcon(status)}>
|
||||
{status === "FAILED" && <Menu.Item label={t("downloads.retry")} onSelect={() => {}} />}
|
||||
{status === "DOWNLOADING" && (
|
||||
<Menu.Item label={t("downloads.pause")} onSelect={() => pause?.()} />
|
||||
)}
|
||||
{status === "PAUSED" && (
|
||||
<Menu.Item label={t("downloads.resume")} onSelect={() => resume?.()} />
|
||||
)}
|
||||
<Menu.Item
|
||||
label={t("downloads.delete")}
|
||||
onSelect={() => {
|
||||
Alert.alert(
|
||||
t("downloads.delete"),
|
||||
t("downloads.deleteMessage"),
|
||||
[
|
||||
{ text: t("misc.cancel"), style: "cancel" },
|
||||
{ text: t("misc.delete"), onPress: remove, style: "destructive" },
|
||||
],
|
||||
{
|
||||
icon: "error",
|
||||
},
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Menu>
|
||||
</View>
|
||||
</PressableFeedback>
|
||||
);
|
||||
};
|
||||
|
||||
const downloadIcon = (status: State["status"]) => {
|
||||
switch (status) {
|
||||
case "DONE":
|
||||
return DownloadForOffline;
|
||||
case "DOWNLOADING":
|
||||
return Downloading;
|
||||
case "FAILED":
|
||||
return Error;
|
||||
case "PAUSED":
|
||||
case "STOPPED":
|
||||
default:
|
||||
return NotStarted;
|
||||
}
|
||||
};
|
||||
|
||||
export const DownloadPage = () => {
|
||||
const pageStyle = usePageStyle();
|
||||
const downloads = useAtomValue(downloadAtom);
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (downloads.length === 0) return <EmptyView message={t("downloads.empty")} />;
|
||||
|
||||
return (
|
||||
<FlashList
|
||||
data={downloads}
|
||||
renderItem={({ item }) => <P>{item.data.name}</P>}
|
||||
getItemType={(item) => item.data.kind}
|
||||
renderItem={({ item }) => (
|
||||
<DownloadedItem
|
||||
name={
|
||||
item.data.kind === "episode"
|
||||
? `${episodeDisplayNumber(item.data)!}: ${item.data.name}`
|
||||
: item.data.name
|
||||
}
|
||||
statusAtom={item.state}
|
||||
runtime={item.data.runtime}
|
||||
kind={item.data.kind}
|
||||
image={item.data.kind === "episode" ? item.data.thumbnail : item.data.poster}
|
||||
/>
|
||||
)}
|
||||
estimatedItemSize={EpisodeLine.layout.size}
|
||||
keyExtractor={(x) => x.data.id}
|
||||
numColumns={1}
|
||||
contentContainerStyle={pageStyle}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -37,7 +37,7 @@ import { getCurrentAccount, storage } from "@kyoo/models/src/account-internal";
|
||||
import { ReactNode, useEffect } from "react";
|
||||
import { Platform, ToastAndroid } from "react-native";
|
||||
|
||||
type State = {
|
||||
export type State = {
|
||||
status: "DOWNLOADING" | "PAUSED" | "DONE" | "FAILED" | "STOPPED";
|
||||
progress: number;
|
||||
size: number;
|
||||
@ -158,6 +158,11 @@ export const useDownloader = () => {
|
||||
query(Player.infoQuery(type, slug), account),
|
||||
]);
|
||||
|
||||
if (store.get(downloadAtom).find((x) => x.data.id === data.id)) {
|
||||
ToastAndroid.show(`${slug} is already downloaded, skipping`, ToastAndroid.LONG);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: support custom paths
|
||||
const path = `${RNBackgroundDownloader.directories.documents}/${slug}-${data.id}.${info.extension}`;
|
||||
const task = RNBackgroundDownloader.download({
|
||||
|
@ -55,7 +55,7 @@ export const NavbarTitle = (props: Stylable & { onLayout?: ViewProps["onLayout"]
|
||||
};
|
||||
|
||||
const SearchBar = forwardRef<TextInput, Stylable>(function SearchBar(props, ref) {
|
||||
const { css, theme } = useYoshiki();
|
||||
const { theme } = useYoshiki();
|
||||
const { t } = useTranslation();
|
||||
const { push, replace, back } = useRouter();
|
||||
const hasChanged = useRef<boolean>(false);
|
||||
@ -135,12 +135,12 @@ export const NavbarProfile = () => {
|
||||
t("login.delete"),
|
||||
t("login.delete-confirmation"),
|
||||
[
|
||||
{ text: t("misc.cancel"), style: "cancel" },
|
||||
{
|
||||
text: t("misc.delete"),
|
||||
onPress: deleteAccount,
|
||||
style: "destructive",
|
||||
},
|
||||
{ text: t("misc.cancel"), style: "cancel" },
|
||||
],
|
||||
{
|
||||
cancelable: true,
|
||||
|
@ -107,6 +107,15 @@
|
||||
"delete": "Delete your account",
|
||||
"delete-confirmation": "This action can't be reverted. Are you sure?"
|
||||
},
|
||||
"downloads": {
|
||||
"empty": "Nothing downloaded yet, start browsing for something you like",
|
||||
"error": "Error: {{error}}",
|
||||
"delete": "Delete item",
|
||||
"deleteMessage": "Do you want to remove this item from your local storage?",
|
||||
"pause": "Pause",
|
||||
"resume": "Resume",
|
||||
"retry": "Retry"
|
||||
},
|
||||
"errors": {
|
||||
"connection": "Could not connect to the kyoo's server",
|
||||
"connection-tips": "Troublshotting tips:\n - Are you connected to internet?\n - Is your kyoo's server online?\n - Have your account been banned?",
|
||||
|
@ -107,6 +107,15 @@
|
||||
"delete": "Supprimer votre compte",
|
||||
"delete-confirmation": "Cette action ne peut pas être annulée. Êtes-vous sur?"
|
||||
},
|
||||
"downloads": {
|
||||
"empty": "Rien de téléchargé pour l'instant, commencez à rechercher quelque chose que vous aimez",
|
||||
"error": "Erreur: {{error}}",
|
||||
"delete": "Supprimer un item télechargé",
|
||||
"deleteMessage": "Voulez-vous vraiment supprimer un item télechgargé ?",
|
||||
"pause": "Pause",
|
||||
"resume": "Reprendre",
|
||||
"retry": "Réessayer"
|
||||
},
|
||||
"errors": {
|
||||
"connection": "Impossible de se connecter au serveur de kyoo.",
|
||||
"connection-tips": "Possible causes:\n - Etes-vous connecté a internet ?\n - Votre serveur kyoo est-il allumé ?\n - Votre compte est-il bannis ?",
|
||||
|
Loading…
x
Reference in New Issue
Block a user