diff --git a/with-router-tv/app.json b/with-router-tv/app.json index a48cef94..eda66c38 100644 --- a/with-router-tv/app.json +++ b/with-router-tv/app.json @@ -1,5 +1,6 @@ { "expo": { + "scheme": "routertv", "plugins": [ [ "@react-native-tvos/config-tv", @@ -16,19 +17,19 @@ } } ], - [ - "expo-build-properties", - { - "ios": { - "newArchEnabled": false - }, - "android": { - "newArchEnabled": false - } - } - ], - "expo-router" + "expo-router", + "expo-font" ], + "android": { + "splash": { + "image": "./assets/images/splash.png" + } + }, + "ios": { + "splash": { + "image": "./assets/images/splash.png" + } + }, "experiments": { "typedRoutes": true } diff --git a/with-router-tv/app/(tabs)/_layout.tsx b/with-router-tv/app/(tabs)/_layout.tsx index a3c8ac3e..3fb355d3 100644 --- a/with-router-tv/app/(tabs)/_layout.tsx +++ b/with-router-tv/app/(tabs)/_layout.tsx @@ -6,10 +6,12 @@ import { TabBarIcon } from '@/components/navigation/TabBarIcon'; import { Colors } from '@/constants/Colors'; import { useColorScheme } from '@/hooks/useColorScheme'; import { useTextStyles } from '@/hooks/useTextStyles'; +import { useScale } from '@/hooks/useScale'; export default function TabLayout() { const colorScheme = useColorScheme(); const textStyles = useTextStyles(); + const scale = useScale(); const tabBarButton = (props: BottomTabBarButtonProps) => { const style: any = props.style ?? {}; @@ -32,7 +34,11 @@ export default function TabLayout() { tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint, tabBarStyle: { height: textStyles.title.lineHeight * 2, - marginBottom: 0, + }, + tabBarPosition: 'top', + tabBarIconStyle: { + height: textStyles.title.lineHeight, + width: 30 * scale, }, headerShown: false, }} @@ -72,10 +78,7 @@ export default function TabLayout() { tabBarButton, tabBarLabelStyle: textStyles.default, tabBarIcon: ({ color, focused }) => ( - + ), }} /> diff --git a/with-router-tv/app/(tabs)/tv_focus.tsx b/with-router-tv/app/(tabs)/tv_focus.tsx index 85553856..46c4bcf3 100644 --- a/with-router-tv/app/(tabs)/tv_focus.tsx +++ b/with-router-tv/app/(tabs)/tv_focus.tsx @@ -32,17 +32,25 @@ export default function FocusDemoScreen() { - On TV platforms, these components have "onFocus()" and "onBlur()" + • On TV platforms, these components have "onFocus()" and "onBlur()" props, in addition to the usual "onPress()". These can be used to modify the style of the component when it is navigated to or navigated - away from by the TV focus engine. In addition, the functional forms of - the Pressable style prop and the Pressable content, which in React - Native core take a "pressed" boolean parameter, can also take - "focused" as a parameter on TV platforms. + away from by the TV focus engine. - As you use the arrow keys to navigate around the screen, the demo uses - the above props to update lists of recent events. + • In addition, the functional forms of the Pressable style prop and + the Pressable content, which in React Native core take a "pressed" + boolean parameter, can also take "focused" as a parameter on TV + platforms. + + + • As you use the arrow keys to navigate around the screen, the demo + uses the above props to update lists of recent events. + + + In RNTV 0.76, `Pressable` and `Touchable` components receive "focus", + "blur", "pressIn", and "pressOut" events directly from native code, + for improved performance when navigating around the screen. {Platform.isTV ? ( diff --git a/with-router-tv/components/EventHandlingDemo.tsx b/with-router-tv/components/EventHandlingDemo.tsx index ec8f5b88..2cb8c830 100644 --- a/with-router-tv/components/EventHandlingDemo.tsx +++ b/with-router-tv/components/EventHandlingDemo.tsx @@ -8,7 +8,6 @@ import { TouchableHighlight, TouchableNativeFeedback, TouchableOpacity, - GestureResponderEvent, } from 'react-native'; import { useState } from 'react'; @@ -22,9 +21,9 @@ export function EventHandlingDemo() { const [pressableEventLog, setPressableEventLog] = useState([]); const logWithAppendedEntry = (log: string[], entry: string) => { - const limit = 3; - const newEventLog = log.slice(0, limit - 1); - newEventLog.unshift(entry); + const limit = 10; + const newEventLog = log.slice(log.length === limit ? 1 : 0, limit); + newEventLog.push(entry); return newEventLog; }; @@ -50,36 +49,37 @@ export function EventHandlingDemo() { return ( - - - - {Platform.OS === 'android' ? ( - - ) : null} - - Focus/press events + TV remote events {remoteEventLog.join('\n')} - Remote control events + Native events {pressableEventLog.join('\n')} + + + + + {Platform.OS === 'android' ? ( + + ) : null} + ); } @@ -92,18 +92,12 @@ const PressableButton = (props: { return ( props.log(`${props.title} focus`)} - onBlur={() => props.log(`${props.title} blur`)} - onPress={() => props.log(`${props.title} pressed`)} - onLongPress={( - event: GestureResponderEvent & { eventKeyAction?: number }, - ) => - props.log( - `${props.title} long press ${ - event.eventKeyAction === 0 ? 'start' : 'end' - }`, - ) - } + onFocus={() => props.log(`${props.title} onFocus`)} + onBlur={() => props.log(`${props.title} onBlur`)} + onPress={() => props.log(`${props.title} onPress`)} + onPressIn={() => props.log(`${props.title} onPressIn`)} + onPressOut={() => props.log(`${props.title} onPressOut`)} + onLongPress={() => props.log(`${props.title} onLongPress`)} style={({ pressed, focused }) => pressed || focused ? styles.pressableFocused : styles.pressable } @@ -129,18 +123,11 @@ const TouchableOpacityButton = (props: { props.log(`${props.title} focus`)} - onBlur={() => props.log(`${props.title} blur`)} - onPress={() => props.log(`${props.title} pressed`)} - onLongPress={( - event: GestureResponderEvent & { eventKeyAction?: number }, - ) => - props.log( - `${props.title} long press ${ - event.eventKeyAction === 0 ? 'start' : 'end' - }`, - ) - } + onFocus={() => props.log(`${props.title} onFocus`)} + onBlur={() => props.log(`${props.title} onBlur`)} + onPressIn={() => props.log(`${props.title} onPressIn`)} + onPressOut={() => props.log(`${props.title} onPressOut`)} + onLongPress={() => props.log(`${props.title} onLongPress`)} > {props.title} @@ -158,18 +145,11 @@ const TouchableHighlightButton = (props: { props.log(`${props.title} focus`)} - onBlur={() => props.log(`${props.title} blur`)} - onPress={() => props.log(`${props.title} pressed`)} - onLongPress={( - event: GestureResponderEvent & { eventKeyAction?: number }, - ) => - props.log( - `${props.title} long press ${ - event.eventKeyAction === 0 ? 'start' : 'end' - }`, - ) - } + onFocus={(event) => props.log(`${props.title} onFocus`)} + onBlur={(event) => props.log(`${props.title} onBlur`)} + onPressIn={() => props.log(`${props.title} onPressIn`)} + onPressOut={() => props.log(`${props.title} onPressOut`)} + onLongPress={() => props.log(`${props.title} onLongPress`)} > {props.title} @@ -185,18 +165,10 @@ const TouchableNativeFeedbackButton = (props: { return ( props.log(`${props.title} focus`)} - onBlur={() => props.log(`${props.title} blur`)} - onPress={() => props.log(`${props.title} pressed`)} - onLongPress={( - event: GestureResponderEvent & { eventKeyAction?: number }, - ) => - props.log( - `${props.title} long press ${ - event.eventKeyAction === 0 ? 'start' : 'end' - }`, - ) - } + onPress={() => props.log(`${props.title} onPress`)} + onPressIn={() => props.log(`${props.title} onPressIn`)} + onPressOut={() => props.log(`${props.title} onPressOut`)} + onLongPress={() => props.log(`${props.title} onLongPress`)} > {props.title} @@ -214,7 +186,8 @@ const useDemoStyles = function () { return StyleSheet.create({ container: { flex: 1, - alignItems: 'center', + flexDirection: 'row', + alignItems: 'flex-start', justifyContent: 'center', }, logContainer: { @@ -225,10 +198,11 @@ const useDemoStyles = function () { justifyContent: 'flex-start', }, logText: { - height: 100 * scale, - width: 200 * scale, + maxHeight: 300 * scale, + width: 300 * scale, fontSize: 10 * scale, margin: 5 * scale, + lineHeight: 12 * scale, alignSelf: 'flex-start', justifyContent: 'flex-start', }, diff --git a/with-router-tv/package.json b/with-router-tv/package.json index bb26579c..3397be10 100644 --- a/with-router-tv/package.json +++ b/with-router-tv/package.json @@ -17,34 +17,33 @@ }, "dependencies": { "@expo/vector-icons": "^14.0.2", - "@react-navigation/native": "^6.0.2", - "expo": "~51.0.31", - "expo-build-properties": "~0.12.3", - "expo-constants": "~16.0.2", - "expo-font": "~12.0.9", - "expo-linking": "~6.3.1", - "expo-router": "~3.5.23", - "expo-splash-screen": "~0.27.5", - "expo-status-bar": "~1.12.1", - "expo-system-ui": "~3.0.7", - "expo-web-browser": "~13.0.3", - "react": "18.2.0", - "react-dom": "18.2.0", - "react-native": "npm:react-native-tvos@~0.74.5-0", - "react-native-gesture-handler": "~2.16.1", - "react-native-reanimated": "~3.10.1", - "react-native-safe-area-context": "4.10.5", - "react-native-screens": "3.31.1", + "expo": "~52.0.2", + "expo-build-properties": "~0.13.1", + "expo-constants": "~17.0.2", + "expo-font": "~13.0.1", + "expo-linking": "~7.0.2", + "expo-router": "~4.0.2", + "expo-splash-screen": "~0.29.6", + "expo-status-bar": "~2.0.0", + "expo-system-ui": "~4.0.2", + "expo-web-browser": "~14.0.0", + "react": "18.3.1", + "react-dom": "18.3.1", + "react-native": "npm:react-native-tvos@0.76.1-1", + "react-native-gesture-handler": "~2.20.2", + "react-native-reanimated": "~3.16.1", + "react-native-safe-area-context": "4.12.0", + "react-native-screens": "4.0.0", "react-native-web": "~0.19.10" }, "devDependencies": { "@babel/core": "^7.20.0", - "@react-native-tvos/config-tv": "^0.0.10", + "@react-native-tvos/config-tv": "^0.1.0", "@types/jest": "^29.5.12", - "@types/react": "~18.2.45", + "@types/react": "~18.3.12", "@types/react-test-renderer": "^18.0.7", "jest": "^29.2.1", - "jest-expo": "~51.0.1", + "jest-expo": "~52.0.0-preview.4", "react-test-renderer": "18.2.0", "typescript": "~5.3.3" }, diff --git a/with-tv/app.json b/with-tv/app.json index 11fcf693..838c8187 100644 --- a/with-tv/app.json +++ b/with-tv/app.json @@ -16,6 +16,16 @@ } } ] - ] + ], + "android": { + "splash": { + "image": "./assets/images/icon-1920x720.png" + } + }, + "ios": { + "splash": { + "image": "./assets/images/icon-1920x720.png" + } + } } } diff --git a/with-tv/package.json b/with-tv/package.json index c33a5e21..ae1260bf 100644 --- a/with-tv/package.json +++ b/with-tv/package.json @@ -9,17 +9,17 @@ "prebuild": "EXPO_TV=1 expo prebuild --clean" }, "dependencies": { - "expo": "~51.0.31", - "expo-splash-screen": "~0.27.5", - "expo-status-bar": "~1.12.1", - "react": "18.2.0", - "react-dom": "18.2.0", - "react-native": "npm:react-native-tvos@~0.74.5-0" + "expo": "~52.0.2", + "expo-splash-screen": "~0.29.4", + "expo-status-bar": "~2.0.0", + "react": "18.3.1", + "react-dom": "18.3.1", + "react-native": "npm:react-native-tvos@~0.76.1-1" }, "devDependencies": { "@babel/core": "^7.24.0", - "@react-native-tvos/config-tv": "^0.0.10", - "@types/react": "~18.2.45", + "@react-native-tvos/config-tv": "^0.1.0", + "@types/react": "~18.3.12", "typescript": "~5.3.3" }, "expo": {