fix: bump biome and fix error (#864)
Some checks failed
🤖 Android APK Build / 🏗️ Build Android APK (push) Has been cancelled
🤖 iOS IPA Build / 🏗️ Build iOS IPA (push) Has been cancelled
🔒 Lockfile Consistency Check / 🔍 Check bun.lock and package.json consistency (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (javascript-typescript) (push) Has been cancelled
🏷️🔀Merge Conflict Labeler / 🏷️ Labeling Merge Conflicts (push) Has been cancelled
🕒 Handle Stale Issues / 🗑️ Cleanup Stale Issues (push) Has been cancelled

This commit is contained in:
Gauvain
2025-07-21 09:44:24 +02:00
committed by GitHub
parent 3b2a6bd40a
commit 5f39622ad6
202 changed files with 1858 additions and 1954 deletions

View File

@@ -3,9 +3,11 @@ import { useEffect, useRef } from "react";
import { Platform, StyleSheet, View } from "react-native";
import { Slider } from "react-native-awesome-slider";
import { useSharedValue } from "react-native-reanimated";
const VolumeManager = Platform.isTV
? null
: require("react-native-volume-manager");
import { Ionicons } from "@expo/vector-icons";
import type { VolumeResult } from "react-native-volume-manager";
@@ -14,9 +16,7 @@ interface AudioSliderProps {
}
const AudioSlider: React.FC<AudioSliderProps> = ({ setVisibility }) => {
if (Platform.isTV) {
return;
}
const isTv = Platform.isTV;
const volume = useSharedValue<number>(50); // Explicitly type as number
const min = useSharedValue<number>(0); // Explicitly type as number
@@ -25,6 +25,7 @@ const AudioSlider: React.FC<AudioSliderProps> = ({ setVisibility }) => {
const timeoutRef = useRef<number | null>(null); // Use a ref to store the timeout ID
useEffect(() => {
if (isTv) return;
const fetchInitialVolume = async () => {
try {
const { volume: initialVolume } = await VolumeManager.getVolume();
@@ -42,7 +43,7 @@ const AudioSlider: React.FC<AudioSliderProps> = ({ setVisibility }) => {
// Re-enable the native volume UI when the component unmounts
VolumeManager.showNativeVolumeUI({ enabled: true });
};
}, []);
}, [isTv, volume]);
const handleValueChange = async (value: number) => {
volume.value = value;
@@ -53,6 +54,7 @@ const AudioSlider: React.FC<AudioSliderProps> = ({ setVisibility }) => {
};
useEffect(() => {
if (isTv) return;
const volumeListener = VolumeManager.addVolumeListener(
(result: VolumeResult) => {
volume.value = result.volume * 100;
@@ -76,7 +78,9 @@ const AudioSlider: React.FC<AudioSliderProps> = ({ setVisibility }) => {
clearTimeout(timeoutRef.current);
}
};
}, [volume]);
}, [isTv, volume, setVisibility]);
if (isTv) return;
return (
<View style={styles.sliderContainer}>

View File

@@ -1,32 +1,36 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { Platform, StyleSheet, View } from "react-native";
import { Slider } from "react-native-awesome-slider";
import { useSharedValue } from "react-native-reanimated";
// import * as Brightness from "expo-brightness";
const Brightness = !Platform.isTV ? require("expo-brightness") : null;
import { Ionicons } from "@expo/vector-icons";
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
const BrightnessSlider = () => {
if (Platform.isTV) return;
const isTv = Platform.isTV;
const brightness = useSharedValue(50);
const min = useSharedValue(0);
const max = useSharedValue(100);
useEffect(() => {
if (isTv) return;
const fetchInitialBrightness = async () => {
const initialBrightness = await Brightness.getBrightnessAsync();
brightness.value = initialBrightness * 100;
};
fetchInitialBrightness();
}, []);
}, [brightness, isTv]);
const handleValueChange = async (value: number) => {
brightness.value = value;
await Brightness.setBrightnessAsync(value / 100);
};
if (isTv) return;
return (
<View style={styles.sliderContainer}>
<Slider

View File

@@ -1,10 +1,10 @@
import { Button } from "@/components/Button";
import { Text } from "@/components/common/Text";
import { useSettings } from "@/utils/atoms/settings";
import { useRouter } from "expo-router";
import { t } from "i18next";
import React from "react";
import { View } from "react-native";
import { Button } from "@/components/Button";
import { Text } from "@/components/common/Text";
import { useSettings } from "@/utils/atoms/settings";
export interface ContinueWatchingOverlayProps {
goToNextItem: (options: {

View File

@@ -1,25 +1,3 @@
import { Loader } from "@/components/Loader";
import { Text } from "@/components/common/Text";
import ContinueWatchingOverlay from "@/components/video-player/controls/ContinueWatchingOverlay";
import { useAdjacentItems } from "@/hooks/useAdjacentEpisodes";
import { useCreditSkipper } from "@/hooks/useCreditSkipper";
import { useHaptic } from "@/hooks/useHaptic";
import { useIntroSkipper } from "@/hooks/useIntroSkipper";
import { useTrickplay } from "@/hooks/useTrickplay";
import type { TrackInfo, VlcPlayerViewRef } from "@/modules/VlcPlayer.types";
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
import { apiAtom } from "@/providers/JellyfinProvider";
import { VideoPlayer, useSettings } from "@/utils/atoms/settings";
import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings";
import { getItemById } from "@/utils/jellyfin/user-library/getItemById";
import { writeToLog } from "@/utils/log";
import {
formatTimeString,
msToTicks,
secondsToMs,
ticksToMs,
ticksToSeconds,
} from "@/utils/time";
import { Ionicons, MaterialIcons } from "@expo/vector-icons";
import type {
BaseItemDto,
@@ -29,7 +7,7 @@ import { Image } from "expo-image";
import { useLocalSearchParams, useRouter } from "expo-router";
import { useAtom } from "jotai";
import { debounce } from "lodash";
import React, {
import {
type Dispatch,
type FC,
type MutableRefObject,
@@ -42,27 +20,48 @@ import React, {
import {
Platform,
TouchableOpacity,
View,
useWindowDimensions,
View,
} from "react-native";
import { Slider } from "react-native-awesome-slider";
import {
type SharedValue,
runOnJS,
type SharedValue,
useAnimatedReaction,
useSharedValue,
} from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Text } from "@/components/common/Text";
import { Loader } from "@/components/Loader";
import ContinueWatchingOverlay from "@/components/video-player/controls/ContinueWatchingOverlay";
import { useAdjacentItems } from "@/hooks/useAdjacentEpisodes";
import { useCreditSkipper } from "@/hooks/useCreditSkipper";
import { useHaptic } from "@/hooks/useHaptic";
import { useIntroSkipper } from "@/hooks/useIntroSkipper";
import { useTrickplay } from "@/hooks/useTrickplay";
import type { TrackInfo, VlcPlayerViewRef } from "@/modules/VlcPlayer.types";
import { apiAtom } from "@/providers/JellyfinProvider";
import { useSettings, VideoPlayer } from "@/utils/atoms/settings";
import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings";
import { getItemById } from "@/utils/jellyfin/user-library/getItemById";
import { writeToLog } from "@/utils/log";
import {
formatTimeString,
msToTicks,
secondsToMs,
ticksToMs,
ticksToSeconds,
} from "@/utils/time";
import AudioSlider from "./AudioSlider";
import BrightnessSlider from "./BrightnessSlider";
import { EpisodeList } from "./EpisodeList";
import NextEpisodeCountDownButton from "./NextEpisodeCountDownButton";
import SkipButton from "./SkipButton";
import { VideoTouchOverlay } from "./VideoTouchOverlay";
import { ControlProvider } from "./contexts/ControlContext";
import { VideoProvider } from "./contexts/VideoContext";
import DropdownView from "./dropdown/DropdownView";
import { EpisodeList } from "./EpisodeList";
import NextEpisodeCountDownButton from "./NextEpisodeCountDownButton";
import SkipButton from "./SkipButton";
import { useControlsTimeout } from "./useControlsTimeout";
import { VideoTouchOverlay } from "./VideoTouchOverlay";
interface Props {
item: BaseItemDto;
@@ -241,7 +240,10 @@ export const Controls: FC<Props> = ({
({
isAutoPlay,
resetWatchCount,
}: { isAutoPlay?: boolean; resetWatchCount?: boolean }) => {
}: {
isAutoPlay?: boolean;
resetWatchCount?: boolean;
}) => {
if (!nextItem) {
return;
}

View File

@@ -1,18 +1,3 @@
import ContinueWatchingPoster from "@/components/ContinueWatchingPoster";
import { DownloadSingleItem } from "@/components/DownloadItem";
import { Loader } from "@/components/Loader";
import {
HorizontalScroll,
type HorizontalScrollRef,
} from "@/components/common/HorrizontalScroll";
import { Text } from "@/components/common/Text";
import {
SeasonDropdown,
type SeasonIndexState,
} from "@/components/series/SeasonDropdown";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
import { runtimeTicksToSeconds } from "@/utils/time";
import { Ionicons } from "@expo/vector-icons";
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { getTvShowsApi } from "@jellyfin/sdk/lib/utils/api";
@@ -21,6 +6,21 @@ import { atom, useAtom } from "jotai";
import { useEffect, useMemo, useRef, useState } from "react";
import { TouchableOpacity, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import ContinueWatchingPoster from "@/components/ContinueWatchingPoster";
import {
HorizontalScroll,
type HorizontalScrollRef,
} from "@/components/common/HorrizontalScroll";
import { Text } from "@/components/common/Text";
import { DownloadSingleItem } from "@/components/DownloadItem";
import { Loader } from "@/components/Loader";
import {
SeasonDropdown,
type SeasonIndexState,
} from "@/components/series/SeasonDropdown";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
import { runtimeTicksToSeconds } from "@/utils/time";
type Props = {
item: BaseItemDto;
@@ -33,7 +33,7 @@ export const seasonIndexAtom = atom<SeasonIndexState>({});
export const EpisodeList: React.FC<Props> = ({ item, close, goToItem }) => {
const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
const insets = useSafeAreaInsets(); // Get safe area insets
const _insets = useSafeAreaInsets(); // Get safe area insets
const [seasonIndexState, setSeasonIndexState] = useAtom(seasonIndexAtom);
const scrollViewRef = useRef<HorizontalScrollRef>(null); // Reference to the HorizontalScroll
const scrollToIndex = (index: number) => {
@@ -163,92 +163,87 @@ export const EpisodeList: React.FC<Props> = ({ item, close, goToItem }) => {
width: "100%",
}}
>
<>
<View
style={{
justifyContent: "space-between",
}}
className={"flex flex-row items-center space-x-2 z-10 p-4"}
>
{seriesItem && (
<SeasonDropdown
item={seriesItem}
seasons={seasons}
state={seasonIndexState}
onSelect={(season) => {
setSeasonIndexState((prev) => ({
...prev,
[item.SeriesId ?? ""]: season.IndexNumber,
}));
}}
/>
)}
<TouchableOpacity
onPress={async () => {
close();
<View
style={{
justifyContent: "space-between",
}}
className={"flex flex-row items-center space-x-2 z-10 p-4"}
>
{seriesItem && (
<SeasonDropdown
item={seriesItem}
seasons={seasons}
state={seasonIndexState}
onSelect={(season) => {
setSeasonIndexState((prev) => ({
...prev,
[item.SeriesId ?? ""]: season.IndexNumber,
}));
}}
className='aspect-square flex flex-col bg-neutral-800/90 rounded-xl items-center justify-center p-2'
>
<Ionicons name='close' size={24} color='white' />
</TouchableOpacity>
</View>
/>
)}
<TouchableOpacity
onPress={async () => {
close();
}}
className='aspect-square flex flex-col bg-neutral-800/90 rounded-xl items-center justify-center p-2'
>
<Ionicons name='close' size={24} color='white' />
</TouchableOpacity>
</View>
<HorizontalScroll
ref={scrollViewRef}
data={episodes}
extraData={item}
renderItem={(_item, idx) => (
<View
key={_item.Id}
style={{}}
className={`flex flex-col w-44 ${
item.Id !== _item.Id ? "opacity-75" : ""
}`}
<HorizontalScroll
ref={scrollViewRef}
data={episodes}
extraData={item}
renderItem={(_item, _idx) => (
<View
key={_item.Id}
style={{}}
className={`flex flex-col w-44 ${
item.Id !== _item.Id ? "opacity-75" : ""
}`}
>
<TouchableOpacity
onPress={() => {
goToItem(_item.Id);
}}
>
<TouchableOpacity
onPress={() => {
goToItem(_item.Id);
<ContinueWatchingPoster
item={_item}
useEpisodePoster
showPlayButton={_item.Id !== item.Id}
/>
</TouchableOpacity>
<View className='shrink'>
<Text
numberOfLines={2}
style={{
lineHeight: 18, // Adjust this value based on your text size
height: 36, // lineHeight * 2 for consistent two-line space
}}
>
<ContinueWatchingPoster
item={_item}
useEpisodePoster
showPlayButton={_item.Id !== item.Id}
/>
</TouchableOpacity>
<View className='shrink'>
<Text
numberOfLines={2}
style={{
lineHeight: 18, // Adjust this value based on your text size
height: 36, // lineHeight * 2 for consistent two-line space
}}
>
{_item.Name}
</Text>
<Text numberOfLines={1} className='text-xs text-neutral-475'>
{`S${_item.ParentIndexNumber?.toString()}:E${_item.IndexNumber?.toString()}`}
</Text>
<Text className='text-xs text-neutral-500'>
{runtimeTicksToSeconds(_item.RunTimeTicks)}
</Text>
</View>
<View className='self-start mt-2'>
<DownloadSingleItem item={_item} />
</View>
<Text
numberOfLines={5}
className='text-xs text-neutral-500 shrink'
>
{_item.Overview}
{_item.Name}
</Text>
<Text numberOfLines={1} className='text-xs text-neutral-475'>
{`S${_item.ParentIndexNumber?.toString()}:E${_item.IndexNumber?.toString()}`}
</Text>
<Text className='text-xs text-neutral-500'>
{runtimeTicksToSeconds(_item.RunTimeTicks)}
</Text>
</View>
)}
keyExtractor={(e: BaseItemDto) => e.Id ?? ""}
estimatedItemSize={200}
showsHorizontalScrollIndicator={false}
/>
</>
<View className='self-start mt-2'>
<DownloadSingleItem item={_item} />
</View>
<Text numberOfLines={5} className='text-xs text-neutral-500 shrink'>
{_item.Overview}
</Text>
</View>
)}
keyExtractor={(e: BaseItemDto) => e.Id ?? ""}
estimatedItemSize={200}
showsHorizontalScrollIndicator={false}
/>
</View>
);
};

View File

@@ -1,5 +1,3 @@
import { Text } from "@/components/common/Text";
import { Colors } from "@/constants/Colors";
import type React from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
@@ -9,12 +7,14 @@ import {
View,
} from "react-native";
import Animated, {
Easing,
runOnJS,
useAnimatedStyle,
useSharedValue,
withTiming,
Easing,
runOnJS,
} from "react-native-reanimated";
import { Text } from "@/components/common/Text";
import { Colors } from "@/constants/Colors";
interface NextEpisodeCountDownButtonProps extends TouchableOpacityProps {
onFinish?: () => void;

View File

@@ -1,12 +1,12 @@
import { useTrickplay } from "@/hooks/useTrickplay";
import { formatTimeString, msToTicks, ticksToSeconds } from "@/utils/time";
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
import { Image } from "expo-image";
import type React from "react";
import { useRef, useState } from "react";
import { useState } from "react";
import { Text, View } from "react-native";
import { Slider } from "react-native-awesome-slider";
import { type SharedValue, useSharedValue } from "react-native-reanimated";
import { type SharedValue } from "react-native-reanimated";
import { useTrickplay } from "@/hooks/useTrickplay";
import { formatTimeString, msToTicks, ticksToSeconds } from "@/utils/time";
interface SliderScrubberProps {
cacheProgress: SharedValue<number>;

View File

@@ -3,7 +3,7 @@ import type {
MediaSourceInfo,
} from "@jellyfin/sdk/lib/generated-client";
import type React from "react";
import { type ReactNode, createContext, useContext, useState } from "react";
import { createContext, type ReactNode, useContext } from "react";
interface ControlContextProps {
item: BaseItemDto;