chore: Apply linting rules and add git hok (#611)

Co-authored-by: Fredrik Burmester <fredrik.burmester@gmail.com>
This commit is contained in:
lostb1t
2025-03-16 18:01:12 +01:00
committed by GitHub
parent 2688e1b981
commit 92513e234f
268 changed files with 9197 additions and 8394 deletions

View File

@@ -1,10 +1,13 @@
import React, { useEffect, useRef } from "react";
import { View, StyleSheet, Platform } from "react-native";
import { useSharedValue } from "react-native-reanimated";
import type React from "react";
import { useEffect, useRef } from "react";
import { Platform, StyleSheet, View } from "react-native";
import { Slider } from "react-native-awesome-slider";
const VolumeManager = Platform.isTV ? null : require("react-native-volume-manager");
import { useSharedValue } from "react-native-reanimated";
const VolumeManager = Platform.isTV
? null
: require("react-native-volume-manager");
import { Ionicons } from "@expo/vector-icons";
import { VolumeResult } from "react-native-volume-manager";
import type { VolumeResult } from "react-native-volume-manager";
interface AudioSliderProps {
setVisibility: (show: boolean) => void;
@@ -50,20 +53,22 @@ const AudioSlider: React.FC<AudioSliderProps> = ({ setVisibility }) => {
};
useEffect(() => {
const volumeListener = VolumeManager.addVolumeListener((result: VolumeResult) => {
volume.value = result.volume * 100;
setVisibility(true);
const volumeListener = VolumeManager.addVolumeListener(
(result: VolumeResult) => {
volume.value = result.volume * 100;
setVisibility(true);
// Clear any existing timeout
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
// Clear any existing timeout
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
// Set a new timeout to hide the visibility after 2 seconds
timeoutRef.current = setTimeout(() => {
setVisibility(false);
}, 1000);
});
// Set a new timeout to hide the visibility after 2 seconds
timeoutRef.current = setTimeout(() => {
setVisibility(false);
}, 1000);
},
);
return () => {
volumeListener.remove();
@@ -92,9 +97,9 @@ const AudioSlider: React.FC<AudioSliderProps> = ({ setVisibility }) => {
}}
/>
<Ionicons
name="volume-high"
name='volume-high'
size={20}
color="#FDFDFD"
color='#FDFDFD'
style={{
marginLeft: 8,
}}

View File

@@ -1,7 +1,7 @@
import React, { useEffect } from "react";
import { View, StyleSheet, Platform } from "react-native";
import { useSharedValue } from "react-native-reanimated";
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";
@@ -46,9 +46,9 @@ const BrightnessSlider = () => {
}}
/>
<Ionicons
name="sunny"
name='sunny'
size={20}
color="#FDFDFD"
color='#FDFDFD'
style={{
marginLeft: 8,
}}

View File

@@ -1,20 +1,20 @@
import {
HorizontalScroll,
HorizontalScrollRef,
} from "@/components/common/HorrizontalScroll";
import { Text } from "@/components/common/Text";
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,
SeasonIndexState,
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 { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { getTvShowsApi } from "@jellyfin/sdk/lib/utils/api";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { atom, useAtom } from "jotai";
@@ -59,7 +59,7 @@ export const EpisodeList: React.FC<Props> = ({ item, close, goToItem }) => {
getUserItemData({ api, userId: user?.Id, itemId: item.SeriesId }).then(
(res) => {
setSeriesItem(res);
}
},
);
}
}, [item.SeriesId]);
@@ -80,7 +80,7 @@ export const EpisodeList: React.FC<Props> = ({ item, close, goToItem }) => {
headers: {
Authorization: `MediaBrowser DeviceId="${api.deviceInfo.id}", Token="${api.accessToken}"`,
},
}
},
);
return response.data.Items;
},
@@ -90,7 +90,7 @@ export const EpisodeList: React.FC<Props> = ({ item, close, goToItem }) => {
const selectedSeasonId: string | null = useMemo(
() =>
seasons?.find((season: any) => season.IndexNumber === seasonIndex)?.Id,
[seasons, seasonIndex]
[seasons, seasonIndex],
);
const { data: episodes, isFetching } = useQuery({
@@ -123,7 +123,7 @@ export const EpisodeList: React.FC<Props> = ({ item, close, goToItem }) => {
const queryClient = useQueryClient();
useEffect(() => {
for (let e of episodes || []) {
for (const e of episodes || []) {
queryClient.prefetchQuery({
queryKey: ["item", e.Id],
queryFn: async () => {
@@ -187,9 +187,9 @@ export const EpisodeList: React.FC<Props> = ({ item, close, goToItem }) => {
onPress={async () => {
close();
}}
className="aspect-square flex flex-col bg-neutral-800/90 rounded-xl items-center justify-center p-2"
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" />
<Ionicons name='close' size={24} color='white' />
</TouchableOpacity>
</View>
@@ -216,7 +216,7 @@ export const EpisodeList: React.FC<Props> = ({ item, close, goToItem }) => {
showPlayButton={_item.Id !== item.Id}
/>
</TouchableOpacity>
<View className="shrink">
<View className='shrink'>
<Text
numberOfLines={2}
style={{
@@ -226,19 +226,19 @@ export const EpisodeList: React.FC<Props> = ({ item, close, goToItem }) => {
>
{_item.Name}
</Text>
<Text numberOfLines={1} className="text-xs text-neutral-475">
<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">
<Text className='text-xs text-neutral-500'>
{runtimeTicksToSeconds(_item.RunTimeTicks)}
</Text>
</View>
<View className="self-start mt-2">
<View className='self-start mt-2'>
<DownloadSingleItem item={_item} />
</View>
<Text
numberOfLines={5}
className="text-xs text-neutral-500 shrink"
className='text-xs text-neutral-500 shrink'
>
{_item.Overview}
</Text>

View File

@@ -1,6 +1,13 @@
import React, { useEffect } from "react";
import { TouchableOpacity, TouchableOpacityProps, View } from "react-native";
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";
import {
TouchableOpacity,
type TouchableOpacityProps,
View,
} from "react-native";
import Animated, {
useAnimatedStyle,
useSharedValue,
@@ -8,8 +15,6 @@ import Animated, {
Easing,
runOnJS,
} from "react-native-reanimated";
import { Colors } from "@/constants/Colors";
import { useTranslation } from "react-i18next";
interface NextEpisodeCountDownButtonProps extends TouchableOpacityProps {
onFinish?: () => void;
@@ -38,7 +43,7 @@ const NextEpisodeCountDownButton: React.FC<NextEpisodeCountDownButtonProps> = ({
if (finished && onFinish) {
runOnJS(onFinish)();
}
}
},
);
}
}, [show, onFinish]);
@@ -68,13 +73,15 @@ const NextEpisodeCountDownButton: React.FC<NextEpisodeCountDownButtonProps> = ({
return (
<TouchableOpacity
className="w-32 overflow-hidden rounded-md bg-black/60 border border-neutral-900"
className='w-32 overflow-hidden rounded-md bg-black/60 border border-neutral-900'
{...props}
onPress={handlePress}
>
<Animated.View style={animatedStyle} />
<View className="px-3 py-3">
<Text className="text-center font-bold">{t("player.next_episode")}</Text>
<View className='px-3 py-3'>
<Text className='text-center font-bold'>
{t("player.next_episode")}
</Text>
</View>
</TouchableOpacity>
);

View File

@@ -1,5 +1,5 @@
import React from "react";
import { View, TouchableOpacity, Text, ViewProps } from "react-native";
import type React from "react";
import { Text, TouchableOpacity, View, type ViewProps } from "react-native";
interface SkipButtonProps extends ViewProps {
onPress: () => void;
@@ -17,9 +17,9 @@ const SkipButton: React.FC<SkipButtonProps> = ({
<View className={showButton ? "flex" : "hidden"} {...props}>
<TouchableOpacity
onPress={onPress}
className="bg-black/60 rounded-md px-3 py-3 border border-neutral-900"
className='bg-black/60 rounded-md px-3 py-3 border border-neutral-900'
>
<Text className="text-white font-bold">{buttonText}</Text>
<Text className='text-white font-bold'>{buttonText}</Text>
</TouchableOpacity>
</View>
);

View File

@@ -1,11 +1,12 @@
import { useTrickplay } from '@/hooks/useTrickplay';
import { formatTimeString, msToTicks, ticksToSeconds } from '@/utils/time';
import React, { useRef, useState } from 'react';
import { View, Text } from 'react-native';
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 { Text, View } from "react-native";
import { Slider } from "react-native-awesome-slider";
import { SharedValue, useSharedValue } from 'react-native-reanimated';
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
import { type SharedValue, useSharedValue } from "react-native-reanimated";
interface SliderScrubberProps {
cacheProgress: SharedValue<number>;
@@ -30,12 +31,9 @@ const SliderScrubber: React.FC<SliderScrubberProps> = ({
remainingTime,
item,
}) => {
const [time, setTime] = useState({ hours: 0, minutes: 0, seconds: 0 });
const { trickPlayUrl, calculateTrickplayUrl, trickplayInfo } = useTrickplay(
item,
);
const { trickPlayUrl, calculateTrickplayUrl, trickplayInfo } =
useTrickplay(item);
const handleSliderChange = (value: number) => {
const progressInTicks = msToTicks(value);
@@ -86,7 +84,7 @@ const SliderScrubber: React.FC<SliderScrubberProps> = ({
marginTop: -tileHeight / 4 - 60,
zIndex: 10,
}}
className=" bg-neutral-800 overflow-hidden"
className=' bg-neutral-800 overflow-hidden'
>
<Image
cachePolicy={"memory-disk"}
@@ -101,7 +99,7 @@ const SliderScrubber: React.FC<SliderScrubberProps> = ({
],
}}
source={{ uri: url }}
contentFit="cover"
contentFit='cover'
/>
<Text
style={{
@@ -116,9 +114,7 @@ const SliderScrubber: React.FC<SliderScrubberProps> = ({
>
{`${time.hours > 0 ? `${time.hours}:` : ""}${
time.minutes < 10 ? `0${time.minutes}` : time.minutes
}:${
time.seconds < 10 ? `0${time.seconds}` : time.seconds
}`}
}:${time.seconds < 10 ? `0${time.seconds}` : time.seconds}`}
</Text>
</View>
);
@@ -129,11 +125,11 @@ const SliderScrubber: React.FC<SliderScrubberProps> = ({
minimumValue={min}
maximumValue={max}
/>
<View className="flex flex-row items-center justify-between mt-0.5">
<Text className="text-[12px] text-neutral-400">
<View className='flex flex-row items-center justify-between mt-0.5'>
<Text className='text-[12px] text-neutral-400'>
{formatTimeString(currentTime, "ms")}
</Text>
<Text className="text-[12px] text-neutral-400">
<Text className='text-[12px] text-neutral-400'>
-{formatTimeString(remainingTime, "ms")}
</Text>
</View>
@@ -141,4 +137,4 @@ const SliderScrubber: React.FC<SliderScrubberProps> = ({
);
};
export default SliderScrubber;
export default SliderScrubber;

View File

@@ -1,8 +1,9 @@
import {
import type {
BaseItemDto,
MediaSourceInfo,
} from "@jellyfin/sdk/lib/generated-client";
import React, { createContext, useContext, useState, ReactNode } from "react";
import type React from "react";
import { type ReactNode, createContext, useContext, useState } from "react";
interface ControlContextProps {
item: BaseItemDto;
@@ -11,7 +12,7 @@ interface ControlContextProps {
}
const ControlContext = createContext<ControlContextProps | undefined>(
undefined
undefined,
);
interface ControlProviderProps {

View File

@@ -1,8 +1,16 @@
import { TrackInfo } from "@/modules/VlcPlayer.types";
import React, { createContext, useContext, useState, ReactNode, useEffect, useMemo } from "react";
import { useControlContext } from "./ControlContext";
import { Track } from "../types";
import type { TrackInfo } from "@/modules/VlcPlayer.types";
import { router, useLocalSearchParams } from "expo-router";
import type React from "react";
import {
type ReactNode,
createContext,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import type { Track } from "../types";
import { useControlContext } from "./ControlContext";
interface VideoContextProps {
audioTracks: Track[] | null;
@@ -16,8 +24,14 @@ const VideoContext = createContext<VideoContextProps | undefined>(undefined);
interface VideoProviderProps {
children: ReactNode;
getAudioTracks: (() => Promise<TrackInfo[] | null>) | (() => TrackInfo[]) | undefined;
getSubtitleTracks: (() => Promise<TrackInfo[] | null>) | (() => TrackInfo[]) | undefined;
getAudioTracks:
| (() => Promise<TrackInfo[] | null>)
| (() => TrackInfo[])
| undefined;
getSubtitleTracks:
| (() => Promise<TrackInfo[] | null>)
| (() => TrackInfo[])
| undefined;
setAudioTrack: ((index: number) => void) | undefined;
setSubtitleTrack: ((index: number) => void) | undefined;
setSubtitleURL: ((url: string, customName: string) => void) | undefined;
@@ -38,20 +52,24 @@ export const VideoProvider: React.FC<VideoProviderProps> = ({
const isVideoLoaded = ControlContext?.isVideoLoaded;
const mediaSource = ControlContext?.mediaSource;
const allSubs = mediaSource?.MediaStreams?.filter((s) => s.Type === "Subtitle") || [];
const allSubs =
mediaSource?.MediaStreams?.filter((s) => s.Type === "Subtitle") || [];
const { itemId, audioIndex, bitrateValue, subtitleIndex } = useLocalSearchParams<{
itemId: string;
audioIndex: string;
subtitleIndex: string;
mediaSourceId: string;
bitrateValue: string;
}>();
const { itemId, audioIndex, bitrateValue, subtitleIndex } =
useLocalSearchParams<{
itemId: string;
audioIndex: string;
subtitleIndex: string;
mediaSourceId: string;
bitrateValue: string;
}>();
const onTextBasedSubtitle = useMemo(
() =>
allSubs.find((s) => s.Index?.toString() === subtitleIndex && s.IsTextSubtitleStream) || subtitleIndex === "-1",
[allSubs, subtitleIndex]
allSubs.find(
(s) => s.Index?.toString() === subtitleIndex && s.IsTextSubtitleStream,
) || subtitleIndex === "-1",
[allSubs, subtitleIndex],
);
const setPlayerParams = ({
@@ -74,14 +92,21 @@ export const VideoProvider: React.FC<VideoProviderProps> = ({
router.replace(`player/direct-player?${queryParams}`);
};
const setTrackParams = (type: "audio" | "subtitle", index: number, serverIndex: number) => {
const setTrackParams = (
type: "audio" | "subtitle",
index: number,
serverIndex: number,
) => {
const setTrack = type === "audio" ? setAudioTrack : setSubtitleTrack;
const paramKey = type === "audio" ? "audioIndex" : "subtitleIndex";
// If we're transcoding and we're going from a image based subtitle
// to a text based subtitle, we need to change the player params.
const shouldChangePlayerParams = type === "subtitle" && mediaSource?.TranscodingUrl && !onTextBasedSubtitle;
const shouldChangePlayerParams =
type === "subtitle" &&
mediaSource?.TranscodingUrl &&
!onTextBasedSubtitle;
console.log("Set player params", index, serverIndex);
if (shouldChangePlayerParams) {
@@ -102,16 +127,19 @@ export const VideoProvider: React.FC<VideoProviderProps> = ({
const subtitleData = await getSubtitleTracks();
// Step 1: Move external subs to the end, because VLC puts external subs at the end
const sortedSubs = allSubs.sort((a, b) => Number(a.IsExternal) - Number(b.IsExternal));
const sortedSubs = allSubs.sort(
(a, b) => Number(a.IsExternal) - Number(b.IsExternal),
);
// Step 2: Apply VLC indexing logic
let textSubIndex = 0;
const processedSubs: Track[] = sortedSubs?.map((sub) => {
// Always increment for non-transcoding subtitles
// Only increment for text-based subtitles when transcoding
const shouldIncrement = !mediaSource?.TranscodingUrl || sub.IsTextSubtitleStream;
const shouldIncrement =
!mediaSource?.TranscodingUrl || sub.IsTextSubtitleStream;
const vlcIndex = subtitleData?.at(textSubIndex)?.index ?? -1;
const finalIndex = shouldIncrement ? vlcIndex : sub.Index ?? -1;
const finalIndex = shouldIncrement ? vlcIndex : (sub.Index ?? -1);
if (shouldIncrement) textSubIndex++;
return {
@@ -127,7 +155,9 @@ export const VideoProvider: React.FC<VideoProviderProps> = ({
});
// Step 3: Restore the original order
const subtitles: Track[] = processedSubs.sort((a, b) => a.index - b.index);
const subtitles: Track[] = processedSubs.sort(
(a, b) => a.index - b.index,
);
// Add a "Disable Subtitles" option
subtitles.unshift({
@@ -143,20 +173,23 @@ export const VideoProvider: React.FC<VideoProviderProps> = ({
if (getAudioTracks) {
const audioData = await getAudioTracks();
const allAudio = mediaSource?.MediaStreams?.filter((s) => s.Type === "Audio") || [];
const allAudio =
mediaSource?.MediaStreams?.filter((s) => s.Type === "Audio") || [];
const audioTracks: Track[] = allAudio?.map((audio, idx) => {
if (!mediaSource?.TranscodingUrl) {
const vlcIndex = audioData?.at(idx)?.index ?? -1;
return {
name: audio.DisplayTitle ?? "Undefined Audio",
index: audio.Index ?? -1,
setTrack: () => setTrackParams("audio", vlcIndex, audio.Index ?? -1),
setTrack: () =>
setTrackParams("audio", vlcIndex, audio.Index ?? -1),
};
}
return {
name: audio.DisplayTitle ?? "Undefined Audio",
index: audio.Index ?? -1,
setTrack: () => setPlayerParams({ chosenAudioIndex: audio.Index?.toString() }),
setTrack: () =>
setPlayerParams({ chosenAudioIndex: audio.Index?.toString() }),
};
});
setAudioTracks(audioTracks);

View File

@@ -1,17 +1,20 @@
import React, { useCallback } from "react";
import { TouchableOpacity, Platform } from "react-native";
import { Ionicons } from "@expo/vector-icons";
import React, { useCallback } from "react";
import { Platform, TouchableOpacity } from "react-native";
const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null;
import { useVideoContext } from "../contexts/VideoContext";
import { useLocalSearchParams, useRouter } from "expo-router";
import { BITRATES } from "@/components/BitrateSelector";
import { useLocalSearchParams, useRouter } from "expo-router";
import { useControlContext } from "../contexts/ControlContext";
import { useVideoContext } from "../contexts/VideoContext";
const DropdownView = () => {
const videoContext = useVideoContext();
const { subtitleTracks, audioTracks } = videoContext;
const ControlContext = useControlContext();
const [item, mediaSource] = [ControlContext?.item, ControlContext?.mediaSource];
const [item, mediaSource] = [
ControlContext?.item,
ControlContext?.mediaSource,
];
const router = useRouter();
const { subtitleIndex, audioIndex, bitrateValue } = useLocalSearchParams<{
@@ -34,27 +37,29 @@ const DropdownView = () => {
// @ts-expect-error
router.replace(`player/direct-player?${queryParams}`);
},
[item, mediaSource, subtitleIndex, audioIndex]
[item, mediaSource, subtitleIndex, audioIndex],
);
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<TouchableOpacity className="aspect-square flex flex-col rounded-xl items-center justify-center p-2">
<Ionicons name="ellipsis-horizontal" size={24} color={"white"} />
<TouchableOpacity className='aspect-square flex flex-col rounded-xl items-center justify-center p-2'>
<Ionicons name='ellipsis-horizontal' size={24} color={"white"} />
</TouchableOpacity>
</DropdownMenu.Trigger>
<DropdownMenu.Content
loop={true}
side="bottom"
align="start"
side='bottom'
align='start'
alignOffset={0}
avoidCollisions={true}
collisionPadding={8}
sideOffset={8}
>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger key="qualitytrigger">Quality</DropdownMenu.SubTrigger>
<DropdownMenu.SubTrigger key='qualitytrigger'>
Quality
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent
alignOffset={-10}
avoidCollisions={true}
@@ -66,15 +71,21 @@ const DropdownView = () => {
<DropdownMenu.CheckboxItem
key={`quality-item-${idx}`}
value={bitrateValue === (bitrate.value?.toString() ?? "")}
onValueChange={() => changeBitrate(bitrate.value?.toString() ?? "")}
onValueChange={() =>
changeBitrate(bitrate.value?.toString() ?? "")
}
>
<DropdownMenu.ItemTitle key={`audio-item-title-${idx}`}>{bitrate.key}</DropdownMenu.ItemTitle>
<DropdownMenu.ItemTitle key={`audio-item-title-${idx}`}>
{bitrate.key}
</DropdownMenu.ItemTitle>
</DropdownMenu.CheckboxItem>
))}
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger key="subtitle-trigger">Subtitle</DropdownMenu.SubTrigger>
<DropdownMenu.SubTrigger key='subtitle-trigger'>
Subtitle
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent
alignOffset={-10}
avoidCollisions={true}
@@ -88,13 +99,17 @@ const DropdownView = () => {
value={subtitleIndex === sub.index.toString()}
onValueChange={() => sub.setTrack()}
>
<DropdownMenu.ItemTitle key={`subtitle-item-title-${idx}`}>{sub.name}</DropdownMenu.ItemTitle>
<DropdownMenu.ItemTitle key={`subtitle-item-title-${idx}`}>
{sub.name}
</DropdownMenu.ItemTitle>
</DropdownMenu.CheckboxItem>
))}
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger key="audio-trigger">Audio</DropdownMenu.SubTrigger>
<DropdownMenu.SubTrigger key='audio-trigger'>
Audio
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent
alignOffset={-10}
avoidCollisions={true}
@@ -108,7 +123,9 @@ const DropdownView = () => {
value={audioIndex === track.index.toString()}
onValueChange={() => track.setTrack()}
>
<DropdownMenu.ItemTitle key={`audio-item-title-${idx}`}>{track.name}</DropdownMenu.ItemTitle>
<DropdownMenu.ItemTitle key={`audio-item-title-${idx}`}>
{track.name}
</DropdownMenu.ItemTitle>
</DropdownMenu.CheckboxItem>
))}
</DropdownMenu.SubContent>

View File

@@ -23,4 +23,4 @@ type Track = {
setTrack: () => void;
};
export { EmbeddedSubtitle, ExternalSubtitle, TranscodedSubtitle, Track };
export type { EmbeddedSubtitle, ExternalSubtitle, TranscodedSubtitle, Track };

View File

@@ -1,5 +1,5 @@
import { useRef } from "react";
import { GestureResponderEvent } from "react-native";
import type { GestureResponderEvent } from "react-native";
interface TapDetectionOptions {
maxDuration?: number;
@@ -33,7 +33,7 @@ export const useTapDetection = ({
const touchDuration = touchEndTime - touchStartTime.current;
const touchDistance = Math.sqrt(
Math.pow(touchEndPosition.x - touchStartPosition.current.x, 2) +
Math.pow(touchEndPosition.y - touchStartPosition.current.y, 2)
Math.pow(touchEndPosition.y - touchStartPosition.current.y, 2),
);
if (touchDuration < maxDuration && touchDistance < maxDistance) {