mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-03-04 16:56:16 +00:00
fix: conditionals for tv to build / run
This commit is contained in:
@@ -23,7 +23,7 @@ export const Tag: React.FC<
|
||||
textStyle?: StyleProp<TextStyle>;
|
||||
} & ViewProps
|
||||
> = ({ text, textClass, textStyle, ...props }) => {
|
||||
if (Platform.OS === "ios") {
|
||||
if (Platform.OS === "ios" && !Platform.isTV) {
|
||||
return (
|
||||
<View>
|
||||
<GlassEffectView style={styles.glass}>
|
||||
|
||||
@@ -1,50 +1,125 @@
|
||||
import React, { useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import {
|
||||
Animated,
|
||||
Easing,
|
||||
Platform,
|
||||
Pressable,
|
||||
TextInput,
|
||||
type TextInputProps,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
|
||||
interface InputProps extends TextInputProps {
|
||||
extraClassName?: string; // new prop for additional classes
|
||||
extraClassName?: string;
|
||||
}
|
||||
|
||||
export function Input(props: InputProps) {
|
||||
const { style, extraClassName = "", ...otherProps } = props;
|
||||
const inputRef = React.useRef<TextInput>(null);
|
||||
const inputRef = useRef<TextInput>(null);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const scale = useRef(new Animated.Value(1)).current;
|
||||
|
||||
return Platform.isTV ? (
|
||||
<TouchableOpacity
|
||||
onPress={() => inputRef?.current?.focus?.()}
|
||||
activeOpacity={1}
|
||||
>
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
className={`
|
||||
w-full text-lg px-5 py-4 rounded-2xl
|
||||
${isFocused ? "bg-neutral-700 border-2 border-white" : "bg-neutral-900 border-2 border-transparent"}
|
||||
text-white ${extraClassName}
|
||||
`}
|
||||
allowFontScaling={false}
|
||||
style={[
|
||||
style,
|
||||
{
|
||||
backgroundColor: isFocused ? "#ffffff88" : "#8f8d8d88",
|
||||
},
|
||||
]}
|
||||
placeholderTextColor={"#ffffffff"}
|
||||
clearButtonMode='while-editing'
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
{...otherProps}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
const animateFocus = (focused: boolean) => {
|
||||
Animated.timing(scale, {
|
||||
toValue: focused ? 1.02 : 1,
|
||||
duration: 150,
|
||||
easing: Easing.out(Easing.quad),
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
};
|
||||
|
||||
const handleFocus = () => {
|
||||
setIsFocused(true);
|
||||
animateFocus(true);
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
setIsFocused(false);
|
||||
animateFocus(false);
|
||||
};
|
||||
|
||||
if (Platform.isTV) {
|
||||
return (
|
||||
<Pressable
|
||||
onPress={() => inputRef.current?.focus()}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
>
|
||||
<Animated.View
|
||||
style={{
|
||||
transform: [{ scale }],
|
||||
}}
|
||||
>
|
||||
{/* Outer glow when focused */}
|
||||
{isFocused && (
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: -4,
|
||||
left: -4,
|
||||
right: -4,
|
||||
bottom: -4,
|
||||
backgroundColor: "#9334E9",
|
||||
borderRadius: 18,
|
||||
opacity: 0.5,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: isFocused ? "#2a2a2a" : "#1a1a1a",
|
||||
borderWidth: 3,
|
||||
borderColor: isFocused ? "#FFFFFF" : "#333333",
|
||||
borderRadius: 14,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{/* Purple accent bar at top when focused */}
|
||||
{isFocused && (
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 3,
|
||||
backgroundColor: "#9334E9",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
allowFontScaling={false}
|
||||
placeholderTextColor={isFocused ? "#AAAAAA" : "#666666"}
|
||||
style={[
|
||||
{
|
||||
height: 60,
|
||||
fontSize: 22,
|
||||
fontWeight: "500",
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: isFocused ? 4 : 0,
|
||||
color: "#FFFFFF",
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
style,
|
||||
]}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
{...otherProps}
|
||||
/>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
// Mobile version unchanged
|
||||
return (
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
className='p-4 rounded-xl bg-neutral-900'
|
||||
className={`p-4 rounded-xl bg-neutral-900 ${extraClassName}`}
|
||||
allowFontScaling={false}
|
||||
style={[{ color: "white" }, style]}
|
||||
placeholderTextColor={"#9CA3AF"}
|
||||
|
||||
@@ -243,7 +243,7 @@ export const MiniPlayerBar: React.FC = () => {
|
||||
]}
|
||||
>
|
||||
<Animated.View style={[styles.touchable, animatedBarStyle]}>
|
||||
{Platform.OS === "ios" ? (
|
||||
{Platform.OS === "ios" && !Platform.isTV ? (
|
||||
<GlassEffectView style={styles.blurContainer}>
|
||||
<View
|
||||
style={{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as Location from "expo-location";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { getSSID } from "@/modules/wifi-ssid";
|
||||
|
||||
export type PermissionStatus =
|
||||
@@ -15,13 +15,28 @@ export interface UseWifiSSIDReturn {
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
function mapLocationStatus(
|
||||
status: Location.PermissionStatus,
|
||||
): PermissionStatus {
|
||||
// WiFi SSID is not available on tvOS
|
||||
if (Platform.isTV) {
|
||||
// Export a stub hook for tvOS
|
||||
module.exports = {
|
||||
useWifiSSID: (): UseWifiSSIDReturn => ({
|
||||
ssid: null,
|
||||
permissionStatus: "unavailable" as PermissionStatus,
|
||||
requestPermission: async () => false,
|
||||
isLoading: false,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
// Only import Location on non-TV platforms
|
||||
const Location = Platform.isTV ? null : require("expo-location");
|
||||
|
||||
function mapLocationStatus(status: number | undefined): PermissionStatus {
|
||||
if (!Location) return "unavailable";
|
||||
switch (status) {
|
||||
case Location.PermissionStatus.GRANTED:
|
||||
case Location.PermissionStatus?.GRANTED:
|
||||
return "granted";
|
||||
case Location.PermissionStatus.DENIED:
|
||||
case Location.PermissionStatus?.DENIED:
|
||||
return "denied";
|
||||
default:
|
||||
return "undetermined";
|
||||
@@ -30,17 +45,24 @@ function mapLocationStatus(
|
||||
|
||||
export function useWifiSSID(): UseWifiSSIDReturn {
|
||||
const [ssid, setSSID] = useState<string | null>(null);
|
||||
const [permissionStatus, setPermissionStatus] =
|
||||
useState<PermissionStatus>("undetermined");
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [permissionStatus, setPermissionStatus] = useState<PermissionStatus>(
|
||||
Platform.isTV ? "unavailable" : "undetermined",
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState(!Platform.isTV);
|
||||
|
||||
const fetchSSID = useCallback(async () => {
|
||||
if (Platform.isTV) return;
|
||||
const result = await getSSID();
|
||||
console.log("[WiFi Debug] Native module SSID:", result);
|
||||
setSSID(result);
|
||||
}, []);
|
||||
|
||||
const requestPermission = useCallback(async (): Promise<boolean> => {
|
||||
if (Platform.isTV || !Location) {
|
||||
setPermissionStatus("unavailable");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const { status } = await Location.requestForegroundPermissionsAsync();
|
||||
const newStatus = mapLocationStatus(status);
|
||||
@@ -58,6 +80,11 @@ export function useWifiSSID(): UseWifiSSIDReturn {
|
||||
}, [fetchSSID]);
|
||||
|
||||
useEffect(() => {
|
||||
if (Platform.isTV || !Location) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
async function initialize() {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
@@ -79,6 +106,8 @@ export function useWifiSSID(): UseWifiSSIDReturn {
|
||||
|
||||
// Refresh SSID when permission status changes to granted
|
||||
useEffect(() => {
|
||||
if (Platform.isTV) return;
|
||||
|
||||
if (permissionStatus === "granted") {
|
||||
fetchSSID();
|
||||
|
||||
|
||||
10
index.js
10
index.js
@@ -1,6 +1,10 @@
|
||||
import "react-native-url-polyfill/auto";
|
||||
import TrackPlayer from "react-native-track-player";
|
||||
import { PlaybackService } from "./services/PlaybackService";
|
||||
import { Platform } from "react-native";
|
||||
import "expo-router/entry";
|
||||
|
||||
TrackPlayer.registerPlaybackService(() => PlaybackService);
|
||||
// TrackPlayer is not supported on tvOS
|
||||
if (!Platform.isTV) {
|
||||
const TrackPlayer = require("react-native-track-player").default;
|
||||
const { PlaybackService } = require("./services/PlaybackService");
|
||||
TrackPlayer.registerPlaybackService(() => PlaybackService);
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ final class MPVLayerRenderer {
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else { return }
|
||||
if #available(iOS 18.0, *) {
|
||||
if #available(iOS 18.0, tvOS 17.0, *) {
|
||||
self.displayLayer.sampleBufferRenderer.flush(removingDisplayedImage: true, completionHandler: nil)
|
||||
} else {
|
||||
self.displayLayer.flushAndRemoveImage()
|
||||
|
||||
@@ -72,9 +72,11 @@ class MpvPlayerView: ExpoView {
|
||||
|
||||
displayLayer.frame = bounds
|
||||
displayLayer.videoGravity = .resizeAspect
|
||||
#if !os(tvOS)
|
||||
if #available(iOS 17.0, *) {
|
||||
displayLayer.wantsExtendedDynamicRangeContent = true
|
||||
}
|
||||
#endif
|
||||
displayLayer.backgroundColor = UIColor.black.cgColor
|
||||
videoContainer.layer.addSublayer(displayLayer)
|
||||
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import ExpoModulesCore
|
||||
#if !os(tvOS)
|
||||
import NetworkExtension
|
||||
import SystemConfiguration.CaptiveNetwork
|
||||
#endif
|
||||
|
||||
public class WifiSsidModule: Module {
|
||||
public func definition() -> ModuleDefinition {
|
||||
Name("WifiSsid")
|
||||
|
||||
// Get current WiFi SSID using NEHotspotNetwork (iOS 14+)
|
||||
// Not available on tvOS
|
||||
AsyncFunction("getSSID") { () -> String? in
|
||||
#if os(tvOS)
|
||||
return nil
|
||||
#else
|
||||
return await withCheckedContinuation { continuation in
|
||||
NEHotspotNetwork.fetchCurrent { network in
|
||||
if let ssid = network?.ssid {
|
||||
@@ -21,14 +27,21 @@ public class WifiSsidModule: Module {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Synchronous version using only CNCopyCurrentNetworkInfo
|
||||
// Not available on tvOS
|
||||
Function("getSSIDSync") { () -> String? in
|
||||
#if os(tvOS)
|
||||
return nil
|
||||
#else
|
||||
return self.getSSIDViaCNCopy()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
private func getSSIDViaCNCopy() -> String? {
|
||||
guard let interfaces = CNCopySupportedInterfaces() as? [String] else {
|
||||
print("[WifiSsid] CNCopySupportedInterfaces returned nil")
|
||||
@@ -49,4 +62,5 @@ public class WifiSsidModule: Module {
|
||||
print("[WifiSsid] No SSID found via CNCopyCurrentNetworkInfo")
|
||||
return nil
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-i18next": "16.5.3",
|
||||
"react-native": "0.81.5",
|
||||
"react-native": "npm:react-native-tvos@0.81.5-2",
|
||||
"react-native-awesome-slider": "^2.9.0",
|
||||
"react-native-bottom-tabs": "1.1.0",
|
||||
"react-native-circular-progress": "^1.4.1",
|
||||
|
||||
72
patches/react-native-bottom-tabs+1.1.0.patch
Normal file
72
patches/react-native-bottom-tabs+1.1.0.patch
Normal file
@@ -0,0 +1,72 @@
|
||||
diff --git a/node_modules/react-native-bottom-tabs/ios/BottomAccessoryProvider.swift b/node_modules/react-native-bottom-tabs/ios/BottomAccessoryProvider.swift
|
||||
--- a/node_modules/react-native-bottom-tabs/ios/BottomAccessoryProvider.swift
|
||||
+++ b/node_modules/react-native-bottom-tabs/ios/BottomAccessoryProvider.swift
|
||||
@@ -8,7 +8,7 @@
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
- #if !os(macOS)
|
||||
+ #if !os(macOS) && !os(tvOS)
|
||||
@available(iOS 26.0, *)
|
||||
public func emitPlacementChanged(_ placement: TabViewBottomAccessoryPlacement?) {
|
||||
var placementValue = "none"
|
||||
diff --git a/node_modules/react-native-bottom-tabs/ios/TabView/NewTabView.swift b/node_modules/react-native-bottom-tabs/ios/TabView/NewTabView.swift
|
||||
--- a/node_modules/react-native-bottom-tabs/ios/TabView/NewTabView.swift
|
||||
+++ b/node_modules/react-native-bottom-tabs/ios/TabView/NewTabView.swift
|
||||
@@ -67,11 +67,11 @@
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
- #if os(macOS)
|
||||
- // tabViewBottomAccessory is not available on macOS
|
||||
+ #if os(macOS) || os(tvOS)
|
||||
+ // tabViewBottomAccessory is not available on macOS or tvOS
|
||||
content
|
||||
#else
|
||||
- if #available(iOS 26.0, tvOS 26.0, visionOS 3.0, *), bottomAccessoryView != nil {
|
||||
+ if #available(iOS 26.0, visionOS 3.0, *), bottomAccessoryView != nil {
|
||||
content
|
||||
.tabViewBottomAccessory {
|
||||
renderBottomAccessoryView()
|
||||
@@ -84,7 +84,7 @@
|
||||
|
||||
@ViewBuilder
|
||||
private func renderBottomAccessoryView() -> some View {
|
||||
- #if !os(macOS)
|
||||
+ #if !os(macOS) && !os(tvOS)
|
||||
if let bottomAccessoryView {
|
||||
if #available(iOS 26.0, *) {
|
||||
BottomAccessoryRepresentableView(view: bottomAccessoryView)
|
||||
@@ -94,7 +94,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
-#if !os(macOS)
|
||||
+#if !os(macOS) && !os(tvOS)
|
||||
@available(iOS 26.0, *)
|
||||
struct BottomAccessoryRepresentableView: PlatformViewRepresentable {
|
||||
@Environment(\.tabViewBottomAccessoryPlacement) var tabViewBottomAccessoryPlacement
|
||||
diff --git a/node_modules/react-native-bottom-tabs/ios/TabViewImpl.swift b/node_modules/react-native-bottom-tabs/ios/TabViewImpl.swift
|
||||
--- a/node_modules/react-native-bottom-tabs/ios/TabViewImpl.swift
|
||||
+++ b/node_modules/react-native-bottom-tabs/ios/TabViewImpl.swift
|
||||
@@ -281,7 +281,7 @@
|
||||
|
||||
@ViewBuilder
|
||||
func tabBarMinimizeBehavior(_ behavior: MinimizeBehavior?) -> some View {
|
||||
- #if compiler(>=6.2)
|
||||
+ #if compiler(>=6.2) && !os(tvOS)
|
||||
if #available(iOS 26.0, macOS 26.0, *) {
|
||||
if let behavior {
|
||||
self.tabBarMinimizeBehavior(behavior.convert())
|
||||
diff --git a/node_modules/react-native-bottom-tabs/ios/TabViewProps.swift b/node_modules/react-native-bottom-tabs/ios/TabViewProps.swift
|
||||
--- a/node_modules/react-native-bottom-tabs/ios/TabViewProps.swift
|
||||
+++ b/node_modules/react-native-bottom-tabs/ios/TabViewProps.swift
|
||||
@@ -6,7 +6,7 @@
|
||||
case onScrollUp
|
||||
case onScrollDown
|
||||
|
||||
-#if compiler(>=6.2)
|
||||
+#if compiler(>=6.2) && !os(tvOS)
|
||||
@available(iOS 26.0, macOS 26.0, *)
|
||||
func convert() -> TabBarMinimizeBehavior {
|
||||
#if os(macOS)
|
||||
@@ -37,6 +37,11 @@ const dependencies = {
|
||||
),
|
||||
"react-native-ios-utilities": disableForTV("react-native-ios-utilities"),
|
||||
"react-native-pager-view": disableForTV("react-native-pager-view"),
|
||||
"react-native-track-player": disableForTV("react-native-track-player"),
|
||||
"expo-location": disableForTV("expo-location"),
|
||||
"react-native-glass-effect-view": disableForTV(
|
||||
"react-native-glass-effect-view",
|
||||
),
|
||||
};
|
||||
|
||||
// Filter out undefined values
|
||||
|
||||
Reference in New Issue
Block a user