From 0ba3f446151267a96cf15df663ce66d83991a792 Mon Sep 17 00:00:00 2001 From: Gauvain Date: Fri, 29 May 2026 08:32:21 +0200 Subject: [PATCH] chore: upgrade Biome to 2.4.16, clean up lint, and fix TV password modal (#1598) --- app/(auth)/(tabs)/(custom-links)/index.tsx | 2 +- app/(auth)/(tabs)/(favorites)/index.tsx | 2 +- app/(auth)/(tabs)/(home)/downloads/index.tsx | 2 +- app/(auth)/(tabs)/(home)/sessions/index.tsx | 4 +-- .../appearance/hide-libraries/page.tsx | 2 +- .../(home)/settings/hide-libraries/page.tsx | 2 +- .../settings/plugins/jellyseerr/page.tsx | 2 +- .../settings/plugins/kefinTweaks/page.tsx | 2 +- .../settings/plugins/marlin-search/page.tsx | 2 +- .../settings/plugins/streamystats/page.tsx | 2 +- .../jellyseerr/company/[companyId].tsx | 2 +- .../jellyseerr/genre/[genreId].tsx | 2 +- .../jellyseerr/person/[personId].tsx | 2 +- .../livetv/channels.tsx | 2 +- .../livetv/guide.tsx | 2 +- .../livetv/recordings.tsx | 2 +- app/(auth)/(tabs)/(search)/index.tsx | 4 +-- app/(auth)/player/direct-player.tsx | 4 +-- biome.json | 2 +- bun.lock | 20 ++++++------ components/PlayButton.tsx | 2 +- components/PlayButton.tv.tsx | 2 +- components/downloads/DownloadCard.tsx | 2 +- components/home/TVHeroCarousel.tsx | 2 +- components/livetv/TVChannelCard.tsx | 2 +- components/livetv/TVLiveTVGuide.tsx | 2 +- components/login/TVPasswordEntryModal.tsx | 8 +++++ .../video-player/controls/Controls.tv.tsx | 31 +++++++++++++++++++ components/video-player/controls/types.ts | 2 +- hooks/usePlaybackManager.ts | 2 +- hooks/useSessions.ts | 4 +-- package.json | 2 +- providers/WebSocketProvider.tsx | 4 +-- utils/streamRanker.ts | 2 +- 34 files changed, 85 insertions(+), 46 deletions(-) diff --git a/app/(auth)/(tabs)/(custom-links)/index.tsx b/app/(auth)/(tabs)/(custom-links)/index.tsx index 8dbb18660..b128fc332 100644 --- a/app/(auth)/(tabs)/(custom-links)/index.tsx +++ b/app/(auth)/(tabs)/(custom-links)/index.tsx @@ -16,7 +16,7 @@ export interface MenuLink { icon: string; } -export default function menuLinks() { +export default function CustomLinksPage() { const [api] = useAtom(apiAtom); const insets = useSafeAreaInsets(); const [menuLinks, setMenuLinks] = useState([]); diff --git a/app/(auth)/(tabs)/(favorites)/index.tsx b/app/(auth)/(tabs)/(favorites)/index.tsx index a3c83c04b..10fffe9d0 100644 --- a/app/(auth)/(tabs)/(favorites)/index.tsx +++ b/app/(auth)/(tabs)/(favorites)/index.tsx @@ -5,7 +5,7 @@ import { Favorites } from "@/components/home/Favorites"; import { Favorites as TVFavorites } from "@/components/home/Favorites.tv"; import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache"; -export default function favorites() { +export default function FavoritesPage() { const invalidateCache = useInvalidatePlaybackProgressCache(); const [loading, setLoading] = useState(false); diff --git a/app/(auth)/(tabs)/(home)/downloads/index.tsx b/app/(auth)/(tabs)/(home)/downloads/index.tsx index fb8ef0b9e..884b1fbb2 100644 --- a/app/(auth)/(tabs)/(home)/downloads/index.tsx +++ b/app/(auth)/(tabs)/(home)/downloads/index.tsx @@ -20,7 +20,7 @@ import { OfflineModeProvider } from "@/providers/OfflineModeProvider"; import { queueAtom } from "@/utils/atoms/queue"; import { writeToLog } from "@/utils/log"; -export default function page() { +export default function DownloadsPage() { const navigation = useNavigation(); const { t } = useTranslation(); const [_queue, _setQueue] = useAtom(queueAtom); diff --git a/app/(auth)/(tabs)/(home)/sessions/index.tsx b/app/(auth)/(tabs)/(home)/sessions/index.tsx index 0ed8fc940..d8a3590d5 100644 --- a/app/(auth)/(tabs)/(home)/sessions/index.tsx +++ b/app/(auth)/(tabs)/(home)/sessions/index.tsx @@ -23,7 +23,7 @@ import { formatBitrate } from "@/utils/bitrate"; import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl"; import { formatTimeString } from "@/utils/time"; -export default function page() { +export default function SessionsPage() { const { sessions, isLoading } = useSessions({} as useSessionsProps); const { t } = useTranslation(); @@ -72,7 +72,7 @@ const SessionCard = ({ session }: SessionCardProps) => { }; const getProgressPercentage = () => { - if (!session.NowPlayingItem || !session.NowPlayingItem.RunTimeTicks) { + if (!session.NowPlayingItem?.RunTimeTicks) { return 0; } diff --git a/app/(auth)/(tabs)/(home)/settings/appearance/hide-libraries/page.tsx b/app/(auth)/(tabs)/(home)/settings/appearance/hide-libraries/page.tsx index a0b3bab9b..24a3011e3 100644 --- a/app/(auth)/(tabs)/(home)/settings/appearance/hide-libraries/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/appearance/hide-libraries/page.tsx @@ -12,7 +12,7 @@ import DisabledSetting from "@/components/settings/DisabledSetting"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { useSettings } from "@/utils/atoms/settings"; -export default function page() { +export default function AppearanceHideLibrariesPage() { const { settings, updateSettings, pluginSettings } = useSettings(); const user = useAtomValue(userAtom); const api = useAtomValue(apiAtom); diff --git a/app/(auth)/(tabs)/(home)/settings/hide-libraries/page.tsx b/app/(auth)/(tabs)/(home)/settings/hide-libraries/page.tsx index e1c8b56b6..e7a61bde3 100644 --- a/app/(auth)/(tabs)/(home)/settings/hide-libraries/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/hide-libraries/page.tsx @@ -11,7 +11,7 @@ import DisabledSetting from "@/components/settings/DisabledSetting"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { useSettings } from "@/utils/atoms/settings"; -export default function page() { +export default function HideLibrariesPage() { const { settings, updateSettings, pluginSettings } = useSettings(); const user = useAtomValue(userAtom); const api = useAtomValue(apiAtom); diff --git a/app/(auth)/(tabs)/(home)/settings/plugins/jellyseerr/page.tsx b/app/(auth)/(tabs)/(home)/settings/plugins/jellyseerr/page.tsx index beddd900a..84041fd01 100644 --- a/app/(auth)/(tabs)/(home)/settings/plugins/jellyseerr/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/plugins/jellyseerr/page.tsx @@ -4,7 +4,7 @@ import DisabledSetting from "@/components/settings/DisabledSetting"; import { JellyseerrSettings } from "@/components/settings/Jellyseerr"; import { useSettings } from "@/utils/atoms/settings"; -export default function page() { +export default function JellyseerrPluginPage() { const { pluginSettings } = useSettings(); const insets = useSafeAreaInsets(); diff --git a/app/(auth)/(tabs)/(home)/settings/plugins/kefinTweaks/page.tsx b/app/(auth)/(tabs)/(home)/settings/plugins/kefinTweaks/page.tsx index dbbf60994..e9af145fd 100644 --- a/app/(auth)/(tabs)/(home)/settings/plugins/kefinTweaks/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/plugins/kefinTweaks/page.tsx @@ -4,7 +4,7 @@ import DisabledSetting from "@/components/settings/DisabledSetting"; import { KefinTweaksSettings } from "@/components/settings/KefinTweaks"; import { useSettings } from "@/utils/atoms/settings"; -export default function page() { +export default function KefinTweaksPage() { const { pluginSettings } = useSettings(); const insets = useSafeAreaInsets(); diff --git a/app/(auth)/(tabs)/(home)/settings/plugins/marlin-search/page.tsx b/app/(auth)/(tabs)/(home)/settings/plugins/marlin-search/page.tsx index 10be4af58..3ce2c81c3 100644 --- a/app/(auth)/(tabs)/(home)/settings/plugins/marlin-search/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/plugins/marlin-search/page.tsx @@ -18,7 +18,7 @@ import DisabledSetting from "@/components/settings/DisabledSetting"; import { useNetworkAwareQueryClient } from "@/hooks/useNetworkAwareQueryClient"; import { useSettings } from "@/utils/atoms/settings"; -export default function page() { +export default function MarlinSearchPage() { const navigation = useNavigation(); const { t } = useTranslation(); diff --git a/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx b/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx index 697db6c4e..1c4dcd199 100644 --- a/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx @@ -17,7 +17,7 @@ import { ListItem } from "@/components/list/ListItem"; import { useNetworkAwareQueryClient } from "@/hooks/useNetworkAwareQueryClient"; import { useSettings } from "@/utils/atoms/settings"; -export default function page() { +export default function StreamystatsPage() { const { t } = useTranslation(); const navigation = useNavigation(); const insets = useSafeAreaInsets(); diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/company/[companyId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/company/[companyId].tsx index fdcd786c9..34d83446c 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/company/[companyId].tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/company/[companyId].tsx @@ -13,7 +13,7 @@ import { } from "@/utils/jellyseerr/server/models/Search"; import { COMPANY_LOGO_IMAGE_FILTER } from "@/utils/jellyseerr/src/components/Discover/NetworkSlider"; -export default function page() { +export default function JellyseerrCompanyPage() { const local = useLocalSearchParams(); const { jellyseerrApi, isJellyseerrMovieOrTvResult } = useJellyseerr(); diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/genre/[genreId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/genre/[genreId].tsx index 7ea008085..e8ac35dd7 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/genre/[genreId].tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/genre/[genreId].tsx @@ -9,7 +9,7 @@ import JellyseerrPoster from "@/components/posters/JellyseerrPoster"; import { Endpoints, useJellyseerr } from "@/hooks/useJellyseerr"; import { DiscoverSliderType } from "@/utils/jellyseerr/server/constants/discover"; -export default function page() { +export default function JellyseerrGenrePage() { const local = useLocalSearchParams(); const { jellyseerrApi, isJellyseerrMovieOrTvResult } = useJellyseerr(); diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/person/[personId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/person/[personId].tsx index a29e12809..c94a71bd4 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/person/[personId].tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/person/[personId].tsx @@ -11,7 +11,7 @@ import JellyseerrPoster from "@/components/posters/JellyseerrPoster"; import { useJellyseerr } from "@/hooks/useJellyseerr"; import type { PersonCreditCast } from "@/utils/jellyseerr/server/models/Person"; -export default function page() { +export default function JellyseerrPersonPage() { const local = useLocalSearchParams(); const { t } = useTranslation(); diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/channels.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/channels.tsx index 6c9790b59..98b712acc 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/channels.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/channels.tsx @@ -8,7 +8,7 @@ import { ItemImage } from "@/components/common/ItemImage"; import { Text } from "@/components/common/Text"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; -export default function page() { +export default function LiveTvChannelsPage() { const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); const _insets = useSafeAreaInsets(); diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/guide.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/guide.tsx index 390e8eb60..a69318e68 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/guide.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/guide.tsx @@ -17,7 +17,7 @@ const ITEMS_PER_PAGE = 20; const MemoizedLiveTVGuideRow = React.memo(LiveTVGuideRow); -export default function page() { +export default function LiveTvGuidePage() { const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); const insets = useSafeAreaInsets(); diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/recordings.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/recordings.tsx index 9a390162a..cc482c557 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/recordings.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/recordings.tsx @@ -2,7 +2,7 @@ import { useTranslation } from "react-i18next"; import { View } from "react-native"; import { Text } from "@/components/common/Text"; -export default function page() { +export default function LiveTvRecordingsPage() { const { t } = useTranslation(); return ( diff --git a/app/(auth)/(tabs)/(search)/index.tsx b/app/(auth)/(tabs)/(search)/index.tsx index bc431bca7..29461b49a 100644 --- a/app/(auth)/(tabs)/(search)/index.tsx +++ b/app/(auth)/(tabs)/(search)/index.tsx @@ -66,7 +66,7 @@ const exampleSearches = [ "The Mandalorian", ]; -export default function search() { +export default function SearchPage() { const params = useLocalSearchParams(); const insets = useSafeAreaInsets(); const router = useRouter(); @@ -221,7 +221,7 @@ export default function search() { const ids = response1.data.ids; - if (!ids || !ids.length) { + if (!ids?.length) { return []; } diff --git a/app/(auth)/player/direct-player.tsx b/app/(auth)/player/direct-player.tsx index b5d84ff6d..d14bf21fc 100644 --- a/app/(auth)/player/direct-player.tsx +++ b/app/(auth)/player/direct-player.tsx @@ -63,7 +63,7 @@ import { writeToLog } from "@/utils/log"; import { msToTicks, ticksToSeconds } from "@/utils/time"; import { generateDeviceProfile } from "../../../utils/profiles/native"; -export default function page() { +export default function DirectPlayerPage() { const videoRef = useRef(null); const user = useAtomValue(userAtom); const api = useAtomValue(apiAtom); @@ -317,7 +317,7 @@ export default function page() { } let result: Stream | null = null; - if (offline && downloadedItem && downloadedItem.mediaSource) { + if (offline && downloadedItem?.mediaSource) { const url = downloadedItem.videoFilePath; if (item) { result = { diff --git a/biome.json b/biome.json index 67ed64e02..6e51af7d5 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json", + "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json", "files": { "includes": [ "**/*", diff --git a/bun.lock b/bun.lock index 8db5508fa..86e060db7 100644 --- a/bun.lock +++ b/bun.lock @@ -100,7 +100,7 @@ }, "devDependencies": { "@babel/core": "7.28.6", - "@biomejs/biome": "2.3.11", + "@biomejs/biome": "2.4.16", "@react-native-community/cli": "20.1.3", "@react-native-tvos/config-tv": "0.1.6", "@types/jest": "29.5.14", @@ -309,23 +309,23 @@ "@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], - "@biomejs/biome": ["@biomejs/biome@2.3.11", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.11", "@biomejs/cli-darwin-x64": "2.3.11", "@biomejs/cli-linux-arm64": "2.3.11", "@biomejs/cli-linux-arm64-musl": "2.3.11", "@biomejs/cli-linux-x64": "2.3.11", "@biomejs/cli-linux-x64-musl": "2.3.11", "@biomejs/cli-win32-arm64": "2.3.11", "@biomejs/cli-win32-x64": "2.3.11" }, "bin": { "biome": "bin/biome" } }, "sha512-/zt+6qazBWguPG6+eWmiELqO+9jRsMZ/DBU3lfuU2ngtIQYzymocHhKiZRyrbra4aCOoyTg/BmY+6WH5mv9xmQ=="], + "@biomejs/biome": ["@biomejs/biome@2.4.16", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.16", "@biomejs/cli-darwin-x64": "2.4.16", "@biomejs/cli-linux-arm64": "2.4.16", "@biomejs/cli-linux-arm64-musl": "2.4.16", "@biomejs/cli-linux-x64": "2.4.16", "@biomejs/cli-linux-x64-musl": "2.4.16", "@biomejs/cli-win32-arm64": "2.4.16", "@biomejs/cli-win32-x64": "2.4.16" }, "bin": { "biome": "bin/biome" } }, "sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/uXXkBcPKVQY7rc9Ys2CrlirBJYbpESEDme7RKiBD6MmqR2w3j0+ZZXRIL2xiaNPsIMMNhP1YnA+jRRxoOAFrA=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-fh7nnvbweDPm2xEmFjfmq7zSUiox88plgdHF9OIW4i99WnXrAC3o2P3ag9judoUMv8FCSUnlwJCM1B64nO5Fbg=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-l4xkGa9E7Uc0/05qU2lMYfN1H+fzzkHgaJoy98wO+b/7Gl78srbCRRgwYSW+BTLixTBrM6Ede5NSBwt7rd/i6g=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-XPSQ+XIPZMLaZ6zveQdwNjbX+QdROEd1zPgMwD47zvHV+tCGB88VH+aynyGxAHdzL+Tm/+DtKST5SECs4iwCLg=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.11", "", { "os": "linux", "cpu": "x64" }, "sha512-/1s9V/H3cSe0r0Mv/Z8JryF5x9ywRxywomqZVLHAoa/uN0eY7F8gEngWKNS5vbbN/BsfpCG5yeBT5ENh50Frxg=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.16", "", { "os": "linux", "cpu": "x64" }, "sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.11", "", { "os": "linux", "cpu": "x64" }, "sha512-vU7a8wLs5C9yJ4CB8a44r12aXYb8yYgBn+WeyzbMjaCMklzCv1oXr8x+VEyWodgJt9bDmhiaW/I0RHbn7rsNmw=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.16", "", { "os": "linux", "cpu": "x64" }, "sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-PZQ6ElCOnkYapSsysiTy0+fYX+agXPlWugh6+eQ6uPKI3vKAqNp6TnMhoM3oY2NltSB89hz59o8xIfOdyhi9Iw=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.11", "", { "os": "win32", "cpu": "x64" }, "sha512-43VrG813EW+b5+YbDbz31uUsheX+qFKCpXeY9kfdAx+ww3naKxeVkTD9zLIWxUPfJquANMHrmW3wbe/037G0Qg=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.16", "", { "os": "win32", "cpu": "x64" }, "sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw=="], "@bottom-tabs/react-navigation": ["@bottom-tabs/react-navigation@1.2.0", "", { "dependencies": { "color": "^5.0.0" }, "peerDependencies": { "@react-navigation/native": ">=7", "react": "*", "react-native": "*", "react-native-bottom-tabs": "*" } }, "sha512-gEnLP7q9Iai0KlVxHDIdlrDgkvJ5vwPzL2+2ucz5BdPWd++Cf5GO1jPq92R4/85PrioviCZnlAD91Wx8WxPOjA=="], diff --git a/components/PlayButton.tsx b/components/PlayButton.tsx index 5b70ac165..462705d63 100644 --- a/components/PlayButton.tsx +++ b/components/PlayButton.tsx @@ -414,7 +414,7 @@ export const PlayButton: React.FC = ({ ]); const derivedTargetWidth = useDerivedValue(() => { - if (!item || !item.RunTimeTicks) return 0; + if (!item?.RunTimeTicks) return 0; const userData = item.UserData; if (userData?.PlaybackPositionTicks) { return userData.PlaybackPositionTicks > 0 diff --git a/components/PlayButton.tv.tsx b/components/PlayButton.tv.tsx index 2486f4241..c8b6b76e3 100644 --- a/components/PlayButton.tv.tsx +++ b/components/PlayButton.tv.tsx @@ -78,7 +78,7 @@ export const PlayButton: React.FC = ({ }; const derivedTargetWidth = useDerivedValue(() => { - if (!item || !item.RunTimeTicks) return 0; + if (!item?.RunTimeTicks) return 0; const userData = item.UserData; if (userData?.PlaybackPositionTicks) { return userData.PlaybackPositionTicks > 0 diff --git a/components/downloads/DownloadCard.tsx b/components/downloads/DownloadCard.tsx index 66f2a81b1..c67f60583 100644 --- a/components/downloads/DownloadCard.tsx +++ b/components/downloads/DownloadCard.tsx @@ -116,7 +116,7 @@ export const DownloadCard = ({ process, ...props }: DownloadCardProps) => { }, [process?.progress]); // Return null after all hooks have been called - if (!process || !process.item || !process.item.Id) { + if (!process?.item?.Id) { return null; } diff --git a/components/home/TVHeroCarousel.tsx b/components/home/TVHeroCarousel.tsx index 74ae8af37..11339e0c3 100644 --- a/components/home/TVHeroCarousel.tsx +++ b/components/home/TVHeroCarousel.tsx @@ -351,7 +351,7 @@ export const TVHeroCarousel: React.FC = ({ // Get subtitle for episodes const episodeSubtitle = useMemo(() => { - if (!activeItem || activeItem.Type !== "Episode") return null; + if (activeItem?.Type !== "Episode") return null; return `S${activeItem.ParentIndexNumber} E${activeItem.IndexNumber} ยท ${activeItem.Name}`; }, [activeItem]); diff --git a/components/livetv/TVChannelCard.tsx b/components/livetv/TVChannelCard.tsx index 7ac47b71d..95f337db5 100644 --- a/components/livetv/TVChannelCard.tsx +++ b/components/livetv/TVChannelCard.tsx @@ -180,4 +180,4 @@ const styles = StyleSheet.create({ }, }); -export { CARD_WIDTH, CARD_HEIGHT }; +export { CARD_HEIGHT, CARD_WIDTH }; diff --git a/components/livetv/TVLiveTVGuide.tsx b/components/livetv/TVLiveTVGuide.tsx index 7c1f12f64..ad99626f6 100644 --- a/components/livetv/TVLiveTVGuide.tsx +++ b/components/livetv/TVLiveTVGuide.tsx @@ -155,7 +155,7 @@ export const TVLiveTVGuide: React.FC = () => { ); // Fetch programs for visible channels - const { data: programsData, isLoading: isLoadingPrograms } = useQuery({ + const { data: programsData } = useQuery({ queryKey: [ "livetv", "tv-guide", diff --git a/components/login/TVPasswordEntryModal.tsx b/components/login/TVPasswordEntryModal.tsx index 9efa9d0cb..596bb610a 100644 --- a/components/login/TVPasswordEntryModal.tsx +++ b/components/login/TVPasswordEntryModal.tsx @@ -14,6 +14,7 @@ import { } from "react-native"; import { Text } from "@/components/common/Text"; import { useTVFocusAnimation } from "@/components/tv"; +import { useTVBackPress } from "@/hooks/useTVBackPress"; import { scaleSize } from "@/utils/scaleSize"; interface TVPasswordEntryModalProps { @@ -201,6 +202,13 @@ export const TVPasswordEntryModal: React.FC = ({ setIsReady(false); }, [visible]); + // Close the modal on the TV remote back/menu button while it is open. + useTVBackPress(() => { + if (!visible) return false; + onClose(); + return true; + }, [visible, onClose]); + const handleSubmit = async () => { if (!password) { setError(t("password.enter_password")); diff --git a/components/video-player/controls/Controls.tv.tsx b/components/video-player/controls/Controls.tv.tsx index 2e7fab497..657f2a8cd 100644 --- a/components/video-player/controls/Controls.tv.tsx +++ b/components/video-player/controls/Controls.tv.tsx @@ -59,6 +59,7 @@ import { useRemoteControl } from "./hooks/useRemoteControl"; import { useVideoTime } from "./hooks/useVideoTime"; import { TechnicalInfoOverlay } from "./TechnicalInfoOverlay"; import { TrickplayBubble } from "./TrickplayBubble"; +import type { Track } from "./types"; import { useControlsTimeout } from "./useControlsTimeout"; interface Props { @@ -315,6 +316,31 @@ export const Controls: FC = ({ [onSubtitleIndexChange], ); + // Re-fetch subtitle streams from the server (e.g. after a server-side + // download) and map them to the modal's Track shape. setTrack drives the + // player through the same handler used for manual subtitle selection. + const refreshSubtitleTracks = useCallback(async (): Promise => { + try { + const streams = (await onRefreshSubtitleTracks?.()) ?? []; + // Skip streams without a real index: `?? -1` would alias them to the + // "disable subtitles" sentinel and mis-route selection. + return streams + .filter((stream) => typeof stream.Index === "number") + .map((stream) => { + const index = stream.Index as number; + return { + name: + stream.DisplayTitle || + `${stream.Language || "Unknown"} (${stream.Codec})`, + index, + setTrack: () => onSubtitleIndexChange?.(index), + }; + }); + } catch { + return []; + } + }, [onRefreshSubtitleTracks, onSubtitleIndexChange]); + const { trickPlayUrl, calculateTrickplayUrl, @@ -572,6 +598,9 @@ export const Controls: FC = ({ disableTrack?.setTrack(); }, onLocalSubtitleDownloaded: handleLocalSubtitleDownloaded, + refreshSubtitleTracks: onRefreshSubtitleTracks + ? refreshSubtitleTracks + : undefined, }); controlsInteractionRef.current(); }, [ @@ -581,6 +610,8 @@ export const Controls: FC = ({ videoContextSubtitleTracks, subtitleIndex, handleLocalSubtitleDownloaded, + onRefreshSubtitleTracks, + refreshSubtitleTracks, ]); const handleToggleTechnicalInfo = useCallback(() => { diff --git a/components/video-player/controls/types.ts b/components/video-player/controls/types.ts index 30f277aa4..ca2ea1413 100644 --- a/components/video-player/controls/types.ts +++ b/components/video-player/controls/types.ts @@ -28,4 +28,4 @@ type Track = { localPath?: string; }; -export type { EmbeddedSubtitle, ExternalSubtitle, TranscodedSubtitle, Track }; +export type { EmbeddedSubtitle, ExternalSubtitle, Track, TranscodedSubtitle }; diff --git a/hooks/usePlaybackManager.ts b/hooks/usePlaybackManager.ts index 8387511f5..b4316241a 100644 --- a/hooks/usePlaybackManager.ts +++ b/hooks/usePlaybackManager.ts @@ -80,7 +80,7 @@ export const usePlaybackManager = ({ const { data: adjacentItems } = useQuery({ queryKey: ["adjacentItems", item?.Id, item?.SeriesId, isOffline], queryFn: async (): Promise => { - if (!item || !item.SeriesId) { + if (!item?.SeriesId) { return null; } diff --git a/hooks/useSessions.ts b/hooks/useSessions.ts index 5aba65159..108441c0e 100644 --- a/hooks/useSessions.ts +++ b/hooks/useSessions.ts @@ -21,7 +21,7 @@ export const useSessions = ({ const { data, isLoading } = useQuery({ queryKey: ["sessions"], queryFn: async () => { - if (!api || !user || !user.Policy?.IsAdministrator) { + if (!api || !user?.Policy?.IsAdministrator) { return []; } const response = await getSessionApi(api).getSessions({ @@ -55,7 +55,7 @@ export const useAllSessions = ({ const { data, isLoading } = useQuery({ queryKey: ["allSessions"], queryFn: async () => { - if (!api || !user || !user.Policy?.IsAdministrator) { + if (!api || !user?.Policy?.IsAdministrator) { return []; } const response = await getSessionApi(api).getSessions({ diff --git a/package.json b/package.json index bbd5393d6..c032a35d2 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ }, "devDependencies": { "@babel/core": "7.28.6", - "@biomejs/biome": "2.3.11", + "@biomejs/biome": "2.4.16", "@react-native-community/cli": "20.1.3", "@react-native-tvos/config-tv": "0.1.6", "@types/jest": "29.5.14", diff --git a/providers/WebSocketProvider.tsx b/providers/WebSocketProvider.tsx index bb9d1d1ff..41af25cf6 100644 --- a/providers/WebSocketProvider.tsx +++ b/providers/WebSocketProvider.tsx @@ -122,7 +122,7 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => { const handlePlayCommand = useCallback( (data: any) => { - if (!data || !data.ItemIds || !data.ItemIds.length) { + if (!data?.ItemIds?.length) { return; } @@ -150,7 +150,7 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => { }, [connectWebSocket]); useEffect(() => { - if (!deviceId || !api || !api?.accessToken || !isNetworkConnected) { + if (!deviceId || !api?.accessToken || !isNetworkConnected) { return; } diff --git a/utils/streamRanker.ts b/utils/streamRanker.ts index 8121adea9..242cf950d 100644 --- a/utils/streamRanker.ts +++ b/utils/streamRanker.ts @@ -156,4 +156,4 @@ class StreamRanker { } } -export { StreamRanker, SubtitleStreamRanker, AudioStreamRanker }; +export { AudioStreamRanker, StreamRanker, SubtitleStreamRanker };