Skip to content

Commit

Permalink
Merge pull request #30 from bamlab/feat/add-buttons
Browse files Browse the repository at this point in the history
Example app: add buttons
  • Loading branch information
pierpo authored Oct 31, 2023
2 parents 47bfebf + c653df6 commit 51cee5a
Show file tree
Hide file tree
Showing 12 changed files with 144 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ public boolean onKeyDown(int keyCode, KeyEvent event) {
// KeyEventModule.getInstance().onKeyDownEvent(keyCode, event);
//
// Using B.
KeyEventModule.getInstance().onKeyDownEvent(keyCode, event);
if (KeyEventModule.getInstance() != null) {
KeyEventModule.getInstance().onKeyDownEvent(keyCode, event);
}

// There are 2 ways this can be done:
// 1. Override the default keyboard event behavior
Expand All @@ -43,7 +45,9 @@ public boolean onKeyDown(int keyCode, KeyEvent event) {

@Override // <--- Add this method if you want to react to keyUp
public boolean onKeyUp(int keyCode, KeyEvent event) {
KeyEventModule.getInstance().onKeyUpEvent(keyCode, event);
if (KeyEventModule.getInstance() != null) {
KeyEventModule.getInstance().onKeyUpEvent(keyCode, event);
}

// There are 2 ways this can be done:
// 1. Override the default keyboard event behavior
Expand Down
9 changes: 5 additions & 4 deletions packages/example/src/components/VirtualizedSpatialGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { StyleSheet, View } from 'react-native';
import { SpatialNavigationVirtualizedGrid } from 'react-tv-space-navigation/src';
import { programInfos } from '../modules/program/infra/programInfos';
import { ProgramNode } from '../modules/program/view/ProgramNode';
import { scaledPixels } from '../design-system/helpers/scaledPixels';

const NUMBER_OF_ROWS_VISIBLE_ON_SCREEN = 2;
const NUMBER_OF_RENDERED_ROWS = NUMBER_OF_ROWS_VISIBLE_ON_SCREEN + 3;
Expand Down Expand Up @@ -41,11 +42,11 @@ export const VirtualizedSpatialGrid = ({

const styles = StyleSheet.create({
container: {
height: 780,
height: scaledPixels(780),
backgroundColor: '#222',
padding: 30,
borderRadius: 20,
padding: scaledPixels(30),
borderRadius: scaledPixels(20),
overflow: 'hidden',
},
rowStyle: { gap: 30 },
rowStyle: { gap: scaledPixels(30) },
});
43 changes: 43 additions & 0 deletions packages/example/src/design-system/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { forwardRef } from 'react';
import { Animated, View } from 'react-native';
import { SpatialNavigationNode } from 'react-tv-space-navigation/src';
import { Typography } from './Typography';
import styled from '@emotion/native';
import { useFocusAnimation } from '../helpers/useFocusAnimation';
import { scaledPixels } from '../helpers/scaledPixels';

type ButtonProps = {
label: string;
onSelect?: () => void;
};

const ButtonContent = forwardRef<View, { label: string; isFocused: boolean }>((props, ref) => {
const { isFocused, label } = props;
const anim = useFocusAnimation(isFocused);
return (
<Container style={anim} isFocused={isFocused} ref={ref}>
<ColoredTypography isFocused={isFocused}>{label}</ColoredTypography>
</Container>
);
});

ButtonContent.displayName = 'ButtonContent';

export const Button = ({ label }: ButtonProps) => {
return (
<SpatialNavigationNode isFocusable>
{({ isFocused }) => <ButtonContent label={label} isFocused={isFocused} />}
</SpatialNavigationNode>
);
};

const Container = styled(Animated.View)<{ isFocused: boolean }>(({ isFocused, theme }) => ({
alignSelf: 'baseline',
backgroundColor: isFocused ? 'white' : 'black',
padding: theme.spacings.$4,
borderRadius: scaledPixels(12),
}));

const ColoredTypography = styled(Typography)<{ isFocused: boolean }>(({ isFocused }) => ({
color: isFocused ? 'black' : 'white',
}));
13 changes: 13 additions & 0 deletions packages/example/src/design-system/helpers/scaledPixels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Dimensions } from 'react-native';

export const screen = Dimensions.get('window');
const scale = (screen.width || 1920) / 1920;

/**
* Unfortunately, AndroidTV handles pixels in a strange manner
* PixelRatio does not seem to solve the problem properly on web.
* So we just scale the pixels manually.
*
* https://github.com/react-native-tvos/react-native-tvos/issues/57
*/
export const scaledPixels = (pixels: number) => pixels * scale;
17 changes: 17 additions & 0 deletions packages/example/src/design-system/helpers/useFocusAnimation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useEffect, useRef } from 'react';
import { Animated } from 'react-native';

export const useFocusAnimation = (isFocused: boolean) => {
const scaleAnimation = useRef(new Animated.Value(0)).current;

useEffect(() => {
Animated.spring(scaleAnimation, {
toValue: isFocused ? 1.1 : 1,
useNativeDriver: true,
damping: 10,
stiffness: 100,
}).start();
}, [isFocused, scaleAnimation]);

return { transform: [{ scale: scaleAnimation }] };
};
6 changes: 4 additions & 2 deletions packages/example/src/design-system/theme/sizes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { scaledPixels } from '../helpers/scaledPixels';

export const sizes = {
program: {
landscape: { width: 250, height: 200 },
portrait: { width: 200, height: 250 },
landscape: { width: scaledPixels(250), height: scaledPixels(200) },
portrait: { width: scaledPixels(200), height: scaledPixels(250) },
},
};
32 changes: 17 additions & 15 deletions packages/example/src/design-system/theme/spacings.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { scaledPixels } from '../helpers/scaledPixels';

export const spacings = {
0: 0,
'$0.5': 2,
$1: 4,
$2: 8,
$3: 12,
$4: 16,
$5: 20,
$6: 24,
$7: 28,
$8: 32,
$9: 36,
$10: 40,
$12: 48,
$15: 60,
$20: 80,
0: scaledPixels(0),
'$0.5': scaledPixels(2),
$1: scaledPixels(4),
$2: scaledPixels(8),
$3: scaledPixels(12),
$4: scaledPixels(16),
$5: scaledPixels(20),
$6: scaledPixels(24),
$7: scaledPixels(28),
$8: scaledPixels(32),
$9: scaledPixels(36),
$10: scaledPixels(40),
$12: scaledPixels(48),
$15: scaledPixels(60),
$20: scaledPixels(80),
};
18 changes: 10 additions & 8 deletions packages/example/src/design-system/theme/typography.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { scaledPixels } from '../helpers/scaledPixels';

export const fontFamilies = {
montserrat: {
medium: 'Montserrat-Medium',
Expand All @@ -10,25 +12,25 @@ export const typography = {
title: {
regular: {
fontFamily: fontFamilies.montserrat.semiBold,
fontSize: 32,
lineHeight: 40,
fontSize: scaledPixels(32),
lineHeight: scaledPixels(40),
},
strong: {
fontFamily: fontFamilies.montserrat.bold,
fontSize: 32,
lineHeight: 40,
fontSize: scaledPixels(32),
lineHeight: scaledPixels(40),
},
},
body: {
regular: {
fontFamily: fontFamilies.montserrat.medium,
fontSize: 24,
lineHeight: 32,
fontSize: scaledPixels(24),
lineHeight: scaledPixels(32),
},
strong: {
fontFamily: fontFamilies.montserrat.semiBold,
fontSize: 24,
lineHeight: 32,
fontSize: scaledPixels(24),
lineHeight: scaledPixels(32),
},
},
} as const;
Expand Down
16 changes: 4 additions & 12 deletions packages/example/src/modules/program/view/Program.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import styled from '@emotion/native';
import React, { useEffect, useRef } from 'react';
import React from 'react';
import { Animated, Image, View } from 'react-native';
import { ProgramInfo } from '../domain/programInfo';
import { useFocusAnimation } from '../../../design-system/helpers/useFocusAnimation';

type ProgramProps = {
isFocused?: boolean;
Expand All @@ -12,20 +13,11 @@ export const Program = React.forwardRef<View, ProgramProps>(
({ isFocused = false, programInfo }, ref) => {
const imageSource = programInfo.image;

const scaleAnimation = useRef(new Animated.Value(0)).current;

useEffect(() => {
Animated.spring(scaleAnimation, {
toValue: isFocused ? 1.1 : 1,
useNativeDriver: true,
damping: 10,
stiffness: 100,
}).start();
}, [isFocused, scaleAnimation]);
const scaleAnimation = useFocusAnimation(isFocused);

return (
<ProgramContainer
style={{ transform: [{ scale: scaleAnimation }] }} // Apply the animated scale transform
style={scaleAnimation} // Apply the animated scale transform
ref={ref}
isFocused={isFocused}
>
Expand Down
5 changes: 3 additions & 2 deletions packages/example/src/modules/program/view/ProgramList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import { RootStackParamList } from '../../../../App';
import { ProgramInfo } from '../domain/programInfo';
import { programInfos } from '../infra/programInfos';
import { ProgramNode } from './ProgramNode';
import { scaledPixels } from '../../../design-system/helpers/scaledPixels';

const NUMBER_OF_ITEMS_VISIBLE_ON_SCREEN = 6;
const WINDOW_SIZE = NUMBER_OF_ITEMS_VISIBLE_ON_SCREEN + 8;
const ROW_PADDING = 70;
const ROW_PADDING = scaledPixels(70);

export const ProgramList = ({
orientation,
Expand Down Expand Up @@ -77,6 +78,6 @@ export const ProgramsColumn = ({ containerStyle }: { containerStyle?: object })
const Container = styled.View(({ theme }) => ({
backgroundColor: theme.colors.background.mainHover,
padding: theme.spacings.$8,
borderRadius: 20,
borderRadius: scaledPixels(20),
overflow: 'hidden',
}));
41 changes: 20 additions & 21 deletions packages/example/src/pages/ProgramDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import styled from '@emotion/native';
import { RouteProp } from '@react-navigation/native';
import { DefaultFocus, SpatialNavigationNode } from 'react-tv-space-navigation/src';
import { DefaultFocus } from 'react-tv-space-navigation/src';
import { RootStackParamList } from '../../App';
import { Page } from '../components/Page';
import { Box } from '../design-system/components/Box';
import { Spacer } from '../design-system/components/Spacer';
import { Typography } from '../design-system/components/Typography';
import { ProgramListWithTitle } from '../modules/program/view/ProgramListWithTitle';
import { Button } from '../design-system/components/Button';

export const ProgramDetail = ({
route,
Expand All @@ -19,24 +20,24 @@ export const ProgramDetail = ({
<Page>
<Box padding="$5">
<Container paddingHorizontal="$15" paddingTop="$10" direction="horizontal">
<JumbotronContainer>
<Jumbotron source={programInfo.image} />
</JumbotronContainer>
<DefaultFocus>
<SpatialNavigationNode isFocusable>
{({ isFocused }) => (
<JumbotronContainer isFocused={isFocused}>
<Jumbotron source={programInfo.image} />
</JumbotronContainer>
)}
</SpatialNavigationNode>
<Box padding="$15" flex={1}>
<Typography variant="title" fontWeight="strong">
{programInfo.title}
</Typography>
<Spacer gap="$15" />
<Description variant="body" fontWeight="strong">
{programInfo.description}
</Description>
<Spacer gap="$8" />
<Button label="Play" />
<Spacer gap="$8" />
<Button label="More info" />
</Box>
</DefaultFocus>
<Box padding="$15" flex={1}>
<Typography variant="title" fontWeight="strong">
{programInfo.title}
</Typography>
<Spacer gap="$15" />
<Description variant="body" fontWeight="strong">
{programInfo.description}
</Description>
</Box>
</Container>
<Spacer gap="$5" />
<ProgramListWithTitle title="You may also like..."></ProgramListWithTitle>
Expand All @@ -49,14 +50,12 @@ const Container = styled(Box)({
height: '60%',
});

const JumbotronContainer = styled.View<{ isFocused: boolean }>(({ isFocused }) => ({
const JumbotronContainer = styled.View({
width: '60%',
height: '100%',
overflow: 'hidden',
borderRadius: 20,
borderWidth: 3,
borderColor: isFocused ? 'white' : 'transparent',
}));
});

const Jumbotron = styled.Image({
width: '100%',
Expand Down
3 changes: 2 additions & 1 deletion packages/example/src/pages/ProgramGridPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DefaultFocus, SpatialNavigationScrollView } from 'react-tv-space-naviga
import { Page } from '../components/Page';
import { VirtualizedSpatialGrid } from '../components/VirtualizedSpatialGrid';
import '../components/configureRemoteControl';
import { scaledPixels } from '../design-system/helpers/scaledPixels';

export const ProgramGridPage = () => {
return (
Expand All @@ -19,5 +20,5 @@ export const ProgramGridPage = () => {
};

const styles = StyleSheet.create({
container: { padding: 40, flex: 1 },
container: { padding: scaledPixels(40), flex: 1 },
});

0 comments on commit 51cee5a

Please sign in to comment.