fix(typescript): resolve 44 TypeScript errors in core components (#1004)

This commit is contained in:
Gauvain
2025-08-31 16:56:53 +02:00
committed by GitHub
parent 83c4aadbb4
commit df0b569f2d
64 changed files with 162 additions and 129 deletions

View File

@@ -61,7 +61,7 @@ export const DownloadItems: React.FC<DownloadProps> = ({
const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
const [queue, _setQueue] = useAtom(queueAtom);
const [settings] = useSettings();
const [settings] = useSettings(null);
const [downloadUnwatchedOnly, setDownloadUnwatchedOnly] = useState(false);
const { processes, startBackgroundDownload, getDownloadedItems } =

View File

@@ -54,7 +54,7 @@ interface ItemContentProps {
export const ItemContent: React.FC<ItemContentProps> = React.memo(
({ item, isOffline }) => {
const [api] = useAtom(apiAtom);
const [settings] = useSettings();
const [settings] = useSettings(null);
const { orientation } = useOrientation();
const navigation = useNavigation();
const insets = useSafeAreaInsets();

View File

@@ -67,7 +67,7 @@ export const PlayButton: React.FC<Props> = ({
const startColor = useSharedValue(colorAtom);
const widthProgress = useSharedValue(0);
const colorChangeProgress = useSharedValue(0);
const [settings, updateSettings] = useSettings();
const [settings, updateSettings] = useSettings(null);
const lightHapticFeedback = useHaptic("light");
const goToPlayer = useCallback(

View File

@@ -44,7 +44,7 @@ export const PlayButton: React.FC<Props> = ({
const startColor = useSharedValue(colorAtom);
const widthProgress = useSharedValue(0);
const colorChangeProgress = useSharedValue(0);
const [settings] = useSettings();
const [settings] = useSettings(null);
const lightHapticFeedback = useHaptic("light");
const goToPlayer = useCallback(

View File

@@ -1,14 +1,10 @@
import { Ionicons } from "@expo/vector-icons";
import { BlurView } from "expo-blur";
import type { PropsWithChildren } from "react";
import {
Platform,
TouchableOpacity,
type TouchableOpacityProps,
} from "react-native";
import { Platform, TouchableOpacity, type ViewProps } from "react-native";
import { useHaptic } from "@/hooks/useHaptic";
interface Props extends TouchableOpacityProps {
interface Props extends ViewProps {
onPress?: () => void;
icon?: keyof typeof Ionicons.glyphMap;
background?: boolean;
@@ -25,7 +21,7 @@ export const RoundButton: React.FC<PropsWithChildren<Props>> = ({
size = "default",
fillColor,
hapticFeedback = true,
...props
...viewProps
}) => {
const buttonSize = size === "large" ? "h-10 w-10" : "h-9 w-9";
const fillColorClass = fillColor === "primary" ? "bg-purple-600" : "";
@@ -43,7 +39,7 @@ export const RoundButton: React.FC<PropsWithChildren<Props>> = ({
<TouchableOpacity
onPress={handlePress}
className={`rounded-full ${buttonSize} flex items-center justify-center ${fillColorClass}`}
{...props}
{...(viewProps as any)}
>
{icon ? (
<Ionicons
@@ -61,7 +57,7 @@ export const RoundButton: React.FC<PropsWithChildren<Props>> = ({
<TouchableOpacity
onPress={handlePress}
className={`rounded-full ${buttonSize} flex items-center justify-center ${fillColorClass}`}
{...props}
{...(viewProps as any)}
>
{icon ? (
<Ionicons
@@ -81,7 +77,7 @@ export const RoundButton: React.FC<PropsWithChildren<Props>> = ({
className={`rounded-full ${buttonSize} flex items-center justify-center ${
fillColor ? fillColorClass : "bg-neutral-800/80"
}`}
{...props}
{...(viewProps as any)}
>
{icon ? (
<Ionicons
@@ -95,11 +91,11 @@ export const RoundButton: React.FC<PropsWithChildren<Props>> = ({
);
return (
<TouchableOpacity onPress={handlePress} {...props}>
<TouchableOpacity onPress={handlePress} {...(viewProps as any)}>
<BlurView
intensity={90}
className={`rounded-full overflow-hidden ${buttonSize} flex items-center justify-center ${fillColorClass}`}
{...props}
{...(viewProps as any)}
>
{icon ? (
<Ionicons

View File

@@ -39,7 +39,7 @@ export const TouchableJellyseerrRouter: React.FC<PropsWithChildren<Props>> = ({
const segments = useSegments();
const { jellyseerrApi, jellyseerrUser, requestMedia } = useJellyseerr();
const from = segments[2];
const from = segments[2] || "(home)";
const autoApprove = useMemo(() => {
return (
@@ -66,14 +66,14 @@ export const TouchableJellyseerrRouter: React.FC<PropsWithChildren<Props>> = ({
onPress={() => {
if (!result) return;
// @ts-expect-error
router.push({
pathname: `/(auth)/(tabs)/${from}/jellyseerr/page`,
// @ts-expect-error
params: {
...result,
mediaTitle,
releaseYear,
canRequest,
canRequest: canRequest.toString(),
posterSrc,
mediaType,
},

View File

@@ -61,7 +61,7 @@ export const TouchableItemRouter: React.FC<PropsWithChildren<Props>> = ({
const markAsPlayedStatus = useMarkAsPlayed([item]);
const { isFavorite, toggleFavorite } = useFavorite(item);
const from = segments[2];
const from = segments[2] || "(home)";
const showActionSheet = useCallback(() => {
if (

View File

@@ -9,6 +9,7 @@ import {
TouchableOpacity,
type TouchableOpacityProps,
View,
type ViewProps,
} from "react-native";
import { toast } from "sonner-native";
import { Text } from "@/components/common/Text";
@@ -22,7 +23,7 @@ const bytesToMB = (bytes: number) => {
return bytes / 1024 / 1024;
};
interface ActiveDownloadsProps extends TouchableOpacityProps {}
interface ActiveDownloadsProps extends ViewProps {}
export default function ActiveDownloads({ ...props }: ActiveDownloadsProps) {
const { processes } = useDownload();

View File

@@ -7,7 +7,7 @@ import { FilterSheet } from "./FilterSheet";
interface FilterButtonProps<T> extends ViewProps {
id: string;
showSearch?: boolean;
disableSearch?: boolean;
queryKey: string;
values: T[];
title: string;
@@ -28,7 +28,7 @@ export const FilterButton = <T,>({
title,
renderItemLabel,
searchFilter,
showSearch = true,
disableSearch = false,
multiple = false,
icon = "filter",
...props
@@ -94,7 +94,7 @@ export const FilterButton = <T,>({
set={set}
renderItemLabel={renderItemLabel}
searchFilter={searchFilter}
showSearch={showSearch}
disableSearch={disableSearch}
multiple={multiple}
/>
</>

View File

@@ -26,7 +26,7 @@ import { itemRouter } from "../common/TouchableItemRouter";
interface Props extends ViewProps {}
export const LargeMovieCarousel: React.FC<Props> = ({ ...props }) => {
const [settings] = useSettings();
const [settings] = useSettings(null);
const ref = React.useRef<ICarouselInstance>(null);
const progress = useSharedValue<number>(0);
@@ -146,7 +146,7 @@ const RenderItem: React.FC<{ item: BaseItemDto }> = ({ item }) => {
}, [item]);
const segments = useSegments();
const from = segments[2];
const from = segments[2] || "(home)";
const opacity = useSharedValue(1);

View File

@@ -22,7 +22,7 @@ const PersonPoster: React.FC<Props & ViewProps> = ({
const { jellyseerrApi } = useJellyseerr();
const router = useRouter();
const segments = useSegments();
const from = segments[2];
const from = segments[2] || "(home)";
if (from === "(home)" || from === "(search)" || from === "(libraries)")
return (

View File

@@ -16,11 +16,12 @@ const CompanySlide: React.FC<
> = ({ slide, data, ...props }) => {
const segments = useSegments();
const { jellyseerrApi } = useJellyseerr();
const from = segments[2];
const from = segments[2] || "(home)";
const navigate = useCallback(
({ id, image, name }: Network | Studio) =>
router.push({
// @ts-expect-error - Dynamic pathname for jellyseerr routing
pathname: `/(auth)/(tabs)/${from}/jellyseerr/company/${id}`,
params: { id, image, name, type: slide.type },
}),

View File

@@ -23,7 +23,7 @@ const GenericSlideCard: React.FC<
id: string;
url?: string;
title?: string;
colors?: string[];
colors?: readonly [string, string, ...string[]];
contentFit?: ImageContentFit;
} & ViewProps
> = ({

View File

@@ -13,11 +13,12 @@ import { genreColorMap } from "@/utils/jellyseerr/src/components/Discover/consta
const GenreSlide: React.FC<SlideProps & ViewProps> = ({ slide, ...props }) => {
const segments = useSegments();
const { jellyseerrApi } = useJellyseerr();
const from = segments[2];
const from = segments[2] || "(home)";
const navigate = useCallback(
(genre: GenreSliderItem) =>
router.push({
// @ts-expect-error - Dynamic pathname for jellyseerr routing
pathname: `/(auth)/(tabs)/${from}/jellyseerr/genre/${genre.id}`,
params: { type: slide.type, name: genre.name },
}),

View File

@@ -41,7 +41,7 @@ const icons: Record<CollectionType, IconName> = {
export const LibraryItemCard: React.FC<Props> = ({ library, ...props }) => {
const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
const [settings] = useSettings();
const [settings] = useSettings(null);
const { t } = useTranslation();

View File

@@ -1,15 +1,11 @@
import { Ionicons } from "@expo/vector-icons";
import type { PropsWithChildren, ReactNode } from "react";
import {
TouchableOpacity,
type TouchableOpacityProps,
View,
type ViewProps,
} from "react-native";
import { TouchableOpacity, View, type ViewProps } from "react-native";
import { Text } from "../common/Text";
interface Props extends TouchableOpacityProps, ViewProps {
interface Props extends ViewProps {
title?: string | null | undefined;
subtitle?: string | null | undefined;
value?: string | null | undefined;
children?: ReactNode;
iconAfter?: ReactNode;
@@ -17,10 +13,12 @@ interface Props extends TouchableOpacityProps, ViewProps {
showArrow?: boolean;
textColor?: "default" | "blue" | "red";
onPress?: () => void;
disabled?: boolean;
}
export const ListItem: React.FC<PropsWithChildren<Props>> = ({
title,
subtitle,
value,
iconAfter,
children,
@@ -29,7 +27,7 @@ export const ListItem: React.FC<PropsWithChildren<Props>> = ({
textColor = "default",
onPress,
disabled = false,
...props
...viewProps
}) => {
if (onPress)
return (
@@ -39,10 +37,11 @@ export const ListItem: React.FC<PropsWithChildren<Props>> = ({
className={`flex flex-row items-center justify-between bg-neutral-900 h-11 pr-4 pl-4 ${
disabled ? "opacity-50" : ""
}`}
{...props}
{...(viewProps as any)}
>
<ListItemContent
title={title}
subtitle={subtitle}
value={value}
icon={icon}
textColor={textColor}
@@ -58,10 +57,11 @@ export const ListItem: React.FC<PropsWithChildren<Props>> = ({
className={`flex flex-row items-center justify-between bg-neutral-900 h-11 pr-4 pl-4 ${
disabled ? "opacity-50" : ""
}`}
{...props}
{...viewProps}
>
<ListItemContent
title={title}
subtitle={subtitle}
value={value}
icon={icon}
textColor={textColor}
@@ -76,6 +76,7 @@ export const ListItem: React.FC<PropsWithChildren<Props>> = ({
const ListItemContent = ({
title,
subtitle,
textColor,
icon,
value,
@@ -91,18 +92,25 @@ const ListItemContent = ({
<Ionicons name='person-circle-outline' size={18} color='white' />
</View>
)}
<Text
className={
textColor === "blue"
? "text-[#0584FE]"
: textColor === "red"
? "text-red-600"
: "text-white"
}
numberOfLines={1}
>
{title}
</Text>
<View className='flex-1'>
<Text
className={
textColor === "blue"
? "text-[#0584FE]"
: textColor === "red"
? "text-red-600"
: "text-white"
}
numberOfLines={1}
>
{title}
</Text>
{subtitle && (
<Text className='text-[#9899A1] text-sm mt-0.5' numberOfLines={2}>
{subtitle}
</Text>
)}
</View>
{value && (
<View className='ml-auto items-end'>
<Text selectable className=' text-[#9899A1]' numberOfLines={1}>

View File

@@ -7,7 +7,7 @@ import { Text } from "../common/Text";
type SearchItemWrapperProps<T> = {
items?: T[];
renderItem: (item: any) => React.ReactNode;
renderItem: (item: any) => React.ReactElement | null;
header?: string;
onEndReached?: (() => void) | null | undefined;
};
@@ -35,11 +35,9 @@ export const SearchItemWrapper = <T,>({
showsHorizontalScrollIndicator={false}
keyExtractor={(_, index) => index.toString()}
estimatedItemSize={250}
/*@ts-expect-error */
data={items}
onEndReachedThreshold={1}
onEndReached={onEndReached}
//@ts-expect-error
renderItem={({ item }) => (item ? renderItem(item) : null)}
/>
</>

View File

@@ -48,7 +48,7 @@ export const CastAndCrew: React.FC<Props> = ({ item, loading, ...props }) => {
</Text>
<HorizontalScroll
loading={loading}
keyExtractor={(i, _idx) => i.Id.toString()}
keyExtractor={(i, _idx) => i.Id?.toString() || ""}
height={247}
data={destinctPeople}
renderItem={(i) => (
@@ -60,7 +60,7 @@ export const CastAndCrew: React.FC<Props> = ({ item, loading, ...props }) => {
}}
className='flex flex-col w-28'
>
<Poster id={i.id} url={getPrimaryImageUrl({ api, item: i })} />
<Poster id={i.Id} url={getPrimaryImageUrl({ api, item: i })} />
<Text className='mt-2'>{i.Name}</Text>
<Text className='text-xs opacity-50'>{i.Role}</Text>
</TouchableOpacity>

View File

@@ -28,15 +28,17 @@ export const CurrentSeries: React.FC<Props> = ({ item, ...props }) => {
height={247}
renderItem={(item, _index) => (
<TouchableOpacity
key={item.Id}
onPress={() => router.push(`/series/${item.SeriesId}`)}
key={item?.Id}
onPress={() =>
item?.SeriesId && router.push(`/series/${item.SeriesId}`)
}
className='flex flex-col space-y-2 w-28'
>
<Poster
id={item.id}
url={getPrimaryImageUrlById({ api, id: item.ParentId })}
id={item?.Id}
url={getPrimaryImageUrlById({ api, id: item?.ParentId })}
/>
<Text>{item.SeriesName}</Text>
<Text>{item?.SeriesName}</Text>
</TouchableOpacity>
)}
/>

View File

@@ -19,7 +19,6 @@ export const EpisodeTitleHeader: React.FC<Props> = ({ item, ...props }) => {
<TouchableOpacity
onPress={() => {
router.push(
// @ts-expect-error
`/(auth)/series/${item.SeriesId}?seasonIndex=${item?.ParentIndexNumber}`,
);
}}

View File

@@ -49,7 +49,7 @@ const JellyseerrSeasonEpisodes: React.FC<{
showsHorizontalScrollIndicator={false}
estimatedItemSize={50}
data={seasonWithEpisodes?.episodes}
keyExtractor={(item) => item.id}
keyExtractor={(item) => item.id.toString()}
renderItem={(item, index) => (
<RenderItem key={index} item={item} index={index} />
)}

View File

@@ -12,7 +12,7 @@ interface Props extends ViewProps {}
export const AppLanguageSelector: React.FC<Props> = () => {
const isTv = Platform.isTV;
const [settings, updateSettings] = useSettings();
const [settings, updateSettings] = useSettings(null);
const { t } = useTranslation();
if (isTv) return null;

View File

@@ -17,7 +17,7 @@ export const AudioToggles: React.FC<Props> = ({ ...props }) => {
const isTv = Platform.isTV;
const media = useMedia();
const [_, __, pluginSettings] = useSettings();
const [_, __, pluginSettings] = useSettings(null);
const { settings, updateSettings } = media;
const cultures = media.cultures;
const { t } = useTranslation();

View File

@@ -4,7 +4,7 @@ import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
export const ChromecastSettings: React.FC = ({ ...props }) => {
const [settings, updateSettings] = useSettings();
const [settings, updateSettings] = useSettings(null);
return (
<View {...props}>
<ListGroup title={"Chromecast"}>

View File

@@ -7,7 +7,7 @@ import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
export const Dashboard = () => {
const [settings, _updateSettings] = useSettings();
const [settings, _updateSettings] = useSettings(null);
const { sessions = [] } = useSessions({} as useSessionsProps);
const router = useRouter();

View File

@@ -7,7 +7,7 @@ import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
export default function DownloadSettings({ ...props }) {
const [settings, updateSettings, pluginSettings] = useSettings();
const [settings, updateSettings, pluginSettings] = useSettings(null);
const { t } = useTranslation();
const allDisabled = useMemo(

View File

@@ -13,7 +13,7 @@ interface Props extends ViewProps {}
export const GestureControls: React.FC<Props> = ({ ...props }) => {
const { t } = useTranslation();
const [settings, updateSettings, pluginSettings] = useSettings();
const [settings, updateSettings, pluginSettings] = useSettings(null);
const disabled = useMemo(
() =>

View File

@@ -70,7 +70,7 @@ export const HomeIndex = () => {
_pluginSettings,
_setPluginSettings,
refreshStreamyfinPluginSettings,
] = useSettings();
] = useSettings(null);
const [isConnected, setIsConnected] = useState<boolean | null>(null);
const [loadingRetry, setLoadingRetry] = useState(false);
@@ -81,7 +81,7 @@ export const HomeIndex = () => {
const scrollViewRef = useRef<ScrollView>(null);
const { downloadedFiles, cleanCacheDirectory } = useDownload();
const { getDownloadedItems, cleanCacheDirectory } = useDownload();
const prevIsConnected = useRef<boolean | null>(false);
const invalidateCache = useInvalidatePlaybackProgressCache();
useEffect(() => {
@@ -100,7 +100,7 @@ export const HomeIndex = () => {
});
return;
}
const hasDownloads = downloadedFiles && downloadedFiles.length > 0;
const hasDownloads = getDownloadedItems().length > 0;
navigation.setOptions({
headerLeft: () => (
<TouchableOpacity
@@ -117,7 +117,7 @@ export const HomeIndex = () => {
</TouchableOpacity>
),
});
}, [downloadedFiles, navigation, router]);
}, [navigation, router]);
useEffect(() => {
cleanCacheDirectory().catch((_e) =>
@@ -338,10 +338,8 @@ export const HomeIndex = () => {
const customSections = useMemo(() => {
if (!api || !user?.Id || !settings?.home?.sections) return [];
const ss: Section[] = [];
for (const key in settings.home?.sections) {
// @ts-expect-error
const section = settings.home?.sections[key];
const id = section.title || key;
for (const [index, section] of settings.home.sections.entries()) {
const id = section.items?.title || `section-${index}`;
ss.push({
title: id,
queryKey: ["home", id],
@@ -363,10 +361,10 @@ export const HomeIndex = () => {
const response = await getTvShowsApi(api).getNextUp({
userId: user?.Id,
fields: ["MediaSourceCount"],
limit: section.items?.limit || 25,
limit: section.nextUp?.limit || 25,
enableImageTypes: ["Primary", "Backdrop", "Thumb"],
enableResumable: section.items?.enableResumable,
enableRewatching: section.items?.enableRewatching,
enableResumable: section.nextUp?.enableResumable,
enableRewatching: section.nextUp?.enableRewatching,
});
return response.data.Items || [];
}

View File

@@ -20,7 +20,7 @@ export const JellyseerrSettings = () => {
const { t } = useTranslation();
const [user] = useAtom(userAtom);
const [settings, updateSettings, _pluginSettings] = useSettings();
const [settings, updateSettings, _pluginSettings] = useSettings(null);
const [jellyseerrPassword, setJellyseerrPassword] = useState<
string | undefined

View File

@@ -28,7 +28,7 @@ export const useMedia = () => {
};
export const MediaProvider = ({ children }: { children: ReactNode }) => {
const [settings, updateSettings] = useSettings();
const [settings, updateSettings] = useSettings(null);
const api = useAtomValue(apiAtom);
const queryClient = useQueryClient();

View File

@@ -13,7 +13,7 @@ interface Props extends ViewProps {}
export const MediaToggles: React.FC<Props> = ({ ...props }) => {
const { t } = useTranslation();
const [settings, updateSettings, pluginSettings] = useSettings();
const [settings, updateSettings, pluginSettings] = useSettings(null);
const disabled = useMemo(
() =>

View File

@@ -23,7 +23,7 @@ import { ListItem } from "../list/ListItem";
export const OtherSettings: React.FC = () => {
const router = useRouter();
const [settings, updateSettings, pluginSettings] = useSettings();
const [settings, updateSettings, pluginSettings] = useSettings(null);
const { t } = useTranslation();

View File

@@ -6,7 +6,7 @@ import { ListGroup } from "../list/ListGroup";
import { ListItem } from "../list/ListItem";
export const PluginSettings = () => {
const [settings, _updateSettings] = useSettings();
const [settings, _updateSettings] = useSettings(null);
const router = useRouter();

View File

@@ -20,7 +20,7 @@ export const SubtitleToggles: React.FC<Props> = ({ ...props }) => {
const isTv = Platform.isTV;
const media = useMedia();
const [_, __, pluginSettings] = useSettings();
const [_, __, pluginSettings] = useSettings(null);
const { settings, updateSettings } = media;
const cultures = media.cultures;
const { t } = useTranslation();

View File

@@ -22,7 +22,7 @@ const AudioSlider: React.FC<AudioSliderProps> = ({ setVisibility }) => {
const max = useSharedValue<number>(100); // Explicitly type as number
const isUserInteracting = useRef(false);
const timeoutRef = useRef<number | null>(null); // Use a ref to store the timeout ID
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); // Use a ref to store the timeout ID
useEffect(() => {
if (isTv) return;

View File

@@ -88,7 +88,7 @@ export const BottomControls: FC<BottomControlsProps> = ({
trickplayInfo,
time,
}) => {
const [settings] = useSettings();
const [settings] = useSettings(null);
const insets = useSafeAreaInsets();
return (

View File

@@ -30,7 +30,7 @@ export const CenterControls: FC<CenterControlsProps> = ({
handleSkipBackward,
handleSkipForward,
}) => {
const [settings] = useSettings();
const [settings] = useSettings(null);
const insets = useSafeAreaInsets();
return (

View File

@@ -16,7 +16,7 @@ export interface ContinueWatchingOverlayProps {
const ContinueWatchingOverlay: React.FC<ContinueWatchingOverlayProps> = ({
goToNextItem,
}) => {
const [settings] = useSettings();
const [settings] = useSettings(null);
const router = useRouter();
return settings.autoPlayEpisodeCount >=

View File

@@ -31,7 +31,7 @@ export const GestureOverlay = ({
onSkipForward,
onSkipBackward,
}: Props) => {
const [settings] = useSettings();
const [settings] = useSettings(null);
const lightHaptic = useHaptic("light");
const [feedback, setFeedback] = useState<FeedbackState>({

View File

@@ -70,7 +70,7 @@ export const HeaderControls: FC<HeaderControlsProps> = ({
setVideoAspectRatio,
setVideoScaleFactor,
}) => {
const [settings] = useSettings();
const [settings] = useSettings(null);
const router = useRouter();
const insets = useSafeAreaInsets();
const { width: screenWidth } = useWindowDimensions();

View File

@@ -20,7 +20,7 @@ export function useVideoNavigation({
seek,
play,
}: UseVideoNavigationProps) {
const [settings] = useSettings();
const [settings] = useSettings(null);
const lightHapticFeedback = useHaptic("light");
const wasPlayingRef = useRef(false);

View File

@@ -15,7 +15,7 @@ export const useControlsTimeout = ({
onHideControls,
timeout = 4000,
}: UseControlsTimeoutProps) => {
const controlsTimeoutRef = useRef<NodeJS.Timeout>();
const controlsTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
const resetControlsTimeout = () => {