mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Create a basic download list
This commit is contained in:
parent
2e0a0e5eb0
commit
c0cf11ee79
@ -82,7 +82,6 @@ const Menu = <AsProps,>({
|
|||||||
<>
|
<>
|
||||||
<Trigger
|
<Trigger
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if ("onPress" in props && typeof props.onPress === "function") props.onPress();
|
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
}}
|
}}
|
||||||
{...(props as any)}
|
{...(props as any)}
|
||||||
|
@ -18,21 +18,192 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useAtomValue } from "jotai";
|
import { State, downloadAtom } from "./state";
|
||||||
import { downloadAtom } from "./state";
|
|
||||||
import { FlashList } from "@shopify/flash-list";
|
import { FlashList } from "@shopify/flash-list";
|
||||||
import { View } from "react-native";
|
import { ImageStyle, View } from "react-native";
|
||||||
import { P } from "@kyoo/primitives";
|
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 = () => {
|
export const DownloadPage = () => {
|
||||||
|
const pageStyle = usePageStyle();
|
||||||
const downloads = useAtomValue(downloadAtom);
|
const downloads = useAtomValue(downloadAtom);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
if (downloads.length === 0) return <EmptyView message={t("downloads.empty")} />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlashList
|
<FlashList
|
||||||
data={downloads}
|
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}
|
keyExtractor={(x) => x.data.id}
|
||||||
numColumns={1}
|
numColumns={1}
|
||||||
|
contentContainerStyle={pageStyle}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -37,7 +37,7 @@ import { getCurrentAccount, storage } from "@kyoo/models/src/account-internal";
|
|||||||
import { ReactNode, useEffect } from "react";
|
import { ReactNode, useEffect } from "react";
|
||||||
import { Platform, ToastAndroid } from "react-native";
|
import { Platform, ToastAndroid } from "react-native";
|
||||||
|
|
||||||
type State = {
|
export type State = {
|
||||||
status: "DOWNLOADING" | "PAUSED" | "DONE" | "FAILED" | "STOPPED";
|
status: "DOWNLOADING" | "PAUSED" | "DONE" | "FAILED" | "STOPPED";
|
||||||
progress: number;
|
progress: number;
|
||||||
size: number;
|
size: number;
|
||||||
@ -158,6 +158,11 @@ export const useDownloader = () => {
|
|||||||
query(Player.infoQuery(type, slug), account),
|
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
|
// TODO: support custom paths
|
||||||
const path = `${RNBackgroundDownloader.directories.documents}/${slug}-${data.id}.${info.extension}`;
|
const path = `${RNBackgroundDownloader.directories.documents}/${slug}-${data.id}.${info.extension}`;
|
||||||
const task = RNBackgroundDownloader.download({
|
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 SearchBar = forwardRef<TextInput, Stylable>(function SearchBar(props, ref) {
|
||||||
const { css, theme } = useYoshiki();
|
const { theme } = useYoshiki();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { push, replace, back } = useRouter();
|
const { push, replace, back } = useRouter();
|
||||||
const hasChanged = useRef<boolean>(false);
|
const hasChanged = useRef<boolean>(false);
|
||||||
@ -135,12 +135,12 @@ export const NavbarProfile = () => {
|
|||||||
t("login.delete"),
|
t("login.delete"),
|
||||||
t("login.delete-confirmation"),
|
t("login.delete-confirmation"),
|
||||||
[
|
[
|
||||||
|
{ text: t("misc.cancel"), style: "cancel" },
|
||||||
{
|
{
|
||||||
text: t("misc.delete"),
|
text: t("misc.delete"),
|
||||||
onPress: deleteAccount,
|
onPress: deleteAccount,
|
||||||
style: "destructive",
|
style: "destructive",
|
||||||
},
|
},
|
||||||
{ text: t("misc.cancel"), style: "cancel" },
|
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
|
@ -107,6 +107,15 @@
|
|||||||
"delete": "Delete your account",
|
"delete": "Delete your account",
|
||||||
"delete-confirmation": "This action can't be reverted. Are you sure?"
|
"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": {
|
"errors": {
|
||||||
"connection": "Could not connect to the kyoo's server",
|
"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?",
|
"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": "Supprimer votre compte",
|
||||||
"delete-confirmation": "Cette action ne peut pas être annulée. Êtes-vous sur?"
|
"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": {
|
"errors": {
|
||||||
"connection": "Impossible de se connecter au serveur de kyoo.",
|
"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 ?",
|
"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