Add a downloads tab

This commit is contained in:
Zoe Roux 2023-12-18 12:47:55 +01:00
parent f1cc396bfc
commit 71d29a303f
10 changed files with 113 additions and 19 deletions

View File

@ -22,6 +22,7 @@ import { Icon } from "@kyoo/primitives";
import { Tabs } from "expo-router"; import { Tabs } from "expo-router";
import Home from "@material-symbols/svg-400/rounded/home-fill.svg"; import Home from "@material-symbols/svg-400/rounded/home-fill.svg";
import Browse from "@material-symbols/svg-400/rounded/browse-fill.svg"; import Browse from "@material-symbols/svg-400/rounded/browse-fill.svg";
import Downloading from "@material-symbols/svg-400/rounded/downloading-fill.svg";
export default function TabsLayout() { export default function TabsLayout() {
return ( return (
@ -44,6 +45,13 @@ export default function TabsLayout() {
tabBarIcon: ({ color, size }) => <Icon icon={Browse} color={color} size={size} />, tabBarIcon: ({ color, size }) => <Icon icon={Browse} color={color} size={size} />,
}} }}
/> />
<Tabs.Screen
name="downloads"
options={{
tabBarLabel: "Downloads",
tabBarIcon: ({ color, size }) => <Icon icon={Downloading} color={color} size={size} />,
}}
/>
</Tabs> </Tabs>
); );
} }

View File

@ -0,0 +1,23 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { DownloadPage } from "@kyoo/ui";
export default DownloadPage;

View File

@ -48,6 +48,7 @@ import "@formatjs/intl-displaynames/locale-data/fr";
import en from "../../../translations/en.json"; import en from "../../../translations/en.json";
import fr from "../../../translations/fr.json"; import fr from "../../../translations/fr.json";
import { useTheme } from "yoshiki/native"; import { useTheme } from "yoshiki/native";
import { DownloadProvider } from "@kyoo/ui";
i18next.use(initReactI18next).init({ i18next.use(initReactI18next).init({
interpolation: { interpolation: {
@ -103,9 +104,11 @@ export default function Root() {
> >
<PortalProvider> <PortalProvider>
<AccountProvider> <AccountProvider>
<NavigationThemeProvider> <DownloadProvider>
<Slot /> <NavigationThemeProvider>
</NavigationThemeProvider> <Slot />
</NavigationThemeProvider>
</DownloadProvider>
</AccountProvider> </AccountProvider>
</PortalProvider> </PortalProvider>
</ThemeSelector> </ThemeSelector>

View File

@ -52,6 +52,7 @@ const styleText = (
{ {
marginVertical: rem(0.5), marginVertical: rem(0.5),
color: type === "header" ? theme.heading : theme.paragraph, color: type === "header" ? theme.heading : theme.paragraph,
flexShrink: 1,
fontSize: rem(1), fontSize: rem(1),
fontFamily: theme.font.normal, fontFamily: theme.font.normal,
}, },

View File

@ -27,7 +27,7 @@ import Download from "@material-symbols/svg-400/rounded/download.svg";
import { WatchStatusV, queryFn, useAccount } from "@kyoo/models"; import { WatchStatusV, queryFn, useAccount } from "@kyoo/models";
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import { watchListIcon } from "./watchlist-info"; import { watchListIcon } from "./watchlist-info";
import { downloadFile } from "../downloads/download"; import { downloadFile } from "../downloads/state";
export const EpisodesContext = ({ export const EpisodesContext = ({
type = "episode", type = "episode",

View File

@ -0,0 +1,22 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
export { DownloadPage } from "./page";
export { DownloadProvider } from "./state";

View File

@ -0,0 +1,39 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { useAtomValue } from "jotai";
import { downloadAtom } from "./state";
import { FlashList } from "@shopify/flash-list";
import { View } from "react-native";
export const DownloadPage = () => {
const downloads = useAtomValue(downloadAtom);
return (
<FlashList
data={downloads}
renderItem={({ item }) => {
<View>{item.data.name}</View>;
}}
keyExtractor={(x) => x.data.id}
numColumns={1}
/>
);
};

View File

@ -18,13 +18,8 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>. * along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { import RNBackgroundDownloader, {
download, type DownloadTask,
completeHandler,
directories,
DownloadTask,
checkForExistingDownloads,
ensureDownloadsAreRunning,
} from "@kesha-antonov/react-native-background-downloader"; } from "@kesha-antonov/react-native-background-downloader";
import { deleteAsync } from "expo-file-system"; import { deleteAsync } from "expo-file-system";
import { import {
@ -39,7 +34,7 @@ import {
import { Player } from "../player"; import { Player } from "../player";
import { atom, useSetAtom, PrimitiveAtom, useStore } from "jotai"; import { atom, useSetAtom, PrimitiveAtom, useStore } from "jotai";
import { getCurrentAccount, storage } from "@kyoo/models/src/account-internal"; import { getCurrentAccount, storage } from "@kyoo/models/src/account-internal";
import { useEffect } from "react"; import { ReactNode, useEffect } from "react";
type State = { type State = {
status: "DOWNLOADING" | "PAUSED" | "DONE" | "FAILED" | "STOPPED"; status: "DOWNLOADING" | "PAUSED" | "DONE" | "FAILED" | "STOPPED";
@ -53,7 +48,7 @@ type State = {
play: () => void; play: () => void;
}; };
const downloadAtom = atom< export const downloadAtom = atom<
{ {
data: Episode | Movie; data: Episode | Movie;
info: WatchInfo; info: WatchInfo;
@ -116,7 +111,7 @@ const setupDownloadTask = (
update((x) => ({ ...x, percent: 100, status: "DONE" })); update((x) => ({ ...x, percent: 100, status: "DONE" }));
// apparently this is needed for ios /shrug i'm totaly gona forget this // apparently this is needed for ios /shrug i'm totaly gona forget this
// if i ever implement ios so keeping this here // if i ever implement ios so keeping this here
completeHandler(task.id); RNBackgroundDownloader.completeHandler(task.id);
}) })
.error((error) => update((x) => ({ ...x, status: "FAILED", error }))); .error((error) => update((x) => ({ ...x, status: "FAILED", error })));
@ -158,8 +153,8 @@ export const useDownloader = () => {
]); ]);
// TODO: support custom paths // TODO: support custom paths
const path = `${directories.documents}/${slug}-${data.id}.${info.extension}`; const path = `${RNBackgroundDownloader.directories.documents}/${slug}-${data.id}.${info.extension}`;
const task = download({ const task = RNBackgroundDownloader.download({
id: data.id, id: data.id,
// TODO: support variant qualities // TODO: support variant qualities
url: `${account.apiUrl}/api/video/${type}/${slug}/direct`, url: `${account.apiUrl}/api/video/${type}/${slug}/direct`,
@ -175,14 +170,14 @@ export const useDownloader = () => {
}; };
}; };
export const DownloadProvider = () => { export const DownloadProvider = ({ children }: { children: ReactNode }) => {
const store = useStore(); const store = useStore();
useEffect(() => { useEffect(() => {
async function run() { async function run() {
if (store.get(downloadAtom).length) return; if (store.get(downloadAtom).length) return;
const tasks = await checkForExistingDownloads(); const tasks = await RNBackgroundDownloader.checkForExistingDownloads();
const dls: { data: Episode | Movie; info: WatchInfo; path: string; state: State }[] = const dls: { data: Episode | Movie; info: WatchInfo; path: string; state: State }[] =
JSON.parse(storage.getString("downloads") ?? "[]"); JSON.parse(storage.getString("downloads") ?? "[]");
const downloads = dls.map((dl) => { const downloads = dls.map((dl) => {
@ -214,8 +209,10 @@ export const DownloadProvider = () => {
for (const t of tasks) { for (const t of tasks) {
if (!downloads.find((x) => x.data.id === t.id)) t.stop(); if (!downloads.find((x) => x.data.id === t.id)) t.stop();
} }
ensureDownloadsAreRunning(); RNBackgroundDownloader.ensureDownloadsAreRunning();
} }
run(); run();
}, [store]); }, [store]);
return children;
}; };

View File

@ -26,3 +26,4 @@ export { CollectionPage } from "./collection";
export { Player } from "./player"; export { Player } from "./player";
export { SearchPage } from "./search"; export { SearchPage } from "./search";
export { LoginPage, RegisterPage } from "./login"; export { LoginPage, RegisterPage } from "./login";
export { DownloadPage, DownloadProvider } from "./downloads";