diff --git a/app/(auth)/(tabs)/(home)/settings/logs/page.tsx b/app/(auth)/(tabs)/(home)/settings/logs/page.tsx
index 03a8e1ca..12878717 100644
--- a/app/(auth)/(tabs)/(home)/settings/logs/page.tsx
+++ b/app/(auth)/(tabs)/(home)/settings/logs/page.tsx
@@ -1,3 +1,4 @@
+import { File, Paths } from "expo-file-system";
import { useNavigation } from "expo-router";
import * as Sharing from "expo-sharing";
import { useCallback, useEffect, useId, useMemo, useState } from "react";
@@ -48,18 +49,17 @@ export default function Page() {
// Sharing it as txt while its formatted allows us to share it with many more applications
const share = useCallback(async () => {
- const uri = `${FileSystem.documentDirectory}logs.txt`;
+ const logsFile = new File(Paths.document, "logs.txt");
setLoading(true);
- FileSystem.writeAsStringAsync(uri, JSON.stringify(filteredLogs))
- .then(() => {
- setLoading(false);
- Sharing.shareAsync(uri, { mimeType: "txt", UTI: "txt" });
- })
- .catch((e) =>
- writeErrorLog("Something went wrong attempting to export", e),
- )
- .finally(() => setLoading(false));
+ try {
+ logsFile.write(JSON.stringify(filteredLogs));
+ await Sharing.shareAsync(logsFile.uri, { mimeType: "txt", UTI: "txt" });
+ } catch (e: any) {
+ writeErrorLog("Something went wrong attempting to export", e);
+ } finally {
+ setLoading(false);
+ }
}, [filteredLogs]);
useEffect(() => {
diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/person/[personId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/person/[personId].tsx
index 7bef1d7a..a29e1280 100644
--- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/person/[personId].tsx
+++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/person/[personId].tsx
@@ -87,14 +87,15 @@ export default function page() {
{data?.details?.name}
{t("jellyseerr.born")}{" "}
- {new Date(data?.details?.birthday!).toLocaleDateString(
- `${locale}-${region}`,
- {
- year: "numeric",
- month: "long",
- day: "numeric",
- },
- )}{" "}
+ {data?.details?.birthday &&
+ new Date(data.details.birthday).toLocaleDateString(
+ `${locale}-${region}`,
+ {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ },
+ )}{" "}
| {data?.details?.placeOfBirth}
>
diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/channels.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/channels.tsx
index 2aabbe46..6c9790b5 100644
--- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/channels.tsx
+++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/channels.tsx
@@ -33,7 +33,6 @@ export default function page() {
(
diff --git a/biome.json b/biome.json
index 5c18980e..b94bda94 100644
--- a/biome.json
+++ b/biome.json
@@ -1,5 +1,5 @@
{
- "$schema": "https://biomejs.dev/schemas/2.2.7/schema.json",
+ "$schema": "https://biomejs.dev/schemas/2.3.5/schema.json",
"files": {
"includes": [
"**/*",
diff --git a/bun.lock b/bun.lock
index dd515c17..b63e9016 100644
--- a/bun.lock
+++ b/bun.lock
@@ -86,7 +86,7 @@
},
"devDependencies": {
"@babel/core": "^7.20.0",
- "@biomejs/biome": "^2.2.4",
+ "@biomejs/biome": "^2.3.5",
"@react-native-community/cli": "^20.0.0",
"@react-native-tvos/config-tv": "^0.1.1",
"@types/jest": "^29.5.12",
@@ -300,23 +300,23 @@
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
- "@biomejs/biome": ["@biomejs/biome@2.3.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.4", "@biomejs/cli-darwin-x64": "2.3.4", "@biomejs/cli-linux-arm64": "2.3.4", "@biomejs/cli-linux-arm64-musl": "2.3.4", "@biomejs/cli-linux-x64": "2.3.4", "@biomejs/cli-linux-x64-musl": "2.3.4", "@biomejs/cli-win32-arm64": "2.3.4", "@biomejs/cli-win32-x64": "2.3.4" }, "bin": { "biome": "bin/biome" } }, "sha512-TU08LXjBHdy0mEY9APtEtZdNQQijXUDSXR7IK1i45wgoPD5R0muK7s61QcFir6FpOj/RP1+YkPx5QJlycXUU3w=="],
+ "@biomejs/biome": ["@biomejs/biome@2.3.5", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.5", "@biomejs/cli-darwin-x64": "2.3.5", "@biomejs/cli-linux-arm64": "2.3.5", "@biomejs/cli-linux-arm64-musl": "2.3.5", "@biomejs/cli-linux-x64": "2.3.5", "@biomejs/cli-linux-x64-musl": "2.3.5", "@biomejs/cli-win32-arm64": "2.3.5", "@biomejs/cli-win32-x64": "2.3.5" }, "bin": { "biome": "bin/biome" } }, "sha512-HvLhNlIlBIbAV77VysRIBEwp55oM/QAjQEin74QQX9Xb259/XP/D5AGGnZMOyF1el4zcvlNYYR3AyTMUV3ILhg=="],
- "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-w40GvlNzLaqmuWYiDU6Ys9FNhJiclngKqcGld3iJIiy2bpJ0Q+8n3haiaC81uTPY/NA0d8Q/I3Z9+ajc14102Q=="],
+ "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fLdTur8cJU33HxHUUsii3GLx/TR0BsfQx8FkeqIiW33cGMtUD56fAtrh+2Fx1uhiCsVZlFh6iLKUU3pniZREQw=="],
- "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-3s7TLVtjJ7ni1xADXsS7x7GMUrLBZXg8SemXc3T0XLslzvqKj/dq1xGeBQ+pOWQzng9MaozfacIHdK2UlJ3jGA=="],
+ "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-qpT8XDqeUlzrOW8zb4k3tjhT7rmvVRumhi2657I2aGcY4B+Ft5fNwDdZGACzn8zj7/K1fdWjgwYE3i2mSZ+vOA=="],
- "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-y7efHyyM2gYmHy/AdWEip+VgTMe9973aP7XYKPzu/j8JxnPHuSUXftzmPhkVw0lfm4ECGbdBdGD6+rLmTgNZaA=="],
+ "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-u/pybjTBPGBHB66ku4pK1gj+Dxgx7/+Z0jAriZISPX1ocTO8aHh8x8e7Kb1rB4Ms0nA/SzjtNOVJ4exVavQBCw=="],
- "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-IruVGQRwMURivWazchiq7gKAqZSFs5so6gi0hJyxk7x6HR+iwZbO2IxNOqyLURBvL06qkIHs7Wffl6Bw30vCbQ=="],
+ "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-eGUG7+hcLgGnMNl1KHVZUYxahYAhC462jF/wQolqu4qso2MSk32Q+QrpN7eN4jAHAg7FUMIo897muIhK4hXhqg=="],
- "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gKfjWR/6/dfIxPJCw8REdEowiXCkIpl9jycpNVHux8aX2yhWPLjydOshkDL6Y/82PcQJHn95VCj7J+BRcE5o1Q=="],
+ "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-XrIVi9YAW6ye0CGQ+yax0gLfx+BFOtKaNX74n+xHWla6Cl6huUmcKNO7HPx7BiKnJUzrxXY1qYlm7xMvi08X4g=="],
- "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-mzKFFv/w66e4/jCobFmD3kymCqG+FuWE7sVa4Yjqd9v7qt2UhXo67MSZKY9Ih18V2IwPzRKQPCw6KwdZs6AXSA=="],
+ "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-awVuycTPpVTH/+WDVnEEYSf6nbCBHf/4wB3lquwT7puhNg8R4XvonWNZzUsfHZrCkjkLhFH/vCZK5jHatD9FEg=="],
- "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-5TJ6JfVez+yyupJ/iGUici2wzKf0RrSAxJhghQXtAEsc67OIpdwSKAQboemILrwKfHDi5s6mu7mX+VTCTUydkw=="],
+ "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-DlBiMlBZZ9eIq4H7RimDSGsYcOtfOIfZOaI5CqsWiSlbTfqbPVfWtCf92wNzx8GNMbu1s7/g3ZZESr6+GwM/SA=="],
- "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.4", "", { "os": "win32", "cpu": "x64" }, "sha512-FGCijXecmC4IedQ0esdYNlMpx0Jxgf4zceCaMu6fkjWyjgn50ZQtMiqZZQ0Q/77yqPxvtkgZAvt5uGw0gAAjig=="],
+ "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.5", "", { "os": "win32", "cpu": "x64" }, "sha512-nUmR8gb6yvrKhtRgzwo/gDimPwnO5a4sCydf8ZS2kHIJhEmSmk+STsusr1LHTuM//wXppBawvSQi2xFXJCdgKQ=="],
"@bottom-tabs/react-navigation": ["@bottom-tabs/react-navigation@1.0.2", "", { "dependencies": { "color": "^5.0.0" }, "peerDependencies": { "@react-navigation/native": ">=7", "react": "*", "react-native": "*", "react-native-bottom-tabs": "*" } }, "sha512-OrCw8s2NzFxO1TO5W2vyr7HNvh1Yjy00f72D/0BIPtImc0aj5CRrT9nFRE7YP0FWZb0AY5+0QU9jaoph1rBlSg=="],
diff --git a/components/PlayButton.tsx b/components/PlayButton.tsx
index 61f3f2d5..a1bf4f6f 100644
--- a/components/PlayButton.tsx
+++ b/components/PlayButton.tsx
@@ -50,7 +50,6 @@ export const PlayButton: React.FC = ({
selectedOptions,
isOffline,
colors,
- ...props
}: Props) => {
const { showActionSheetWithOptions } = useActionSheet();
const client = useRemoteMediaClient();
diff --git a/components/common/InfiniteHorizontalScroll.tsx b/components/common/InfiniteHorizontalScroll.tsx
index 182a2817..78eac996 100644
--- a/components/common/InfiniteHorizontalScroll.tsx
+++ b/components/common/InfiniteHorizontalScroll.tsx
@@ -120,7 +120,6 @@ export function InfiniteHorizontalScroll({
renderItem={({ item, index }) => (
{renderItem(item, index)}
)}
- estimatedItemSize={height}
horizontal
onEndReached={() => {
if (hasNextPage) {
diff --git a/components/downloads/MovieCard.tsx b/components/downloads/MovieCard.tsx
index b0a5d555..f02fe796 100644
--- a/components/downloads/MovieCard.tsx
+++ b/components/downloads/MovieCard.tsx
@@ -29,15 +29,15 @@ export const MovieCard: React.FC = ({ item }) => {
const { showActionSheetWithOptions } = useActionSheet();
const base64Image = useMemo(() => {
- return storage.getString(item?.Id!);
- }, []);
+ return item?.Id ? storage.getString(item.Id) : undefined;
+ }, [item?.Id]);
/**
* Handles deleting the file with haptic feedback.
*/
const handleDeleteFile = useCallback(() => {
if (item.Id) {
- deleteFile(item.Id, item.Type);
+ deleteFile(item.Id);
}
}, [deleteFile, item.Id]);
diff --git a/components/jellyseerr/ParallaxSlideShow.tsx b/components/jellyseerr/ParallaxSlideShow.tsx
index 8e080812..b4b75592 100644
--- a/components/jellyseerr/ParallaxSlideShow.tsx
+++ b/components/jellyseerr/ParallaxSlideShow.tsx
@@ -143,7 +143,6 @@ const ParallaxSlideShow = ({
renderItem={({ item, index }) => renderItem(item, index)}
keyExtractor={keyExtractor}
numColumns={3}
- estimatedItemSize={214}
ItemSeparatorComponent={() => }
/>
diff --git a/components/jellyseerr/discover/Slide.tsx b/components/jellyseerr/discover/Slide.tsx
index 7352fb6c..41b4667e 100644
--- a/components/jellyseerr/discover/Slide.tsx
+++ b/components/jellyseerr/discover/Slide.tsx
@@ -1,16 +1,15 @@
import { FlashList } from "@shopify/flash-list";
-import type { ContentStyle } from "@shopify/flash-list/src/FlashListProps";
import { t } from "i18next";
import type React from "react";
import type { PropsWithChildren } from "react";
-import { View, type ViewProps } from "react-native";
+import { View, type ViewProps, type ViewStyle } from "react-native";
import { Text } from "@/components/common/Text";
import { DiscoverSliderType } from "@/utils/jellyseerr/server/constants/discover";
import type DiscoverSlider from "@/utils/jellyseerr/server/entity/DiscoverSlider";
export interface SlideProps {
slide: DiscoverSlider;
- contentContainerStyle?: ContentStyle;
+ contentContainerStyle?: ViewStyle;
}
interface Props extends SlideProps {
@@ -45,7 +44,6 @@ const Slide = ({
}}
showsHorizontalScrollIndicator={false}
keyExtractor={keyExtractor}
- estimatedItemSize={250}
data={data}
onEndReachedThreshold={1}
onEndReached={onEndReached}
diff --git a/components/search/SearchItemWrapper.tsx b/components/search/SearchItemWrapper.tsx
index 2451d3aa..3c266084 100644
--- a/components/search/SearchItemWrapper.tsx
+++ b/components/search/SearchItemWrapper.tsx
@@ -34,7 +34,6 @@ export const SearchItemWrapper = ({
}}
showsHorizontalScrollIndicator={false}
keyExtractor={(_, index) => index.toString()}
- estimatedItemSize={250}
data={items}
onEndReachedThreshold={1}
onEndReached={onEndReached}
diff --git a/components/series/JellyseerrSeasons.tsx b/components/series/JellyseerrSeasons.tsx
index e339e84b..3b130683 100644
--- a/components/series/JellyseerrSeasons.tsx
+++ b/components/series/JellyseerrSeasons.tsx
@@ -47,7 +47,6 @@ const JellyseerrSeasonEpisodes: React.FC<{
horizontal
loading={isLoading}
showsHorizontalScrollIndicator={false}
- estimatedItemSize={50}
data={seasonWithEpisodes?.episodes}
keyExtractor={(item) => item.id.toString()}
renderItem={(item, index) => (
@@ -284,7 +283,6 @@ const JellyseerrSeasons: React.FC<{
)}
ItemSeparatorComponent={() => }
- estimatedItemSize={250}
renderItem={({ item: season }) => (
<>
= ({ seriesId }) => {
(
diff --git a/components/series/SeasonEpisodesCarousel.tsx b/components/series/SeasonEpisodesCarousel.tsx
index 8e3051ee..cf763b8d 100644
--- a/components/series/SeasonEpisodesCarousel.tsx
+++ b/components/series/SeasonEpisodesCarousel.tsx
@@ -4,7 +4,7 @@ import { useQuery } from "@tanstack/react-query";
import { router } from "expo-router";
import { useAtom } from "jotai";
import { useEffect, useMemo, useRef } from "react";
-import { TouchableOpacity, type ViewProps } from "react-native";
+import { TouchableOpacity, type ViewStyle } from "react-native";
import { useDownload } from "@/providers/DownloadProvider";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import ContinueWatchingPoster from "../ContinueWatchingPoster";
@@ -14,17 +14,20 @@ import {
} from "../common/HorizontalScroll";
import { ItemCardText } from "../ItemCardText";
-interface Props extends ViewProps {
+interface Props {
item?: BaseItemDto | null;
loading?: boolean;
isOffline?: boolean;
+ style?: ViewStyle;
+ containerStyle?: ViewStyle;
}
export const SeasonEpisodesCarousel: React.FC = ({
item,
loading,
isOffline,
- ...props
+ style,
+ containerStyle,
}) => {
const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
@@ -90,6 +93,8 @@ export const SeasonEpisodesCarousel: React.FC = ({
data={episodes}
extraData={item}
loading={loading || isPending}
+ style={style}
+ containerStyle={containerStyle}
renderItem={(_item, _idx) => (
= ({
)}
- {...props}
/>
);
};
diff --git a/components/video-player/controls/EpisodeList.tsx b/components/video-player/controls/EpisodeList.tsx
index 0a90b429..a7d6c5bc 100644
--- a/components/video-player/controls/EpisodeList.tsx
+++ b/components/video-player/controls/EpisodeList.tsx
@@ -271,7 +271,6 @@ export const EpisodeList: React.FC = ({ item, close, goToItem }) => {
)}
keyExtractor={(e: BaseItemDto) => e.Id ?? ""}
- estimatedItemSize={200}
showsHorizontalScrollIndicator={false}
/>
)}
diff --git a/components/video-player/controls/SliderScrubbter.tsx b/components/video-player/controls/SliderScrubbter.tsx
index 7de3c7d5..f0a39a12 100644
--- a/components/video-player/controls/SliderScrubbter.tsx
+++ b/components/video-player/controls/SliderScrubbter.tsx
@@ -89,10 +89,10 @@ const SliderScrubber: React.FC = ({
= ({
;
+ enqueueDownload(url: string, destinationPath?: string): Promise;
cancelDownload(taskId: number): void;
+ cancelQueuedDownload(url: string): void;
cancelAllDownloads(): void;
getActiveDownloads(): Promise;
addListener(
diff --git a/package.json b/package.json
index b627cfdc..2d3d21cd 100644
--- a/package.json
+++ b/package.json
@@ -104,7 +104,7 @@
},
"devDependencies": {
"@babel/core": "^7.20.0",
- "@biomejs/biome": "^2.2.4",
+ "@biomejs/biome": "^2.3.5",
"@react-native-community/cli": "^20.0.0",
"@react-native-tvos/config-tv": "^0.1.1",
"@types/jest": "^29.5.12",
diff --git a/providers/DownloadProvider.tsx b/providers/DownloadProvider.tsx
index 4610c30b..98080767 100644
--- a/providers/DownloadProvider.tsx
+++ b/providers/DownloadProvider.tsx
@@ -8,6 +8,7 @@ import {
getAllDownloadedItems,
getDownloadedItemById,
getDownloadsDatabase,
+ updateDownloadedItem,
} from "./Downloads/database";
import { getDownloadedItemSize } from "./Downloads/fileOperations";
import { useDownloadEventHandlers } from "./Downloads/hooks/useDownloadEventHandlers";
@@ -29,7 +30,7 @@ function useDownloadProvider() {
const successHapticFeedback = useHaptic("success");
// Track task ID to process ID mapping
- const taskMapRef = useRef