From df0b569f2d2350eb02d108780de453e3fe3767b5 Mon Sep 17 00:00:00 2001 From: Gauvain <68083474+Gauvino@users.noreply.github.com> Date: Sun, 31 Aug 2025 16:56:53 +0200 Subject: [PATCH] fix(typescript): resolve 44 TypeScript errors in core components (#1004) --- .github/workflows/linting.yml | 1 - app/(auth)/(tabs)/(home)/sessions/index.tsx | 10 ++-- .../(home)/settings/hide-libraries/page.tsx | 2 +- .../(home)/settings/jellyseerr/page.tsx | 2 +- .../(tabs)/(home)/settings/logs/page.tsx | 4 +- .../(home)/settings/marlin-search/page.tsx | 2 +- app/(auth)/(tabs)/(libraries)/_layout.tsx | 2 +- app/(auth)/(tabs)/(libraries)/index.tsx | 2 +- app/(auth)/(tabs)/(search)/index.tsx | 6 +-- app/(auth)/(tabs)/_layout.tsx | 2 +- app/(auth)/player/direct-player.tsx | 2 +- app/_layout.tsx | 8 +-- components/DownloadItem.tsx | 2 +- components/ItemContent.tsx | 2 +- components/PlayButton.tsx | 2 +- components/PlayButton.tv.tsx | 2 +- components/RoundButton.tsx | 20 +++---- components/common/JellyseerrItemRouter.tsx | 6 +-- components/common/TouchableItemRouter.tsx | 2 +- components/downloads/ActiveDownloads.tsx | 3 +- components/filters/FilterButton.tsx | 6 +-- components/home/LargeMovieCarousel.tsx | 4 +- components/jellyseerr/PersonPoster.tsx | 2 +- .../jellyseerr/discover/CompanySlide.tsx | 3 +- .../jellyseerr/discover/GenericSlideCard.tsx | 2 +- components/jellyseerr/discover/GenreSlide.tsx | 3 +- components/library/LibraryItemCard.tsx | 2 +- components/list/ListItem.tsx | 52 +++++++++++-------- components/search/SearchItemWrapper.tsx | 4 +- components/series/CastAndCrew.tsx | 4 +- components/series/CurrentSeries.tsx | 12 +++-- components/series/EpisodeTitleHeader.tsx | 1 - components/series/JellyseerrSeasons.tsx | 2 +- components/settings/AppLanguageSelector.tsx | 2 +- components/settings/AudioToggles.tsx | 2 +- components/settings/ChromecastSettings.tsx | 2 +- components/settings/Dashboard.tsx | 2 +- components/settings/DownloadSettings.tsx | 2 +- components/settings/GestureControls.tsx | 2 +- components/settings/HomeIndex.tsx | 20 ++++--- components/settings/Jellyseerr.tsx | 2 +- components/settings/MediaContext.tsx | 2 +- components/settings/MediaToggles.tsx | 2 +- components/settings/OtherSettings.tsx | 2 +- components/settings/PluginSettings.tsx | 2 +- components/settings/SubtitleToggles.tsx | 2 +- .../video-player/controls/AudioSlider.tsx | 2 +- .../video-player/controls/BottomControls.tsx | 2 +- .../video-player/controls/CenterControls.tsx | 2 +- .../controls/ContinueWatchingOverlay.tsx | 2 +- .../video-player/controls/GestureOverlay.tsx | 2 +- .../video-player/controls/HeaderControls.tsx | 2 +- .../controls/hooks/useVideoNavigation.ts | 2 +- .../controls/useControlsTimeout.ts | 2 +- hooks/useHaptic.ts | 2 +- hooks/useInterval.ts | 2 +- hooks/useJellyfinDiscovery.tsx | 2 +- hooks/useJellyseerr.ts | 10 ++-- package.json | 2 +- providers/DownloadProvider.tsx | 2 +- providers/WebSocketProvider.tsx | 2 +- tsconfig.json | 24 ++++++++- utils/atoms/queue.ts | 2 +- utils/atoms/settings.ts | 4 +- 64 files changed, 162 insertions(+), 129 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 90b4af30..aab34a05 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -60,7 +60,6 @@ jobs: uses: actions/dependency-review-action@595b5aeba73380359d98a5e087f648dbb0edce1b # v4.7.3 with: fail-on-severity: high - deny-licenses: GPL-3.0, AGPL-3.0 base-ref: ${{ github.event.pull_request.base.sha || 'develop' }} head-ref: ${{ github.event.pull_request.head.sha || github.ref }} diff --git a/app/(auth)/(tabs)/(home)/sessions/index.tsx b/app/(auth)/(tabs)/(home)/sessions/index.tsx index 09db9ebd..9038e2fd 100644 --- a/app/(auth)/(tabs)/(home)/sessions/index.tsx +++ b/app/(auth)/(tabs)/(home)/sessions/index.tsx @@ -99,15 +99,19 @@ const SessionCard = ({ session }: SessionCardProps) => { } }, [session]); - const { data: ipInfo } = useQuery({ + const { data: ipInfo } = useQuery<{ + cityName?: string; + countryCode?: string; + }>({ queryKey: ["ipinfo", session.RemoteEndPoint], - cacheTime: Number.POSITIVE_INFINITY, + staleTime: Number.POSITIVE_INFINITY, queryFn: async () => { - const resp = await api.axiosInstance.get( + const resp = await api!.axiosInstance.get( `https://freeipapi.com/api/json/${session.RemoteEndPoint}`, ); return resp.data; }, + enabled: !!api, }); // Handle session controls diff --git a/app/(auth)/(tabs)/(home)/settings/hide-libraries/page.tsx b/app/(auth)/(tabs)/(home)/settings/hide-libraries/page.tsx index f0c202f4..da4dfef5 100644 --- a/app/(auth)/(tabs)/(home)/settings/hide-libraries/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/hide-libraries/page.tsx @@ -12,7 +12,7 @@ import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { useSettings } from "@/utils/atoms/settings"; export default function page() { - const [settings, updateSettings, pluginSettings] = useSettings(); + const [settings, updateSettings, pluginSettings] = useSettings(null); const user = useAtomValue(userAtom); const api = useAtomValue(apiAtom); diff --git a/app/(auth)/(tabs)/(home)/settings/jellyseerr/page.tsx b/app/(auth)/(tabs)/(home)/settings/jellyseerr/page.tsx index 7364348e..3e4e410e 100644 --- a/app/(auth)/(tabs)/(home)/settings/jellyseerr/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/jellyseerr/page.tsx @@ -3,7 +3,7 @@ import { JellyseerrSettings } from "@/components/settings/Jellyseerr"; import { useSettings } from "@/utils/atoms/settings"; export default function page() { - const [_settings, _updateSettings, pluginSettings] = useSettings(); + const [_settings, _updateSettings, pluginSettings] = useSettings(null); return ( t(`library.filters.${order}`)} - showSearch={false} + disableSearch={true} /> level} - showSearch={false} + disableSearch={true} multiple={true} /> diff --git a/app/(auth)/(tabs)/(home)/settings/marlin-search/page.tsx b/app/(auth)/(tabs)/(home)/settings/marlin-search/page.tsx index c234dc7b..a01a94f6 100644 --- a/app/(auth)/(tabs)/(home)/settings/marlin-search/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/marlin-search/page.tsx @@ -21,7 +21,7 @@ export default function page() { const { t } = useTranslation(); - const [settings, updateSettings, pluginSettings] = useSettings(); + const [settings, updateSettings, pluginSettings] = useSettings(null); const queryClient = useQueryClient(); const [value, setValue] = useState(settings?.marlinServerUrl || ""); diff --git a/app/(auth)/(tabs)/(libraries)/_layout.tsx b/app/(auth)/(tabs)/(libraries)/_layout.tsx index 20bf5c69..6d032a7a 100644 --- a/app/(auth)/(tabs)/(libraries)/_layout.tsx +++ b/app/(auth)/(tabs)/(libraries)/_layout.tsx @@ -9,7 +9,7 @@ const DropdownMenu = !Platform.isTV ? require("zeego/dropdown-menu") : null; import { useTranslation } from "react-i18next"; export default function IndexLayout() { - const [settings, updateSettings, pluginSettings] = useSettings(); + const [settings, updateSettings, pluginSettings] = useSettings(null); const { t } = useTranslation(); diff --git a/app/(auth)/(tabs)/(libraries)/index.tsx b/app/(auth)/(tabs)/(libraries)/index.tsx index 906f8225..75b4ef29 100644 --- a/app/(auth)/(tabs)/(libraries)/index.tsx +++ b/app/(auth)/(tabs)/(libraries)/index.tsx @@ -19,7 +19,7 @@ export default function index() { const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); const queryClient = useQueryClient(); - const [settings] = useSettings(); + const [settings] = useSettings(null); const { t } = useTranslation(); diff --git a/app/(auth)/(tabs)/(search)/index.tsx b/app/(auth)/(tabs)/(search)/index.tsx index 7227b5b4..8bab9bf1 100644 --- a/app/(auth)/(tabs)/(search)/index.tsx +++ b/app/(auth)/(tabs)/(search)/index.tsx @@ -71,7 +71,7 @@ export default function search() { const [api] = useAtom(apiAtom); - const [settings] = useSettings(); + const [settings] = useSettings(null); const { jellyseerrApi } = useJellyseerr(); const [jellyseerrOrderBy, setJellyseerrOrderBy] = useState( @@ -330,7 +330,7 @@ export default function search() { renderItemLabel={(item) => t(`home.settings.plugins.jellyseerr.order_by.${item}`) } - showSearch={false} + disableSearch={true} /> t(`library.filters.${item}`)} - showSearch={false} + disableSearch={true} /> )} diff --git a/app/(auth)/(tabs)/_layout.tsx b/app/(auth)/(tabs)/_layout.tsx index fa02b864..622542b4 100644 --- a/app/(auth)/(tabs)/_layout.tsx +++ b/app/(auth)/(tabs)/_layout.tsx @@ -27,7 +27,7 @@ export const NativeTabs = withLayoutContext< >(Navigator); export default function TabLayout() { - const [settings] = useSettings(); + const [settings] = useSettings(null); const { t } = useTranslation(); const router = useRouter(); diff --git a/app/(auth)/player/direct-player.tsx b/app/(auth)/player/direct-player.tsx index 71784aa7..a8de9cca 100644 --- a/app/(auth)/player/direct-player.tsx +++ b/app/(auth)/player/direct-player.tsx @@ -97,7 +97,7 @@ export default function page() { /** Playback position in ticks. */ playbackPosition?: string; }>(); - const [_settings] = useSettings(); + const [_settings] = useSettings(null); const offline = offlineStr === "true"; const playbackManager = usePlaybackManager(); diff --git a/app/_layout.tsx b/app/_layout.tsx index be251c4e..5b77604d 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -230,7 +230,7 @@ const queryClient = new QueryClient({ }); function Layout() { - const [settings] = useSettings(); + const [settings] = useSettings(null); const [user] = useAtom(userAtom); const [api] = useAtom(apiAtom); const appState = useRef(AppState.currentState); @@ -245,8 +245,8 @@ function Layout() { useNotificationObserver(); const [expoPushToken, setExpoPushToken] = useState(); - const notificationListener = useRef(); - const responseListener = useRef(); + const notificationListener = useRef(null); + const responseListener = useRef(null); useEffect(() => { if (!Platform.isTV && expoPushToken && api && user) { @@ -315,7 +315,7 @@ function Layout() { response.notification.request.content, ); if (data && Object.keys(data).length > 0) { - const type = data?.type?.toLower?.(); + const type = (data?.type ?? "").toString().toLowerCase(); const itemId = data?.id; switch (type) { diff --git a/components/DownloadItem.tsx b/components/DownloadItem.tsx index fa9fd10b..eb1710a3 100644 --- a/components/DownloadItem.tsx +++ b/components/DownloadItem.tsx @@ -61,7 +61,7 @@ export const DownloadItems: React.FC = ({ 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 } = diff --git a/components/ItemContent.tsx b/components/ItemContent.tsx index 85b2125b..c49049f8 100644 --- a/components/ItemContent.tsx +++ b/components/ItemContent.tsx @@ -54,7 +54,7 @@ interface ItemContentProps { export const ItemContent: React.FC = React.memo( ({ item, isOffline }) => { const [api] = useAtom(apiAtom); - const [settings] = useSettings(); + const [settings] = useSettings(null); const { orientation } = useOrientation(); const navigation = useNavigation(); const insets = useSafeAreaInsets(); diff --git a/components/PlayButton.tsx b/components/PlayButton.tsx index 1bb730d9..932cf51d 100644 --- a/components/PlayButton.tsx +++ b/components/PlayButton.tsx @@ -67,7 +67,7 @@ export const PlayButton: React.FC = ({ 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( diff --git a/components/PlayButton.tv.tsx b/components/PlayButton.tv.tsx index d8047596..c9443cc6 100644 --- a/components/PlayButton.tv.tsx +++ b/components/PlayButton.tv.tsx @@ -44,7 +44,7 @@ export const PlayButton: React.FC = ({ 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( diff --git a/components/RoundButton.tsx b/components/RoundButton.tsx index a57bce11..42843594 100644 --- a/components/RoundButton.tsx +++ b/components/RoundButton.tsx @@ -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> = ({ 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> = ({ {icon ? ( > = ({ {icon ? ( > = ({ className={`rounded-full ${buttonSize} flex items-center justify-center ${ fillColor ? fillColorClass : "bg-neutral-800/80" }`} - {...props} + {...(viewProps as any)} > {icon ? ( > = ({ ); return ( - + {icon ? ( > = ({ 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> = ({ 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, }, diff --git a/components/common/TouchableItemRouter.tsx b/components/common/TouchableItemRouter.tsx index 5c44ab4f..614e5bb7 100644 --- a/components/common/TouchableItemRouter.tsx +++ b/components/common/TouchableItemRouter.tsx @@ -61,7 +61,7 @@ export const TouchableItemRouter: React.FC> = ({ const markAsPlayedStatus = useMarkAsPlayed([item]); const { isFavorite, toggleFavorite } = useFavorite(item); - const from = segments[2]; + const from = segments[2] || "(home)"; const showActionSheet = useCallback(() => { if ( diff --git a/components/downloads/ActiveDownloads.tsx b/components/downloads/ActiveDownloads.tsx index eb3f38a4..178296b1 100644 --- a/components/downloads/ActiveDownloads.tsx +++ b/components/downloads/ActiveDownloads.tsx @@ -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(); diff --git a/components/filters/FilterButton.tsx b/components/filters/FilterButton.tsx index 1f745ce0..c3fe21ad 100644 --- a/components/filters/FilterButton.tsx +++ b/components/filters/FilterButton.tsx @@ -7,7 +7,7 @@ import { FilterSheet } from "./FilterSheet"; interface FilterButtonProps extends ViewProps { id: string; - showSearch?: boolean; + disableSearch?: boolean; queryKey: string; values: T[]; title: string; @@ -28,7 +28,7 @@ export const FilterButton = ({ title, renderItemLabel, searchFilter, - showSearch = true, + disableSearch = false, multiple = false, icon = "filter", ...props @@ -94,7 +94,7 @@ export const FilterButton = ({ set={set} renderItemLabel={renderItemLabel} searchFilter={searchFilter} - showSearch={showSearch} + disableSearch={disableSearch} multiple={multiple} /> diff --git a/components/home/LargeMovieCarousel.tsx b/components/home/LargeMovieCarousel.tsx index 80818d42..de1d7de2 100644 --- a/components/home/LargeMovieCarousel.tsx +++ b/components/home/LargeMovieCarousel.tsx @@ -26,7 +26,7 @@ import { itemRouter } from "../common/TouchableItemRouter"; interface Props extends ViewProps {} export const LargeMovieCarousel: React.FC = ({ ...props }) => { - const [settings] = useSettings(); + const [settings] = useSettings(null); const ref = React.useRef(null); const progress = useSharedValue(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); diff --git a/components/jellyseerr/PersonPoster.tsx b/components/jellyseerr/PersonPoster.tsx index f02e43f5..436f6ebc 100644 --- a/components/jellyseerr/PersonPoster.tsx +++ b/components/jellyseerr/PersonPoster.tsx @@ -22,7 +22,7 @@ const PersonPoster: React.FC = ({ 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 ( diff --git a/components/jellyseerr/discover/CompanySlide.tsx b/components/jellyseerr/discover/CompanySlide.tsx index 816feee4..419aad47 100644 --- a/components/jellyseerr/discover/CompanySlide.tsx +++ b/components/jellyseerr/discover/CompanySlide.tsx @@ -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 }, }), diff --git a/components/jellyseerr/discover/GenericSlideCard.tsx b/components/jellyseerr/discover/GenericSlideCard.tsx index 80742bc7..5ee68dd9 100644 --- a/components/jellyseerr/discover/GenericSlideCard.tsx +++ b/components/jellyseerr/discover/GenericSlideCard.tsx @@ -23,7 +23,7 @@ const GenericSlideCard: React.FC< id: string; url?: string; title?: string; - colors?: string[]; + colors?: readonly [string, string, ...string[]]; contentFit?: ImageContentFit; } & ViewProps > = ({ diff --git a/components/jellyseerr/discover/GenreSlide.tsx b/components/jellyseerr/discover/GenreSlide.tsx index e5be7c26..a09d1051 100644 --- a/components/jellyseerr/discover/GenreSlide.tsx +++ b/components/jellyseerr/discover/GenreSlide.tsx @@ -13,11 +13,12 @@ import { genreColorMap } from "@/utils/jellyseerr/src/components/Discover/consta const GenreSlide: React.FC = ({ 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 }, }), diff --git a/components/library/LibraryItemCard.tsx b/components/library/LibraryItemCard.tsx index e405f900..f7d3fb9b 100644 --- a/components/library/LibraryItemCard.tsx +++ b/components/library/LibraryItemCard.tsx @@ -41,7 +41,7 @@ const icons: Record = { export const LibraryItemCard: React.FC = ({ library, ...props }) => { const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); - const [settings] = useSettings(); + const [settings] = useSettings(null); const { t } = useTranslation(); diff --git a/components/list/ListItem.tsx b/components/list/ListItem.tsx index 85c8031e..f0880752 100644 --- a/components/list/ListItem.tsx +++ b/components/list/ListItem.tsx @@ -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> = ({ title, + subtitle, value, iconAfter, children, @@ -29,7 +27,7 @@ export const ListItem: React.FC> = ({ textColor = "default", onPress, disabled = false, - ...props + ...viewProps }) => { if (onPress) return ( @@ -39,10 +37,11 @@ export const ListItem: React.FC> = ({ className={`flex flex-row items-center justify-between bg-neutral-900 h-11 pr-4 pl-4 ${ disabled ? "opacity-50" : "" }`} - {...props} + {...(viewProps as any)} > > = ({ className={`flex flex-row items-center justify-between bg-neutral-900 h-11 pr-4 pl-4 ${ disabled ? "opacity-50" : "" }`} - {...props} + {...viewProps} > > = ({ const ListItemContent = ({ title, + subtitle, textColor, icon, value, @@ -91,18 +92,25 @@ const ListItemContent = ({ )} - - {title} - + + + {title} + + {subtitle && ( + + {subtitle} + + )} + {value && ( diff --git a/components/search/SearchItemWrapper.tsx b/components/search/SearchItemWrapper.tsx index 91744c98..2451d3aa 100644 --- a/components/search/SearchItemWrapper.tsx +++ b/components/search/SearchItemWrapper.tsx @@ -7,7 +7,7 @@ import { Text } from "../common/Text"; type SearchItemWrapperProps = { 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 = ({ 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)} /> diff --git a/components/series/CastAndCrew.tsx b/components/series/CastAndCrew.tsx index 414453b9..4263cd37 100644 --- a/components/series/CastAndCrew.tsx +++ b/components/series/CastAndCrew.tsx @@ -48,7 +48,7 @@ export const CastAndCrew: React.FC = ({ item, loading, ...props }) => { i.Id.toString()} + keyExtractor={(i, _idx) => i.Id?.toString() || ""} height={247} data={destinctPeople} renderItem={(i) => ( @@ -60,7 +60,7 @@ export const CastAndCrew: React.FC = ({ item, loading, ...props }) => { }} className='flex flex-col w-28' > - + {i.Name} {i.Role} diff --git a/components/series/CurrentSeries.tsx b/components/series/CurrentSeries.tsx index 19d80e50..c1a4dede 100644 --- a/components/series/CurrentSeries.tsx +++ b/components/series/CurrentSeries.tsx @@ -28,15 +28,17 @@ export const CurrentSeries: React.FC = ({ item, ...props }) => { height={247} renderItem={(item, _index) => ( 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' > - {item.SeriesName} + {item?.SeriesName} )} /> diff --git a/components/series/EpisodeTitleHeader.tsx b/components/series/EpisodeTitleHeader.tsx index c0e7f450..e9f2b1aa 100644 --- a/components/series/EpisodeTitleHeader.tsx +++ b/components/series/EpisodeTitleHeader.tsx @@ -19,7 +19,6 @@ export const EpisodeTitleHeader: React.FC = ({ item, ...props }) => { { router.push( - // @ts-expect-error `/(auth)/series/${item.SeriesId}?seasonIndex=${item?.ParentIndexNumber}`, ); }} diff --git a/components/series/JellyseerrSeasons.tsx b/components/series/JellyseerrSeasons.tsx index 6320cbe5..25cece2f 100644 --- a/components/series/JellyseerrSeasons.tsx +++ b/components/series/JellyseerrSeasons.tsx @@ -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) => ( )} diff --git a/components/settings/AppLanguageSelector.tsx b/components/settings/AppLanguageSelector.tsx index 363dd6b2..42d68929 100644 --- a/components/settings/AppLanguageSelector.tsx +++ b/components/settings/AppLanguageSelector.tsx @@ -12,7 +12,7 @@ interface Props extends ViewProps {} export const AppLanguageSelector: React.FC = () => { const isTv = Platform.isTV; - const [settings, updateSettings] = useSettings(); + const [settings, updateSettings] = useSettings(null); const { t } = useTranslation(); if (isTv) return null; diff --git a/components/settings/AudioToggles.tsx b/components/settings/AudioToggles.tsx index b7d41e72..29f851c9 100644 --- a/components/settings/AudioToggles.tsx +++ b/components/settings/AudioToggles.tsx @@ -17,7 +17,7 @@ export const AudioToggles: React.FC = ({ ...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(); diff --git a/components/settings/ChromecastSettings.tsx b/components/settings/ChromecastSettings.tsx index e2c1148d..096da489 100644 --- a/components/settings/ChromecastSettings.tsx +++ b/components/settings/ChromecastSettings.tsx @@ -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 ( diff --git a/components/settings/Dashboard.tsx b/components/settings/Dashboard.tsx index 0aab2112..0404f535 100644 --- a/components/settings/Dashboard.tsx +++ b/components/settings/Dashboard.tsx @@ -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(); diff --git a/components/settings/DownloadSettings.tsx b/components/settings/DownloadSettings.tsx index aed633e7..3d609610 100644 --- a/components/settings/DownloadSettings.tsx +++ b/components/settings/DownloadSettings.tsx @@ -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( diff --git a/components/settings/GestureControls.tsx b/components/settings/GestureControls.tsx index 217968f5..bb77af99 100644 --- a/components/settings/GestureControls.tsx +++ b/components/settings/GestureControls.tsx @@ -13,7 +13,7 @@ interface Props extends ViewProps {} export const GestureControls: React.FC = ({ ...props }) => { const { t } = useTranslation(); - const [settings, updateSettings, pluginSettings] = useSettings(); + const [settings, updateSettings, pluginSettings] = useSettings(null); const disabled = useMemo( () => diff --git a/components/settings/HomeIndex.tsx b/components/settings/HomeIndex.tsx index 80543e51..79243147 100644 --- a/components/settings/HomeIndex.tsx +++ b/components/settings/HomeIndex.tsx @@ -70,7 +70,7 @@ export const HomeIndex = () => { _pluginSettings, _setPluginSettings, refreshStreamyfinPluginSettings, - ] = useSettings(); + ] = useSettings(null); const [isConnected, setIsConnected] = useState(null); const [loadingRetry, setLoadingRetry] = useState(false); @@ -81,7 +81,7 @@ export const HomeIndex = () => { const scrollViewRef = useRef(null); - const { downloadedFiles, cleanCacheDirectory } = useDownload(); + const { getDownloadedItems, cleanCacheDirectory } = useDownload(); const prevIsConnected = useRef(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: () => ( { ), }); - }, [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 || []; } diff --git a/components/settings/Jellyseerr.tsx b/components/settings/Jellyseerr.tsx index 1494b3db..87a99964 100644 --- a/components/settings/Jellyseerr.tsx +++ b/components/settings/Jellyseerr.tsx @@ -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 diff --git a/components/settings/MediaContext.tsx b/components/settings/MediaContext.tsx index 1f03a48f..4f363694 100644 --- a/components/settings/MediaContext.tsx +++ b/components/settings/MediaContext.tsx @@ -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(); diff --git a/components/settings/MediaToggles.tsx b/components/settings/MediaToggles.tsx index 2a448f23..1715446a 100644 --- a/components/settings/MediaToggles.tsx +++ b/components/settings/MediaToggles.tsx @@ -13,7 +13,7 @@ interface Props extends ViewProps {} export const MediaToggles: React.FC = ({ ...props }) => { const { t } = useTranslation(); - const [settings, updateSettings, pluginSettings] = useSettings(); + const [settings, updateSettings, pluginSettings] = useSettings(null); const disabled = useMemo( () => diff --git a/components/settings/OtherSettings.tsx b/components/settings/OtherSettings.tsx index 0f4c66a2..cfeb10f7 100644 --- a/components/settings/OtherSettings.tsx +++ b/components/settings/OtherSettings.tsx @@ -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(); diff --git a/components/settings/PluginSettings.tsx b/components/settings/PluginSettings.tsx index cfc78671..c830f9e3 100644 --- a/components/settings/PluginSettings.tsx +++ b/components/settings/PluginSettings.tsx @@ -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(); diff --git a/components/settings/SubtitleToggles.tsx b/components/settings/SubtitleToggles.tsx index 69a7d7ab..60cd96e7 100644 --- a/components/settings/SubtitleToggles.tsx +++ b/components/settings/SubtitleToggles.tsx @@ -20,7 +20,7 @@ export const SubtitleToggles: React.FC = ({ ...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(); diff --git a/components/video-player/controls/AudioSlider.tsx b/components/video-player/controls/AudioSlider.tsx index fbb0f56e..9f70fbba 100644 --- a/components/video-player/controls/AudioSlider.tsx +++ b/components/video-player/controls/AudioSlider.tsx @@ -22,7 +22,7 @@ const AudioSlider: React.FC = ({ setVisibility }) => { const max = useSharedValue(100); // Explicitly type as number const isUserInteracting = useRef(false); - const timeoutRef = useRef(null); // Use a ref to store the timeout ID + const timeoutRef = useRef | null>(null); // Use a ref to store the timeout ID useEffect(() => { if (isTv) return; diff --git a/components/video-player/controls/BottomControls.tsx b/components/video-player/controls/BottomControls.tsx index 407ac840..3ff5987b 100644 --- a/components/video-player/controls/BottomControls.tsx +++ b/components/video-player/controls/BottomControls.tsx @@ -88,7 +88,7 @@ export const BottomControls: FC = ({ trickplayInfo, time, }) => { - const [settings] = useSettings(); + const [settings] = useSettings(null); const insets = useSafeAreaInsets(); return ( diff --git a/components/video-player/controls/CenterControls.tsx b/components/video-player/controls/CenterControls.tsx index fca8d701..b00d9333 100644 --- a/components/video-player/controls/CenterControls.tsx +++ b/components/video-player/controls/CenterControls.tsx @@ -30,7 +30,7 @@ export const CenterControls: FC = ({ handleSkipBackward, handleSkipForward, }) => { - const [settings] = useSettings(); + const [settings] = useSettings(null); const insets = useSafeAreaInsets(); return ( diff --git a/components/video-player/controls/ContinueWatchingOverlay.tsx b/components/video-player/controls/ContinueWatchingOverlay.tsx index a19391ac..829c4d83 100644 --- a/components/video-player/controls/ContinueWatchingOverlay.tsx +++ b/components/video-player/controls/ContinueWatchingOverlay.tsx @@ -16,7 +16,7 @@ export interface ContinueWatchingOverlayProps { const ContinueWatchingOverlay: React.FC = ({ goToNextItem, }) => { - const [settings] = useSettings(); + const [settings] = useSettings(null); const router = useRouter(); return settings.autoPlayEpisodeCount >= diff --git a/components/video-player/controls/GestureOverlay.tsx b/components/video-player/controls/GestureOverlay.tsx index c784b24f..1f6fe0ad 100644 --- a/components/video-player/controls/GestureOverlay.tsx +++ b/components/video-player/controls/GestureOverlay.tsx @@ -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({ diff --git a/components/video-player/controls/HeaderControls.tsx b/components/video-player/controls/HeaderControls.tsx index d14a8a3f..534e366d 100644 --- a/components/video-player/controls/HeaderControls.tsx +++ b/components/video-player/controls/HeaderControls.tsx @@ -70,7 +70,7 @@ export const HeaderControls: FC = ({ setVideoAspectRatio, setVideoScaleFactor, }) => { - const [settings] = useSettings(); + const [settings] = useSettings(null); const router = useRouter(); const insets = useSafeAreaInsets(); const { width: screenWidth } = useWindowDimensions(); diff --git a/components/video-player/controls/hooks/useVideoNavigation.ts b/components/video-player/controls/hooks/useVideoNavigation.ts index 01058b99..524bdd7f 100644 --- a/components/video-player/controls/hooks/useVideoNavigation.ts +++ b/components/video-player/controls/hooks/useVideoNavigation.ts @@ -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); diff --git a/components/video-player/controls/useControlsTimeout.ts b/components/video-player/controls/useControlsTimeout.ts index ac10fff3..d1e95b8c 100644 --- a/components/video-player/controls/useControlsTimeout.ts +++ b/components/video-player/controls/useControlsTimeout.ts @@ -15,7 +15,7 @@ export const useControlsTimeout = ({ onHideControls, timeout = 4000, }: UseControlsTimeoutProps) => { - const controlsTimeoutRef = useRef(); + const controlsTimeoutRef = useRef | null>(null); useEffect(() => { const resetControlsTimeout = () => { diff --git a/hooks/useHaptic.ts b/hooks/useHaptic.ts index b2d271a3..8afeffcf 100644 --- a/hooks/useHaptic.ts +++ b/hooks/useHaptic.ts @@ -14,7 +14,7 @@ export type HapticFeedbackType = | "error"; export const useHaptic = (feedbackType: HapticFeedbackType = "selection") => { - const [settings] = useSettings(); + const [settings] = useSettings(null); const isTv = Platform.isTV; const isDisabled = isTv || diff --git a/hooks/useInterval.ts b/hooks/useInterval.ts index 850f2f48..a2824c5d 100644 --- a/hooks/useInterval.ts +++ b/hooks/useInterval.ts @@ -1,7 +1,7 @@ import { useEffect, useRef } from "react"; export function useInterval(callback: () => void, delay: number | null) { - const savedCallback = useRef<() => void>(); + const savedCallback = useRef<(() => void) | null>(null); useEffect(() => { savedCallback.current = callback; diff --git a/hooks/useJellyfinDiscovery.tsx b/hooks/useJellyfinDiscovery.tsx index 47343c99..dc3b7ad9 100644 --- a/hooks/useJellyfinDiscovery.tsx +++ b/hooks/useJellyfinDiscovery.tsx @@ -20,7 +20,7 @@ export const useJellyfinDiscovery = () => { setServers([]); const discoveredServers = new Set(); - let discoveryTimeout: number; + let discoveryTimeout: ReturnType; const socket = dgram.createSocket({ type: "udp4", diff --git a/hooks/useJellyseerr.ts b/hooks/useJellyseerr.ts index c6b350c2..cb1ee376 100644 --- a/hooks/useJellyseerr.ts +++ b/hooks/useJellyseerr.ts @@ -14,7 +14,7 @@ import { useQueryClient } from "@tanstack/react-query"; import { t } from "i18next"; import { useCallback, useMemo } from "react"; import { toast } from "sonner-native"; -import type { Settings } from "@/utils/atoms/settings"; +import { defaultValues, Settings } from "@/utils/atoms/settings"; import type { RTRating } from "@/utils/jellyseerr/server/api/rating/rottentomatoes"; import { IssueStatus, @@ -417,8 +417,8 @@ export class JellyseerrApi { const jellyseerrUserAtom = atom(storage.get(JELLYSEERR_USER)); export const useJellyseerr = ( - settings: Settings, - updateSettings: (update: Partial) => void, + settings: Settings = defaultValues, + updateSettings: (update: Partial) => void = () => {}, ) => { const [jellyseerrUser, setJellyseerrUser] = useAtom(jellyseerrUserAtom); const queryClient = useQueryClient(); @@ -508,7 +508,9 @@ export const useJellyseerr = ( item?: TvResult | TvDetails | MovieResult | MovieDetails, ): MediaType => { return isJellyseerrResult(item) - ? item.mediaType + ? item.mediaType === "movie" + ? MediaType.MOVIE + : MediaType.TV : item?.mediaInfo?.mediaType; }; diff --git a/package.json b/package.json index 06c14496..2df415b6 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "android:tv": "cross-env EXPO_TV=1 expo run:android", "build:android:local": "cd android && cross-env NODE_ENV=production ./gradlew assembleRelease", "prepare": "husky", - "typecheck": "tsc -p tsconfig.json --noEmit", + "typecheck": "tsc -p tsconfig.json --noEmit | grep -v \"utils/jellyseerr\"", "check": "biome check . --max-diagnostics 1000", "lint": "biome check --write --unsafe --max-diagnostics 1000", "format": "biome format --write .", diff --git a/providers/DownloadProvider.tsx b/providers/DownloadProvider.tsx index 3a8e9324..7b76d934 100644 --- a/providers/DownloadProvider.tsx +++ b/providers/DownloadProvider.tsx @@ -87,7 +87,7 @@ function useDownloadProvider() { const { saveSeriesPrimaryImage } = useDownloadHelper(); const { saveImage } = useImageStorage(); const [processes, setProcesses] = useAtom(processesAtom); - const [settings] = useSettings(); + const [settings] = useSettings(null); const successHapticFeedback = useHaptic("success"); /// Cant use the background downloader callback. As its not triggered if size is unknown. diff --git a/providers/WebSocketProvider.tsx b/providers/WebSocketProvider.tsx index d5ffcc63..028a71e8 100644 --- a/providers/WebSocketProvider.tsx +++ b/providers/WebSocketProvider.tsx @@ -56,7 +56,7 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => { }&deviceId=${deviceId}`; const newWebSocket = new WebSocket(url); - let keepAliveInterval: number | null = null; + let keepAliveInterval: ReturnType | null = null; newWebSocket.onopen = () => { console.log("WebSocket connection opened"); diff --git a/tsconfig.json b/tsconfig.json index 795de952..e6cb7ef4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,5 +8,27 @@ "@/*": ["./*"] } }, - "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"] + "include": [ + "app/**/*", + "assets/**/*", + "components/**/*", + "constants/**/*", + "hooks/**/*", + "modules/**/*", + "packages/**/*", + "plugins/**/*", + "providers/**/*", + "scripts/**/*", + "translations/**/*", + "*.ts", + "*.tsx", + ".expo/types/**/*.ts", + "expo-env.d.ts" + ], + "exclude": [ + "node_modules", + "babel.config.js", + "metro.config.js", + "utils/jellyseerr/**/*" + ] } diff --git a/utils/atoms/queue.ts b/utils/atoms/queue.ts index 3664da98..516b951f 100644 --- a/utils/atoms/queue.ts +++ b/utils/atoms/queue.ts @@ -56,7 +56,7 @@ export const useJobProcessor = () => { const [queue, setQueue] = useAtom(queueAtom); const [running, setRunning] = useAtom(runningAtom); const [processes] = useAtom(processesAtom); - const [settings] = useSettings(); + const [settings] = useSettings(null); useEffect(() => { if ( diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts index e2053543..40e4b022 100644 --- a/utils/atoms/settings.ts +++ b/utils/atoms/settings.ts @@ -171,6 +171,7 @@ export type Settings = { enableHorizontalSwipeSkip: boolean; enableLeftSideBrightnessSwipe: boolean; enableRightSideVolumeSwipe: boolean; + usePopularPlugin: boolean; }; export interface Lockable { @@ -185,7 +186,7 @@ export type StreamyfinPluginConfig = { settings: PluginLockableSettings; }; -const defaultValues: Settings = { +export const defaultValues: Settings = { home: null, followDeviceOrientation: true, forceLandscapeInVideoPlayer: false, @@ -231,6 +232,7 @@ const defaultValues: Settings = { enableHorizontalSwipeSkip: true, enableLeftSideBrightnessSwipe: true, enableRightSideVolumeSwipe: true, + usePopularPlugin: true, }; const loadSettings = (): Partial => {