diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/series/[id].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/series/[id].tsx index a62405e1..9a284d33 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/series/[id].tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/series/[id].tsx @@ -15,7 +15,7 @@ import { Image } from "expo-image"; import { useLocalSearchParams, useNavigation } from "expo-router"; import { useAtom } from "jotai"; import React, { useEffect, useMemo } from "react"; -import { View } from "react-native"; +import { Platform, View } from "react-native"; import { useTranslation } from "react-i18next"; const page: React.FC = () => { @@ -85,21 +85,25 @@ const page: React.FC = () => { allEpisodes.length > 0 && ( - ( - - )} - DownloadedIconComponent={() => ( - + ( + + )} + DownloadedIconComponent={() => ( + + )} /> - )} - /> + + )} ), }); diff --git a/app/(auth)/player/_layout.tsx b/app/(auth)/player/_layout.tsx index bee527e5..0eefb300 100644 --- a/app/(auth)/player/_layout.tsx +++ b/app/(auth)/player/_layout.tsx @@ -3,16 +3,21 @@ import React, { useEffect } from "react"; import { SystemBars } from "react-native-edge-to-edge"; import * as ScreenOrientation from "@/packages/expo-screen-orientation"; import { useSettings } from "@/utils/atoms/settings"; +import { Platform } from "react-native"; export default function Layout() { const [settings] = useSettings(); useEffect(() => { + if (Platform.isTV) return; + if (settings.defaultVideoOrientation) { ScreenOrientation.lockAsync(settings.defaultVideoOrientation); } return () => { + if (Platform.isTV) return; + if (settings.autoRotate === true) { ScreenOrientation.unlockAsync(); } else { diff --git a/app/_layout.tsx b/app/_layout.tsx index 0d061be5..ad84ba0c 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -270,6 +270,7 @@ function Layout() { useEffect(() => { // If the user has auto rotate enabled, unlock the orientation + if (Platform.isTV) return; if (settings.autoRotate === true) { ScreenOrientation.unlockAsync(); } else { diff --git a/bun.lock b/bun.lock index 33b28594..74136017 100644 --- a/bun.lock +++ b/bun.lock @@ -60,7 +60,7 @@ "react-i18next": "^15.4.0", "react-native": "npm:react-native-tvos@~0.77.0-0", "react-native-awesome-slider": "^2.9.0", - "react-native-bottom-tabs": "0.8.7", + "react-native-bottom-tabs": "0.8.6", "react-native-circular-progress": "^1.4.1", "react-native-compressor": "^1.10.3", "react-native-country-flag": "^2.0.2", @@ -1826,7 +1826,7 @@ "react-native-awesome-slider": ["react-native-awesome-slider@2.9.0", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-gesture-handler": ">=2.0.0", "react-native-reanimated": ">=3.0.0" } }, "sha512-sc5qgX4YtM6IxjtosjgQLdsal120MvU+YWs0F2MdgQWijps22AXLDCUoBnZZ8vxVhVyJ2WnnIPrmtVBvVJjSuQ=="], - "react-native-bottom-tabs": ["react-native-bottom-tabs@0.8.7", "", { "dependencies": { "react-freeze": "^1.0.0", "sf-symbols-typescript": "^2.0.0", "use-latest-callback": "^0.2.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-cVQYs4r8Hb9V9oOO/SqsmBaZ7IzE/3Tpvz4mmRjNXKi1cBWC+ZpKTuqRx6EPjBCYTVK+vbAfoTM6IHS+6NVg4w=="], + "react-native-bottom-tabs": ["react-native-bottom-tabs@0.8.6", "", { "dependencies": { "sf-symbols-typescript": "^2.0.0", "use-latest-callback": "^0.2.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-N5b3MoSfsEqlmvFyIyL0X0bd+QAtB+cXH1rl/+R2Kr0BefBTC7ZldGcPhgK3FhBbt0vJDpd3kLb/dvmqZd+Eag=="], "react-native-circular-progress": ["react-native-circular-progress@1.4.1", "", { "dependencies": { "prop-types": "^15.8.1" }, "peerDependencies": { "react": ">=16.0.0", "react-native": ">=0.50.0", "react-native-svg": ">=7.0.0" } }, "sha512-HEzvI0WPuWvsCgWE3Ff2HBTMgAEQB2GvTFw0KHyD/t1STAlDDRiolu0mEGhVvihKR3jJu3v3V4qzvSklY/7XzQ=="], diff --git a/components/DownloadItem.tsx b/components/DownloadItem.tsx index befc34ca..e7286023 100644 --- a/components/DownloadItem.tsx +++ b/components/DownloadItem.tsx @@ -2,7 +2,7 @@ import { useRemuxHlsToMp4 } from "@/hooks/useRemuxHlsToMp4"; import { useDownload } from "@/providers/DownloadProvider"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { queueActions, queueAtom } from "@/utils/atoms/queue"; -import {DownloadMethod, useSettings} from "@/utils/atoms/settings"; +import { DownloadMethod, useSettings } from "@/utils/atoms/settings"; import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings"; import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl"; import { saveDownloadItemInfoToDiskTmp } from "@/utils/optimize-server"; @@ -21,7 +21,7 @@ import { import { Href, router, useFocusEffect } from "expo-router"; import { useAtom } from "jotai"; import React, { useCallback, useMemo, useRef, useState } from "react"; -import { Alert, View, ViewProps } from "react-native"; +import { Alert, Platform, View, ViewProps } from "react-native"; import { toast } from "sonner-native"; import { AudioTrackSelector } from "./AudioTrackSelector"; import { Bitrate, BitrateSelector } from "./BitrateSelector"; @@ -66,10 +66,12 @@ export const DownloadItems: React.FC = ({ const [selectedAudioStream, setSelectedAudioStream] = useState(-1); const [selectedSubtitleStream, setSelectedSubtitleStream] = useState(0); - const [maxBitrate, setMaxBitrate] = useState(settings?.defaultBitrate ?? { - key: "Max", - value: undefined, - }); + const [maxBitrate, setMaxBitrate] = useState( + settings?.defaultBitrate ?? { + key: "Max", + value: undefined, + } + ); const userCanDownload = useMemo( () => user?.Policy?.EnableContentDownloading, @@ -162,7 +164,9 @@ export const DownloadItems: React.FC = ({ ); } } else { - toast.error(t("home.downloads.toasts.you_are_not_allowed_to_download_files")); + toast.error( + t("home.downloads.toasts.you_are_not_allowed_to_download_files") + ); } }, [ queue, @@ -333,7 +337,10 @@ export const DownloadItems: React.FC = ({ {title} - {subtitle || t("item_card.download.download_x_item", {item_count: itemsNotDownloaded.length})} + {subtitle || + t("item_card.download.download_x_item", { + item_count: itemsNotDownloaded.length, + })} @@ -391,12 +398,16 @@ export const DownloadSingleItem: React.FC<{ size?: "default" | "large"; item: BaseItemDto; }> = ({ item, size = "default" }) => { + if (Platform.isTV) return; + return ( ( diff --git a/components/ItemContent.tsx b/components/ItemContent.tsx index 39aa1660..8fb538dd 100644 --- a/components/ItemContent.tsx +++ b/components/ItemContent.tsx @@ -15,6 +15,7 @@ import { SeasonEpisodesCarousel } from "@/components/series/SeasonEpisodesCarous import useDefaultPlaySettings from "@/hooks/useDefaultPlaySettings"; import { useImageColors } from "@/hooks/useImageColors"; import { useOrientation } from "@/hooks/useOrientation"; +import * as ScreenOrientation from "@/packages/expo-screen-orientation"; import { apiAtom } from "@/providers/JellyfinProvider"; import { useSettings } from "@/utils/atoms/settings"; import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById"; @@ -24,17 +25,16 @@ import { } from "@jellyfin/sdk/lib/generated-client/models"; import { Image } from "expo-image"; import { useNavigation } from "expo-router"; -import * as ScreenOrientation from "@/packages/expo-screen-orientation"; import { useAtom } from "jotai"; import React, { useEffect, useMemo, useState } from "react"; import { Platform, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; -const Chromecast = !Platform.isTV ? require("./Chromecast") : null; +import { AddToFavorites } from "./AddToFavorites"; import { ItemHeader } from "./ItemHeader"; import { ItemTechnicalDetails } from "./ItemTechnicalDetails"; import { MediaSourceSelector } from "./MediaSourceSelector"; import { MoreMoviesWithActor } from "./MoreMoviesWithActor"; -import { AddToFavorites } from "./AddToFavorites"; +const Chromecast = !Platform.isTV ? require("./Chromecast") : null; export type SelectedOptions = { bitrate: Bitrate; @@ -94,7 +94,9 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo( /> {item.Type !== "Program" && ( - + {!Platform.isTV && ( + + )} @@ -164,7 +166,6 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo( } > - {/* {!Platform.isTV && ( */} {item.Type !== "Program" && !Platform.isTV && ( @@ -222,13 +223,11 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo( )} - {/* {!Platform.isTV && ( */} - {/* )} */} {item.Type === "Episode" && ( diff --git a/components/PlayButton.tsx b/components/PlayButton.tsx index 66d1c434..ff8ecab4 100644 --- a/components/PlayButton.tsx +++ b/components/PlayButton.tsx @@ -1,4 +1,4 @@ -import { Platform } from "react-native"; +import { Platform, Pressable } from "react-native"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { itemThemeColorAtom } from "@/utils/atoms/primaryColor"; import { useSettings } from "@/utils/atoms/settings"; @@ -79,6 +79,7 @@ export const PlayButton: React.FC = ({ ); const onPress = useCallback(async () => { + console.log("onPress"); if (!item) return; lightHapticFeedback(); @@ -113,105 +114,103 @@ export const PlayButton: React.FC = ({ switch (selectedIndex) { case 0: - if (!Platform.isTV) { - await CastContext.getPlayServicesState().then(async (state) => { - if (state && state !== PlayServicesState.SUCCESS) { - CastContext.showPlayServicesErrorDialog(state); - } else { - // Get a new URL with the Chromecast device profile: - try { - const data = await getStreamUrl({ - api, - item, - deviceProfile: chromecastProfile, - startTimeTicks: item?.UserData?.PlaybackPositionTicks!, - userId: user?.Id, - audioStreamIndex: selectedOptions.audioIndex, - maxStreamingBitrate: selectedOptions.bitrate?.value, - mediaSourceId: selectedOptions.mediaSource?.Id, - subtitleStreamIndex: selectedOptions.subtitleIndex, - }); + await CastContext.getPlayServicesState().then(async (state) => { + if (state && state !== PlayServicesState.SUCCESS) { + CastContext.showPlayServicesErrorDialog(state); + } else { + // Get a new URL with the Chromecast device profile: + try { + const data = await getStreamUrl({ + api, + item, + deviceProfile: chromecastProfile, + startTimeTicks: item?.UserData?.PlaybackPositionTicks!, + userId: user?.Id, + audioStreamIndex: selectedOptions.audioIndex, + maxStreamingBitrate: selectedOptions.bitrate?.value, + mediaSourceId: selectedOptions.mediaSource?.Id, + subtitleStreamIndex: selectedOptions.subtitleIndex, + }); - if (!data?.url) { - console.warn("No URL returned from getStreamUrl", data); - Alert.alert( - t("player.client_error"), - t("player.could_not_create_stream_for_chromecast") - ); - return; - } - - client - .loadMedia({ - mediaInfo: { - contentUrl: data?.url, - contentType: "video/mp4", - metadata: - item.Type === "Episode" - ? { - type: "tvShow", - title: item.Name || "", - episodeNumber: item.IndexNumber || 0, - seasonNumber: item.ParentIndexNumber || 0, - seriesTitle: item.SeriesName || "", - images: [ - { - url: getParentBackdropImageUrl({ - api, - item, - quality: 90, - width: 2000, - })!, - }, - ], - } - : item.Type === "Movie" - ? { - type: "movie", - title: item.Name || "", - subtitle: item.Overview || "", - images: [ - { - url: getPrimaryImageUrl({ - api, - item, - quality: 90, - width: 2000, - })!, - }, - ], - } - : { - type: "generic", - title: item.Name || "", - subtitle: item.Overview || "", - images: [ - { - url: getPrimaryImageUrl({ - api, - item, - quality: 90, - width: 2000, - })!, - }, - ], - }, - }, - startTime: 0, - }) - .then(() => { - // state is already set when reopening current media, so skip it here. - if (isOpeningCurrentlyPlayingMedia) { - return; - } - CastContext.showExpandedControls(); - }); - } catch (e) { - console.log(e); + if (!data?.url) { + console.warn("No URL returned from getStreamUrl", data); + Alert.alert( + t("player.client_error"), + t("player.could_not_create_stream_for_chromecast") + ); + return; } + + client + .loadMedia({ + mediaInfo: { + contentUrl: data?.url, + contentType: "video/mp4", + metadata: + item.Type === "Episode" + ? { + type: "tvShow", + title: item.Name || "", + episodeNumber: item.IndexNumber || 0, + seasonNumber: item.ParentIndexNumber || 0, + seriesTitle: item.SeriesName || "", + images: [ + { + url: getParentBackdropImageUrl({ + api, + item, + quality: 90, + width: 2000, + })!, + }, + ], + } + : item.Type === "Movie" + ? { + type: "movie", + title: item.Name || "", + subtitle: item.Overview || "", + images: [ + { + url: getPrimaryImageUrl({ + api, + item, + quality: 90, + width: 2000, + })!, + }, + ], + } + : { + type: "generic", + title: item.Name || "", + subtitle: item.Overview || "", + images: [ + { + url: getPrimaryImageUrl({ + api, + item, + quality: 90, + width: 2000, + })!, + }, + ], + }, + }, + startTime: 0, + }) + .then(() => { + // state is already set when reopening current media, so skip it here. + if (isOpeningCurrentlyPlayingMedia) { + return; + } + CastContext.showExpandedControls(); + }); + } catch (e) { + console.log(e); } - }); - } + } + }); break; case 1: goToPlayer(queryString, selectedOptions.bitrate?.value); @@ -323,75 +322,62 @@ export const PlayButton: React.FC = ({ */ return ( - - - - - - + + - - - - {runtimeTicksToMinutes(item?.RunTimeTicks)} - + + + + + + + {runtimeTicksToMinutes(item?.RunTimeTicks)} + + + + + {client && ( - + + - {client && ( - - - - - )} - {!client && settings?.openInVLC && ( - - - - )} - + )} + {!client && settings?.openInVLC && ( + + + + )} - - {/* - - - {directStream ? "Direct stream" : "Transcoded stream"} - - */} - + + ); }; diff --git a/components/PlayButton.tv.tsx b/components/PlayButton.tv.tsx index 128c2184..1fb0563c 100644 --- a/components/PlayButton.tv.tsx +++ b/components/PlayButton.tv.tsx @@ -63,7 +63,8 @@ export const PlayButton: React.FC = ({ [router] ); - const onPress = useCallback(async () => { + const onPress = () => { + console.log("onpress"); if (!item) return; lightHapticFeedback(); @@ -79,15 +80,7 @@ export const PlayButton: React.FC = ({ const queryString = queryParams.toString(); goToPlayer(queryString, selectedOptions.bitrate?.value); return; - }, [ - item, - settings, - api, - user, - router, - showActionSheetWithOptions, - selectedOptions, - ]); + }; const derivedTargetWidth = useDerivedValue(() => { if (!item || !item.RunTimeTicks) return 0; @@ -179,69 +172,55 @@ export const PlayButton: React.FC = ({ */ return ( - - - - - - + + - - - - {runtimeTicksToMinutes(item?.RunTimeTicks)} - + + + + + + + {runtimeTicksToMinutes(item?.RunTimeTicks)} + + + + + {settings?.openInVLC && ( - + - {settings?.openInVLC && ( - - - - )} - + )} - - {/* - - - {directStream ? "Direct stream" : "Transcoded stream"} - - */} - + + ); }; diff --git a/components/common/Text.tsx b/components/common/Text.tsx index ef7a6491..624b9da6 100644 --- a/components/common/Text.tsx +++ b/components/common/Text.tsx @@ -1,19 +1,27 @@ import React from "react"; -import { TextProps } from "react-native"; +import { Platform, TextProps } from "react-native"; import { UITextView } from "react-native-uitextview"; - +import { Text as RNText } from "react-native"; export function Text( props: TextProps & { uiTextView?: boolean; } ) { const { style, ...otherProps } = props; - - return ( - - ); + if (Platform.isTV) + return ( + + ); + else + return ( + + ); } diff --git a/components/downloads/ActiveDownloads.tsx b/components/downloads/ActiveDownloads.tsx index 7b6316f8..773efab4 100644 --- a/components/downloads/ActiveDownloads.tsx +++ b/components/downloads/ActiveDownloads.tsx @@ -1,16 +1,15 @@ import { Text } from "@/components/common/Text"; import { useDownload } from "@/providers/DownloadProvider"; -import {DownloadMethod, useSettings} from "@/utils/atoms/settings"; +import { DownloadMethod, useSettings } from "@/utils/atoms/settings"; +import { storage } from "@/utils/mmkv"; import { JobStatus } from "@/utils/optimize-server"; import { formatTimeString } from "@/utils/time"; import { Ionicons } from "@expo/vector-icons"; -const BackGroundDownloader = !Platform.isTV - ? require("@kesha-antonov/react-native-background-downloader") - : null; import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { Image } from "expo-image"; import { useRouter } from "expo-router"; -const FFmpegKitProvider = !Platform.isTV ? require("ffmpeg-kit-react-native") : null; -import { useAtom } from "jotai"; +import { t } from "i18next"; +import { useMemo } from "react"; import { ActivityIndicator, Platform, @@ -21,10 +20,12 @@ import { } from "react-native"; import { toast } from "sonner-native"; import { Button } from "../Button"; -import { Image } from "expo-image"; -import { useMemo } from "react"; -import { storage } from "@/utils/mmkv"; -import { t } from "i18next"; +const BackGroundDownloader = !Platform.isTV + ? require("@kesha-antonov/react-native-background-downloader") + : null; +const FFmpegKitProvider = !Platform.isTV + ? require("ffmpeg-kit-react-native") + : null; interface Props extends ViewProps {} @@ -33,14 +34,20 @@ export const ActiveDownloads: React.FC = ({ ...props }) => { if (processes?.length === 0) return ( - {t("home.downloads.active_download")} - {t("home.downloads.no_active_downloads")} + + {t("home.downloads.active_download")} + + + {t("home.downloads.no_active_downloads")} + ); return ( - {t("home.downloads.active_downloads")} + + {t("home.downloads.active_downloads")} + {processes?.map((p: JobStatus) => ( @@ -81,7 +88,9 @@ const DownloadCard = ({ process, ...props }: DownloadCardProps) => { } } else { FFmpegKitProvider.FFmpegKit.cancel(Number(id)); - setProcesses((prev: any[]) => prev.filter((p: { id: string; }) => p.id !== id)); + setProcesses((prev: any[]) => + prev.filter((p: { id: string }) => p.id !== id) + ); } }, onSuccess: () => { @@ -156,7 +165,9 @@ const DownloadCard = ({ process, ...props }: DownloadCardProps) => { {process.speed?.toFixed(2)}x )} {eta(process) && ( - {t("home.downloads.eta", {eta: eta(process)})} + + {t("home.downloads.eta", { eta: eta(process) })} + )} diff --git a/components/video-player/controls/Controls.tsx b/components/video-player/controls/Controls.tsx index 3232965d..17bd2028 100644 --- a/components/video-player/controls/Controls.tsx +++ b/components/video-player/controls/Controls.tsx @@ -540,17 +540,19 @@ export const Controls: React.FC = ({ pointerEvents={showControls ? "auto" : "none"} className={`flex flex-row w-full pt-2`} > - - - - - + {!Platform.isTV && ( + + + + + + )} {!Platform.isTV && ( diff --git a/hooks/useRemuxHlsToMp4.ts b/hooks/useRemuxHlsToMp4.ts index 925d9778..22ea02ce 100644 --- a/hooks/useRemuxHlsToMp4.ts +++ b/hooks/useRemuxHlsToMp4.ts @@ -11,7 +11,9 @@ import * as FileSystem from "expo-file-system"; import { useRouter } from "expo-router"; // import { FFmpegKit, FFmpegSession, Statistics } from "ffmpeg-kit-react-native"; -const FFMPEGKitReactNative = !Platform.isTV ? require("ffmpeg-kit-react-native") : null; +const FFMPEGKitReactNative = !Platform.isTV + ? require("ffmpeg-kit-react-native") + : null; import { useAtomValue } from "jotai"; import { useCallback } from "react"; import { toast } from "sonner-native"; @@ -24,8 +26,10 @@ import { Platform } from "react-native"; import { useTranslation } from "react-i18next"; type FFmpegSession = typeof FFMPEGKitReactNative.FFmpegSession; -type Statistics = typeof FFMPEGKitReactNative.Statistics -const FFmpegKit = FFMPEGKitReactNative.FFmpegKit; +type Statistics = typeof FFMPEGKitReactNative.Statistics; +const FFmpegKit = Platform.isTV + ? null + : (FFMPEGKitReactNative.FFmpegKit as typeof FFMPEGKitReactNative.FFmpegKit); const createFFmpegCommand = (url: string, output: string) => [ "-y", // overwrite output files without asking "-thread_queue_size 512", // https://ffmpeg.org/ffmpeg.html#toc-Advanced-options @@ -101,7 +105,10 @@ export const useRemuxHlsToMp4 = () => { } setProcesses((prev: any[]) => { - return prev.filter((process: { itemId: string | undefined; }) => process.itemId !== item.Id); + return prev.filter( + (process: { itemId: string | undefined }) => + process.itemId !== item.Id + ); }); } catch (e) { console.error(e); @@ -126,7 +133,7 @@ export const useRemuxHlsToMp4 = () => { if (!item.Id) throw new Error("Item is undefined"); setProcesses((prev: any[]) => { - return prev.map((process: { itemId: string | undefined; }) => { + return prev.map((process: { itemId: string | undefined }) => { if (process.itemId === item.Id) { return { ...process, @@ -161,15 +168,18 @@ export const useRemuxHlsToMp4 = () => { // First lets save any important assets we want to present to the user offline await onSaveAssets(api, item); - toast.success(t("home.downloads.toasts.download_started_for", {item: item.Name}), { - action: { - label: "Go to download", - onClick: () => { - router.push("/downloads"); - toast.dismiss(); + toast.success( + t("home.downloads.toasts.download_started_for", { item: item.Name }), + { + action: { + label: "Go to download", + onClick: () => { + router.push("/downloads"); + toast.dismiss(); + }, }, - }, - }); + } + ); try { const job: JobStatus = { @@ -201,7 +211,10 @@ export const useRemuxHlsToMp4 = () => { Error: ${error.message}, Stack: ${error.stack}` ); setProcesses((prev: any[]) => { - return prev.filter((process: { itemId: string | undefined; }) => process.itemId !== item.Id); + return prev.filter( + (process: { itemId: string | undefined }) => + process.itemId !== item.Id + ); }); throw error; // Re-throw the error to propagate it to the caller } diff --git a/modules/vlc-player/ios/VlcPlayer.podspec b/modules/vlc-player/ios/VlcPlayer.podspec index 97f58881..25f2d73e 100644 --- a/modules/vlc-player/ios/VlcPlayer.podspec +++ b/modules/vlc-player/ios/VlcPlayer.podspec @@ -5,7 +5,7 @@ Pod::Spec.new do |s| s.description = 'A sample project description' s.author = '' s.homepage = 'https://docs.expo.dev/modules/' - s.platforms = { :ios => '13.4', :tvos => '13.4' } + s.platforms = { :ios => '13.4', :tvos => '16' } s.source = { git: '' } s.static_framework = true diff --git a/package.json b/package.json index a04b6b4b..074e60bd 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "react-i18next": "^15.4.0", "react-native": "npm:react-native-tvos@~0.77.0-0", "react-native-awesome-slider": "^2.9.0", - "react-native-bottom-tabs": "0.8.7", + "react-native-bottom-tabs": "0.8.6", "react-native-circular-progress": "^1.4.1", "react-native-compressor": "^1.10.3", "react-native-country-flag": "^2.0.2",