bamlab / react-tv-space-navigation

A React Native module to handle spatial navigation for a TV application in a 100% cross-platform way
https://bamlab.github.io/react-tv-space-navigation/
MIT License
201 stars 17 forks source link

A really simple example that demos some navigable touchables without any dependencies #39

Open silencer07 opened 9 months ago

silencer07 commented 9 months ago

Hi,

Thank you so much for this library. TV computing has been an underserved platform that deserves love and apps! This framework helps to the realization of that.

I just have a small problem and maybe this is due to me being a new user of this framework. I tried integrating the components in this library and I cannot figure out what is the problem.

Of course I tried reading the example code, and I tried my hardest to mimic what it is doing and I am not successful :( and I don't know why.

I am thinking maybe a small example that is not fancy. Maybe a 3 touchable boxes that is beside one another which you can navigate using tv remote. no outside dependencies, just a bare react native initiated using CLI.

This would help newcomers like me to grasp what this framework requires and how we can make it work within our apps.

Thank you very much and more power to you guys!

P.S. I realized late I am using the standard react native version in my app. do this framework require a forked version like what I see in the example? i.e. "react-native": "npm:react-native-tvos@0.71.11-0",

pierpo commented 9 months ago

Hey!

About the simple example... that might be a good idea. We wanted to show a complete example and let you extract what you need.

The thing is: setting up the remote control is not that simple. It needs a few tweaks to be able to catch the native keyevents and plug them to the lib.

The lib's API is kind of simple regarding that, but handling it for multiplatform on the consumer's side isn't that simple.

Can you indicate the problems you encounter? Where do you miss the events?

P.S. I realized late I am using the standard react native version in my app. do this framework require a forked version like what I see in the example? i.e. "react-native": "npm:react-native-tvos@0.71.11-0",

You can manage without the TV fork because we don't use anything related... except on Apple tvOS πŸ˜‰ You'll need to fork to build on this platform, unfortunately.

silencer07 commented 9 months ago

Can you indicate the problems you encounter? Where do you miss the events?

Basically my node is not getting even the initial focus even though I properly wrapped it in SpatialNavigationRoot, SpatialNavigationNode and DefaultFocus. I even copy pasted the remote configuration from sample data as well because I thought that is where I am getting it wrong.

TIA!

P.S. I love the complete example. However I think a simple example will help quick troubleshooting for the future users of this framework <3

pierpo commented 9 months ago

Oh, this is weird. The fact that you don't even get the initial default focus shows that the issue is not related to the remote!

Do you have any code snippets to share? I'm very curious about this.

About the simplified example, that's kind of what we wanted to do with the tutorial, but it looks like we failed haha

silencer07 commented 9 months ago

This is the sample code. I truncated it because my code is bad and this is a hobby project I am trying to get deployed before christmas πŸ—‘οΈ

<SpatialNavigationRoot isActive={isFocused}>
      <SafeAreaView style={styles.container}>
        <View
          style={{
            flexBasis: 1,
            flexGrow: 2,
            flexDirection: "row",
          }}
        >
          <View
            style={{
              flex: 4,
              position: "relative",
              padding: 5,
            }}
          >
            <DefaultFocus>
              <SpatialNavigationNode isFocusable>
                {({ isFocused }) => (
                  <View
                    style={{
                      alignItems: "center",
                      justifyContent: "space-evenly",
                      flexDirection: "row",
                      paddingHorizontal: "25%",
                      width: "100%",
                    }}
                  >
                    <DefaultFocus>
                      <CustomButton
                        style={styles.nextButton}
                        disabled={isEmpty(songs)}
                        onPress={() =>
                          videoPlayerRef.current?.next(currentSongId!)
                        }
                      >
                        <Text
                          style={{
                            color: "lightgray",
                            textAlign: "center",
                            fontSize: 12,
                          }}
                        >
                          Next song
                        </Text>
                      </CustomButton>
                    </DefaultFocus>
                    <CustomButton
                      style={styles.linkButton}
                      onPress={() =>
                        navigation.navigate(RootRoutes.Instructions)
                      }
                    >
                      <Text
                        style={{
                          color: "black",
                          textAlign: "center",
                          fontSize: 12,
                        }}
                      >
                        Link Remote
                      </Text>
                    </CustomButton>
                    <CustomButton
                      style={styles.resetButton}
                      onPress={onResetButtonPress}
                    >
                      <Text
                        style={{
                          color: "white",
                          textAlign: "center",
                          fontSize: 12,
                        }}
                      >
                        Reset
                      </Text>
                    </CustomButton>
                  </View>
                )}
              </SpatialNavigationNode>
            </DefaultFocus>
            .....

Custom Button

const highlightedStyle = {
  borderWidth: 2,
  borderColor: "lightgray",
  borderStyle: "solid",
};

export function CustomButton(props: TouchableOpacityProps) {
  return (
    <SpatialNavigationNode
      onFocus={props.onFocus as () => void}
      onBlur={props.onBlur as () => void}
      onSelect={props.disabled ? (props.onPress as () => void) : undefined}
      orientation="horizontal"
      isFocusable={true}
    >
      {({ isFocused }) => {
        let style: Record<string, any> = props.style || {};
        if (isFocused) {
          style = { ...style, ...highlightedStyle };
        }
        return (
          <TouchableOpacity style={style} {...props}>
            {props.children}
          </TouchableOpacity>
        );
      }}
    </SpatialNavigationNode>
  );
}

I copy-pasted the remote configuration and made sure to put it inside the entrypoint of my project import "../components/configureRemoteControl";

Thank you very much!

P.S. I suppose the framework has no problem with absolutely-positioned components right? However in this snippet I already removed it. However I plan to restore the absolute positioning.

Side Note: I am manually doing a full APK build, and installing the said APK manually via a thumb drive to my Xiaomi TV Box 2nd Gen. I believe it has Google TV OS version 12 already :D

manjunath-nuveb commented 9 months ago

@silencer07 `

` when are you setting isFocused to true?
silencer07 commented 9 months ago

@manjunath-nuveb it is from reacat navigation const isFocused = useIsFocused();

Just assume it is always true for the sake of code snippet :D

pierpo commented 9 months ago

@manjunath-nuveb  that's a good check indeed 😊 thanks!

@silencer07 I don't really understand what could be wrong here πŸ€”

I would remove the TouchableOpacity from the buttons. It might mess up with the lib because the whole lib relies on ignoring native focus πŸ˜„

Also, can you try a simpler example in your codebase and check whether it works or not? First, I'd make a simpler button :

const Button = ({ onSelect, label }) => (
 <SpatialNavigationNode
      onSelect={onSelect}
      orientation="horizontal"
      isFocusable={true}
    >
      {({ isFocused }) => {
        return (
          <Text style={{ backgroundColor: isFocused ? 'white' : 'black' }}>{label}</Text>
        );
      }}
    </SpatialNavigationNode>
)

Then, I'd use it in the most simple page:

<SpatialNavigationRoot>
  <SpatialNavigationNode>
    <Button
      onPress={() => console.log('A')}
      label="A"
    />
    <Button
      onPress={() => console.log('B')}
      label="B"
    />
  </SpatialNavigationNode>
</SpatialNavigationRoot>
silencer07 commented 9 months ago

Thanks @pierpo. I would definitely try this on friday and get back to you. I decided to take a two day break for now :D

I really love how you are so engage in this!

silencer07 commented 9 months ago

Hi @pierpo. I have tried what you suggested and the default focus works now!

However it seems that remote does not work, i.e. I cannot move the focus to the next button.

I think I need to change something in the remoteConfiguration sample code I copy pasted here but I cannot pinpoint it. Any clue?

P.S. yep I guess I will still need the simple sample code after all

pierpo commented 9 months ago

Can you share the remote configuration that you used? Are you working on AndroidTV?

silencer07 commented 9 months ago

Hi @pierpo. this is what I did basically

I copy-pasted the remote configuration from example and made sure to put it inside the entrypoint of my project

import "../components/configureRemoteControl";

I am manually doing a full APK build, and installing the said APK manually via a thumb drive to my Xiaomi TV Box 2nd Gen. I believe it has Google TV OS version 12 already :D

pierpo commented 9 months ago

OK, did you properly install react-native-keyevent? This is an important step ;) Also, don't forget to copy remote-control/RemoteControlManager.android.ts. The remote-control/RemoteControlManager.ts is for web.

Install steps for react-native-keyevent: https://github.com/kevinejohn/react-native-keyevent

silencer07 commented 9 months ago

Oh my! I was able to make it work. You are right the react-native-keyevent needs integrated native code.

since I am using expo, I just needed to use https://github.com/ChronSyn/react-native-keyevent-expo-config-plugin and configure it!

This is lovely! I think I am unstuck now!

P.S. your remote configuration is a sensible default, I am thinking maybe we should integrate it to the framework itself.

silencer07 commented 9 months ago

Alright. After fixing the code, my next problem is "onSelect" event not working

This is my custom button now

const highlightedStyle = {
  borderWidth: 2,
  borderColor: "lightgray",
  borderStyle: "solid",
};

export function CustomButton(props: TouchableOpacityProps) {
  return !Platform.isTV ? (
    <TouchableOpacity {...props}>{props.children}</TouchableOpacity>
  ) : (
    <SpatialNavigationNode
      onFocus={props.onFocus as () => void}
      onBlur={props.onBlur as () => void}
      onSelect={!props.disabled ? (props.onPress as () => void) : undefined}
      isFocusable={true}
    >
      {({ isFocused }) => {
        let style: Record<string, any> = props.style || {};
        if (isFocused) {
          style = { ...style, ...highlightedStyle };
        }
        return <View style={style}>{props.children}</View>;
      }}
    </SpatialNavigationNode>
  );
}

and this is how I use it

<CustomButton
  style={styles.linkButton}
  onPress={() =>
    navigation.navigate(RootRoutes.Instructions)
  }
>
  <Text
    style={{
      color: "black",
      textAlign: "center",
      fontSize: 12,
    }}
  >
    Link Remote
  </Text>
</CustomButton>

weirdly enough nothing happens when you press the center button of my remote

EDIT: even enter key of my generic bluetooth keyboard does not work as well


Update: I tried debugging it by adding an Alert message on configureRemoteControl.remoteControlListener

 const remoteControlListener = (keyEvent: SupportedKeys) => {
      Alert.alert("keyEvent", `keyEvent: ${JSON.stringify(keyEvent)}`);
      callback(mapping[keyEvent]);
    };

same with RemoteControlManager.handleKeyDown of RemoteControlManager.android.ts

private handleKeyDown = (keyEvent: { keyCode: number }) => {
    Alert.alert("keyEvent", `keyEvent: ${JSON.stringify(keyEvent)}`);
    .....

And it seems that the center button of my remote is not registering a keyEvent. Up, Down, Left, Right are all registered properly. Is this a manufacturer-specific bug or bug of react-native-keyevent itself?

pierpo commented 9 months ago

Oh, this is probably related to react-native-keyevent indeed. I can't help with that. You should add breakpoints in the native code to see if the enter key is handled?

silencer07 commented 9 months ago

Gotcha, I will just file a new issue in the said library and get back to you. Let's leave this issue open until we found a solution @pierpo

silencer07 commented 9 months ago

@pierpo

I did some more testing, and it seems that doing a long press on center button of remote actually register an "enter" key event.

while this unblocks my further development, It would be nice if there is a workaround I can do to register short press as long press for the mean time :)


Update: Seems that user needs to interact with a system dialog to make the short press work. in my case when I finished asking the user to grant location, I do not need to long press anymore!

silencer07 commented 8 months ago

@pierpo someone pointed me on this one: https://github.com/kevinejohn/react-native-keyevent/issues/52

ticket that i filed in react-native-keyevent library: https://github.com/kevinejohn/react-native-keyevent/issues/80

I will play around and see if this works. if yes, I think we will need a patch-packaged version of react-native-keyevent

ionictest2017 commented 8 months ago
<SpatialNavigationRoot>
  <SpatialNavigationNode>
    <Button
      onPress={() => console.log('A')}
      label="A"
    />
    <Button
      onPress={() => console.log('B')}
      label="B"
    />
  </SpatialNavigationNode>
</SpatialNavigationRoot>

I have tried this simple code in the given example project but it is not working in the Android TV emulator while the focus is working with TouchableOpacity

also, I have put the code for initialization in my React native TV app root file

import { init } from '@noriginmedia/norigin-spatial-navigation';

init({ nativeMode:true, debug:true, visualDebug:true // options });

Please help me, I am trying to implement a menu with the movie swimlane in my React Native tv app

silencer07 commented 7 months ago

@pierpo someone pointed me on this one: kevinejohn/react-native-keyevent#52

ticket that i filed in react-native-keyevent library: kevinejohn/react-native-keyevent#80

I will play around and see if this works. if yes, I think we will need a patch-packaged version of react-native-keyevent

@pierpo seems that there is an additional code needed in the android native side for DPAD_CENTER of a remote to work

https://github.com/ChronSyn/react-native-keyevent-expo-config-plugin/issues/4

I am thinking maybe the example could be updated?

pierpo commented 7 months ago

@silencer07 sorry I had not answered. Thank you for your work on this 😊 We're keeping the issue open and we'll add this to the example indeed.

We just switched the example to Expo so it's convenient to have your patch 😁 (And we added a patch already to support Expo 50)

manjunath-nuveb commented 7 months ago

My issue is similar to this. but happens only when virtual keyboard is opened and closed https://github.com/kevinejohn/react-native-keyevent/issues/83