Skip to content
This repository was archived by the owner on Jan 28, 2025. It is now read-only.

Commit 613d9fa

Browse files
committed
Initial commit
0 parents  commit 613d9fa

File tree

127 files changed

+3565
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

127 files changed

+3565
-0
lines changed

.gitignore

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2+
3+
# dependencies
4+
node_modules/
5+
6+
# Expo
7+
.expo/
8+
dist/
9+
web-build/
10+
11+
# Native
12+
*.orig.*
13+
*.jks
14+
*.p8
15+
*.p12
16+
*.key
17+
*.mobileprovision
18+
19+
# Metro
20+
.metro-health-check*
21+
22+
# debug
23+
npm-debug.*
24+
yarn-debug.*
25+
yarn-error.*
26+
27+
# macOS
28+
.DS_Store
29+
*.pem
30+
31+
# local env files
32+
.env*.local
33+
34+
# typescript
35+
*.tsbuildinfo
36+
37+
android
38+
ios

App.tsx

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import './src/components/configureRemoteControl';
2+
import { ThemeProvider } from '@emotion/react';
3+
import { NavigationContainer } from '@react-navigation/native';
4+
import { useWindowDimensions } from 'react-native';
5+
import { theme } from './src/design-system/theme/theme';
6+
import { Home } from './src/pages/Home';
7+
import { ProgramGridPage } from './src/pages/ProgramGridPage';
8+
import { Menu } from './src/components/Menu/Menu';
9+
import { MenuProvider } from './src/components/Menu/MenuContext';
10+
import styled from '@emotion/native';
11+
import { useFonts } from './src/hooks/useFonts';
12+
import { BottomTabBarProps, createBottomTabNavigator } from '@react-navigation/bottom-tabs';
13+
import { ProgramInfo } from './src/modules/program/domain/programInfo';
14+
import { createNativeStackNavigator } from '@react-navigation/native-stack';
15+
import { ProgramDetail } from './src/pages/ProgramDetail';
16+
import { NonVirtualizedGridPage } from './src/pages/NonVirtualizedGridPage';
17+
import { GridWithLongNodesPage } from './src/pages/GridWithLongNodesPage';
18+
import { useTVPanEvent } from './src/components/PanEvent/useTVPanEvent';
19+
import { SpatialNavigationDeviceTypeProvider } from '../lib/src/spatial-navigation/context/DeviceContext';
20+
import { ListWithVariableSize } from './src/pages/ListWithVariableSize';
21+
import { AsynchronousContent } from './src/pages/AsynchronousContent';
22+
23+
const Stack = createNativeStackNavigator<RootStackParamList>();
24+
25+
const Tab = createBottomTabNavigator<RootTabParamList>();
26+
27+
export type RootTabParamList = {
28+
Home: undefined;
29+
ProgramGridPage: undefined;
30+
NonVirtualizedGridPage: undefined;
31+
GridWithLongNodesPage: undefined;
32+
ListWithVariableSize: undefined;
33+
AsynchronousContent: undefined;
34+
};
35+
36+
export type RootStackParamList = {
37+
TabNavigator: undefined;
38+
ProgramDetail: { programInfo: ProgramInfo };
39+
};
40+
41+
const RenderMenu = (props: BottomTabBarProps) => <Menu {...props} />;
42+
43+
const TabNavigator = () => {
44+
return (
45+
<MenuProvider>
46+
<Tab.Navigator
47+
screenOptions={{
48+
headerShown: false,
49+
}}
50+
initialRouteName="Home"
51+
tabBar={RenderMenu}
52+
sceneContainerStyle={{
53+
marginLeft: theme.sizes.menu.closed,
54+
backgroundColor: theme.colors.background.main,
55+
}}
56+
>
57+
<Tab.Screen name="Home" component={Home} />
58+
<Tab.Screen name="ProgramGridPage" component={ProgramGridPage} />
59+
<Tab.Screen name="NonVirtualizedGridPage" component={NonVirtualizedGridPage} />
60+
<Tab.Screen name="GridWithLongNodesPage" component={GridWithLongNodesPage} />
61+
<Tab.Screen name="ListWithVariableSize" component={ListWithVariableSize} />
62+
<Tab.Screen name="AsynchronousContent" component={AsynchronousContent} />
63+
</Tab.Navigator>
64+
</MenuProvider>
65+
);
66+
};
67+
68+
function App() {
69+
useTVPanEvent();
70+
const { height, width } = useWindowDimensions();
71+
const areFontsLoaded = useFonts();
72+
73+
if (!areFontsLoaded) {
74+
return null;
75+
}
76+
77+
return (
78+
<NavigationContainer>
79+
<ThemeProvider theme={theme}>
80+
<SpatialNavigationDeviceTypeProvider>
81+
<Container width={width} height={height}>
82+
<Stack.Navigator
83+
screenOptions={{
84+
headerShown: false,
85+
contentStyle: {
86+
backgroundColor: theme.colors.background.main,
87+
},
88+
}}
89+
initialRouteName="TabNavigator"
90+
>
91+
<Stack.Screen name="TabNavigator" component={TabNavigator} />
92+
<Stack.Screen name="ProgramDetail" component={ProgramDetail} />
93+
</Stack.Navigator>
94+
</Container>
95+
</SpatialNavigationDeviceTypeProvider>
96+
</ThemeProvider>
97+
</NavigationContainer>
98+
);
99+
}
100+
101+
export default App;
102+
103+
const Container = styled.View<{ width: number; height: number }>(({ width, height }) => ({
104+
width,
105+
height,
106+
flexDirection: 'row-reverse',
107+
backgroundColor: theme.colors.background.main,
108+
}));

README.md

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Hoppix
2+
3+
## Description
4+
5+
Hoppix is a project that aims to provide an example TVOS application built using React Native. It includes features such as navigation, state management, and integration with web development.
6+
7+
## Installation
8+
9+
To install the project, follow these steps:
10+
11+
1. Install the required dependencies:
12+
13+
```
14+
yarn install
15+
```
16+
17+
2. Prebuild the app with expo before running:
18+
```
19+
yarn prebuild
20+
```
21+
22+
## Usage
23+
24+
### Running the TVOS Application on Apple TV or Android TV
25+
26+
You can run this demo application on AppleTV or AndroidTV
27+
To start the TV application, use one of the following commands:
28+
29+
```
30+
yarn start
31+
yarn ios
32+
yarn android
33+
```
34+
35+
This will initiate the TV application using React Native's Metro bundler.
36+
37+
Make sure you have set up the necessary emulator/device configurations on XCode or Android Studio to run the project on AppleTV or Android TV.
38+
39+
### tvOS troubleshooting
40+
41+
If you get the error
42+
43+
```
44+
CommandError: Failed to build iOS project. "xcodebuild" exited with error code 65.
45+
To view more error logs, try building the app with Xcode directly, by opening /Users/thomasrenaud/Desktop/SpaceNavigation/react-tv-space-navigation/packages/example/ios/hoppixTv.xcworkspace.
46+
47+
Command line invocation:
48+
/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -workspace /Users/thomasrenaud/Desktop/SpaceNavigation/react-tv-space-navigation/packages/example/ios/hoppixTv.xcworkspace -configuration Debug -scheme hoppixTv -destination id=B14D0E77-99C4-486F-8096-6584A23C9476
49+
50+
User defaults from command line:
51+
IDEPackageSupportUseBuiltinSCM = YES
52+
```
53+
54+
please delete the .xcode.env.local in your ios directory, and run the `yarn ios` command again.
55+
56+
### Running the Web Application
57+
58+
Hoppix also supports running as a web application. To run the web version of the project, use the following command:
59+
60+
```
61+
yarn web
62+
```
63+
64+
This will start a development server using Webpack and serve the application in your default web browser.
65+
66+
## Handling Remote Control
67+
68+
In order to use Spatial Navigation in the Web Application or TV Application, you must configure the remoteControlManager to map your keyboard or remote keys to LRUD Directions.
69+
70+
See [Remote Control](./src/components/remote-control/) for how to manage Platform Specific remote controls.
71+
72+
## Contributing
73+
74+
Contributions to Hoppix are welcome! If you find any issues or want to enhance the project, please submit a pull request or open an issue on the repository.

__tests__/App-test.tsx

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* @format
3+
*/
4+
5+
import 'react-native';
6+
import App from '../App';
7+
8+
// Note: test renderer must be required after react-native.
9+
import renderer from 'react-test-renderer';
10+
11+
it('renders correctly', () => {
12+
renderer.create(<App />);
13+
});

app.json

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"expo": {
3+
"name": "hoppixTv",
4+
"slug": "hoppixTv",
5+
"version": "1.0.0",
6+
"orientation": "portrait",
7+
"icon": "./assets/icon.png",
8+
"userInterfaceStyle": "light",
9+
"splash": {
10+
"image": "./assets/splash.png",
11+
"resizeMode": "contain",
12+
"backgroundColor": "#ffffff"
13+
},
14+
"assetBundlePatterns": ["**/*"],
15+
"ios": {
16+
"supportsTablet": true,
17+
"bundleIdentifier": "com.reactTvSpaceNavigation.hoppixTv"
18+
},
19+
"android": {
20+
"adaptiveIcon": {
21+
"foregroundImage": "./assets/adaptive-icon.png",
22+
"backgroundColor": "#ffffff"
23+
},
24+
"package": "com.reactTvSpaceNavigation.hoppixTv"
25+
},
26+
"web": {
27+
"favicon": "./assets/favicon.png"
28+
},
29+
"experiments": {
30+
"baseUrl": "/react-tv-space-navigation"
31+
},
32+
"plugins": [
33+
"@bam.tech/react-native-keyevent-expo-config-plugin",
34+
[
35+
"@react-native-tvos/config-tv",
36+
{
37+
"showVerboseWarnings": true
38+
}
39+
],
40+
[
41+
"expo-font",
42+
{
43+
"fonts": [
44+
"./assets/fonts/Montserrat-Bold.ttf",
45+
"./assets/fonts/Montserrat-Regular.ttf",
46+
"./assets/fonts/Montserrat-SemiBold.ttf",
47+
"./assets/fonts/Montserrat-Medium.ttf"
48+
]
49+
}
50+
]
51+
]
52+
}
53+
}

assets/adaptive-icon.png

17.1 KB
Loading

assets/bunny_favicon.png

218 KB
Loading

assets/favicon.ico

4.19 KB
Binary file not shown.

assets/favicon.png

2.55 KB
Loading

assets/fonts/Montserrat-Bold.ttf

193 KB
Binary file not shown.

assets/fonts/Montserrat-Medium.ttf

193 KB
Binary file not shown.

assets/fonts/Montserrat-Regular.ttf

193 KB
Binary file not shown.

assets/fonts/Montserrat-SemiBold.ttf

194 KB
Binary file not shown.

assets/icon.png

21.9 KB
Loading

assets/splash.png

46.2 KB
Loading

babel.config.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = function (api) {
2+
api.cache(true);
3+
return {
4+
presets: ['babel-preset-expo'],
5+
};
6+
};

babel.jest.config.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// This is only used by jest
2+
module.exports = {
3+
sourceMaps: 'inline',
4+
presets: [
5+
'module:metro-react-native-babel-preset',
6+
'@babel/preset-env',
7+
[
8+
'@babel/preset-react',
9+
{
10+
runtime: 'automatic',
11+
},
12+
],
13+
'@babel/preset-typescript',
14+
],
15+
plugins: [
16+
[
17+
'module-resolver',
18+
{
19+
alias: {
20+
'react-tv-space-navigation': '../lib/src/index.ts',
21+
},
22+
},
23+
],
24+
['@babel/plugin-proposal-class-properties', { loose: false }],
25+
],
26+
};

index.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { registerRootComponent } from 'expo';
2+
3+
import App from './App';
4+
5+
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
6+
// It also ensures that whether you load the app in Expo Go or in a native build,
7+
// the environment is set up appropriately
8+
registerRootComponent(App);

jest.config.js

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// @ts-check
2+
3+
/**
4+
* Code in the react-native ecosystem if often shipped untransformed, with flow or typescript in files
5+
* App code also needs to be transformed (it's TypeScript), but the rest of node_modules doesn't need to.
6+
* Transforming the minimum amount of code makes tests run much faster
7+
*
8+
* If encountering a syntax error during tests with a new package, add it to this list
9+
*/
10+
11+
// eslint-disable-next-line @typescript-eslint/no-var-requires
12+
const path = require('path');
13+
14+
const packagesToTransform = [
15+
'react-native',
16+
'react-native-(.*)',
17+
'@react-native',
18+
'@react-native-community',
19+
'@react-native-tvos',
20+
'@react-navigation',
21+
];
22+
23+
/** @type {import('@jest/types').Config.InitialOptions} */
24+
const config = {
25+
preset: '@testing-library/react-native',
26+
/*
27+
* What the preset provides:
28+
* - a transformer to handle media assets (png, video)
29+
*/
30+
// test environment setup
31+
setupFiles: ['./src/testing/jest-setup.ts'],
32+
setupFilesAfterEnv: ['./src/testing/jest-setupAfterEnv.ts'],
33+
clearMocks: true,
34+
// module resolution
35+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
36+
testRegex: '\\.test\\.[jt]sx?$',
37+
transform: {
38+
'\\.[jt]sx?$': [
39+
'babel-jest',
40+
{ configFile: path.resolve(__dirname, './babel.jest.config.js') },
41+
],
42+
},
43+
transformIgnorePatterns: [`node_modules/(?!(${packagesToTransform.join('|')})/)`],
44+
cacheDirectory: '.cache/jest',
45+
// coverage
46+
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'],
47+
coveragePathIgnorePatterns: ['/node_modules/'],
48+
// tools
49+
watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'],
50+
reporters: ['default', 'github-actions'], // Remove this line if your CI is not on Github actions
51+
};
52+
53+
module.exports = config;

0 commit comments

Comments
 (0)