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

@@ -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 }}

View File

@@ -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

View File

@@ -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);

View File

@@ -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 (
<DisabledSetting

View File

@@ -85,7 +85,7 @@ export default function Page() {
values={[order]}
title={t("library.filters.sort_order")}
renderItemLabel={(order) => t(`library.filters.${order}`)}
showSearch={false}
disableSearch={true}
/>
<FilterButton
id={levelsFilterId}
@@ -95,7 +95,7 @@ export default function Page() {
values={levels}
title={t("home.settings.logs.level")}
renderItemLabel={(level) => level}
showSearch={false}
disableSearch={true}
multiple={true}
/>
</View>

View File

@@ -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<string>(settings?.marlinServerUrl || "");

View File

@@ -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();

View File

@@ -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();

View File

@@ -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<JellyseerrSearchSort>(
@@ -330,7 +330,7 @@ export default function search() {
renderItemLabel={(item) =>
t(`home.settings.plugins.jellyseerr.order_by.${item}`)
}
showSearch={false}
disableSearch={true}
/>
<FilterButton
id={orderFilterId}
@@ -340,7 +340,7 @@ export default function search() {
values={[jellyseerrSortOrder]}
title={t("library.filters.sort_order")}
renderItemLabel={(item) => t(`library.filters.${item}`)}
showSearch={false}
disableSearch={true}
/>
</View>
)}

View File

@@ -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();

View File

@@ -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();

View File

@@ -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<ExpoPushToken>();
const notificationListener = useRef<EventSubscription>();
const responseListener = useRef<EventSubscription>();
const notificationListener = useRef<EventSubscription>(null);
const responseListener = useRef<EventSubscription>(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) {

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 = () => {

View File

@@ -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 ||

View File

@@ -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;

View File

@@ -20,7 +20,7 @@ export const useJellyfinDiscovery = () => {
setServers([]);
const discoveredServers = new Set<string>();
let discoveryTimeout: number;
let discoveryTimeout: ReturnType<typeof setTimeout>;
const socket = dgram.createSocket({
type: "udp4",

View File

@@ -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<JellyseerrUser>(JELLYSEERR_USER));
export const useJellyseerr = (
settings: Settings,
updateSettings: (update: Partial<Settings>) => void,
settings: Settings = defaultValues,
updateSettings: (update: Partial<Settings>) => 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;
};

View File

@@ -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 .",

View File

@@ -87,7 +87,7 @@ function useDownloadProvider() {
const { saveSeriesPrimaryImage } = useDownloadHelper();
const { saveImage } = useImageStorage();
const [processes, setProcesses] = useAtom<JobStatus[]>(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.

View File

@@ -56,7 +56,7 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => {
}&deviceId=${deviceId}`;
const newWebSocket = new WebSocket(url);
let keepAliveInterval: number | null = null;
let keepAliveInterval: ReturnType<typeof setInterval> | null = null;
newWebSocket.onopen = () => {
console.log("WebSocket connection opened");

View File

@@ -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/**/*"
]
}

View File

@@ -56,7 +56,7 @@ export const useJobProcessor = () => {
const [queue, setQueue] = useAtom(queueAtom);
const [running, setRunning] = useAtom(runningAtom);
const [processes] = useAtom<JobStatus[]>(processesAtom);
const [settings] = useSettings();
const [settings] = useSettings(null);
useEffect(() => {
if (

View File

@@ -171,6 +171,7 @@ export type Settings = {
enableHorizontalSwipeSkip: boolean;
enableLeftSideBrightnessSwipe: boolean;
enableRightSideVolumeSwipe: boolean;
usePopularPlugin: boolean;
};
export interface Lockable<T> {
@@ -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<Settings> => {