Navigation with Dpad events in flutter tv app

Navigation with Dpad events in flutter tv app

  

I develop a flutter tv app for android tv

the code for the navigation rail :

class CustomNavigationRail extends StatefulWidget {
  const CustomNavigationRail({
    super.key,
  });

  @override
  State<CustomNavigationRail> createState() => _CustomNavigationRailState();
}

class _CustomNavigationRailState extends State<CustomNavigationRail> {
  int index = 0;
  bool isExtended = false;
  final selectedColor = const Color(0XFFFD8B2B);
  final labelStyleOne = TextStyle(
    fontFamily: "Averta",
    fontWeight: FontWeight.normal,
    fontSize: 60.sp,
  );
  final labelStyleTwo = TextStyle(
    fontFamily: "Averta",
    fontWeight: FontWeight.bold,
    fontSize: 60.sp,
  );
  late FocusNode _navigationRailfocusNode;
  late FocusNode _selectedPageFocusNode;
  late FocusNode firstGridItemFocusNode;

  @override
  void initState() {
    isExtended = false;
    super.initState();
    _navigationRailfocusNode = FocusNode();
    _selectedPageFocusNode = FocusNode();
    _selectedPageFocusNode.requestFocus();
    firstGridItemFocusNode = FocusNode();
  }

  @override
  void dispose() {
    _navigationRailfocusNode.dispose();
    _selectedPageFocusNode.dispose();
    firstGridItemFocusNode.dispose();
    super.dispose();
  }

  void _handleKeyEvent(RawKeyEvent event) {
    if (event is RawKeyDownEvent) {
      if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
        _updateIndex(-1);
      } else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
        _updateIndex(1);
      } else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
        setState(() {
          isExtended = false;
          moveToSelectedPage();
        });
      } else if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
        setState(() {
          isExtended = true;
          moveToRail();
        });
      } else if (event.logicalKey == LogicalKeyboardKey.select) {
        setState(() {
          isExtended = false;
        });
      }
    }
  }

  void _updateIndex(int direction) {
    setState(() {
      index = (index + direction) % 5; // Assuming you have 5 destinations
      if (index < 0) {
        index =
            4; // Wrap around to the last index when moving up from the first
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Stack(
      children: [
        Container(
            constraints: const BoxConstraints.expand(),
            decoration: const BoxDecoration(
                image: DecorationImage(
                    image: AssetImage("assets/images/bg-snrt-live.webp"),
                    fit: BoxFit.cover))),
        Row(
          children: [
            Focus(
              focusNode: _navigationRailfocusNode,
              onKey: (node, event) {
                _handleKeyEvent(event);
                return KeyEventResult.handled;
              },
              child: NavigationRail(
                backgroundColor: const Color(0XFF354765),
                selectedIndex: index,
                //labelType: NavigationRailLabelType.all,
                selectedLabelTextStyle: labelStyleOne.copyWith(
                  color: Color(0XFFFD8B2B),
                ),

                unselectedLabelTextStyle:
                    labelStyleOne.copyWith(color: Color(0XFF94A3BC)),
                onDestinationSelected: (index) => setState(
                    () => {this.index = index, isExtended = !isExtended}),
                leading: Image(
                    image: AssetImage("assets/images/side_bar_image.png"),
                    height: 300.h,
                    width: 450.w),
                extended: isExtended,
                destinations: [
                  NavigationRailDestination(
                    icon: SvgPicture.asset(
                      "assets/images/tv.svg",
                      color: Colors.white,
                      height: 50.h,
                      width: 50.w,
                      matchTextDirection: true,
                    ),
                    selectedIcon: SvgPicture.asset(
                      "assets/images/tv.svg",
                      color: const Color(0XFFFD8B2B),
                      height: 50.h,
                      width: 50.w,
                      matchTextDirection: true,
                    ),
                    label: Text(
                      translate('global_keywords.television'),
                    ),
                  ),
                  NavigationRailDestination(
                    icon: SvgPicture.asset(
                      "assets/images/radio.svg",
                      color: Colors.white,
                      height: 50.h,
                      width: 50.w,
                      matchTextDirection: true,
                    ),
                    selectedIcon: SvgPicture.asset(
                      "assets/images/radio.svg",
                      color: const Color(0XFFFD8B2B),
                      height: 50.h,
                      width: 50.w,
                      matchTextDirection: true,
                    ),
                    label: Text(
                      translate('global_keywords.radio'),
                    ),
                  ),
                  NavigationRailDestination(
                    icon: SvgPicture.asset(
                      "assets/images/apropos.svg",
                      color: Colors.white,
                      height: 50.h,
                      width: 50.w,
                      matchTextDirection: true,
                    ),
                    selectedIcon: SvgPicture.asset(
                      "assets/images/apropos.svg",
                      color: const Color(0XFFFD8B2B),
                      height: 50.h,
                      width: 50.w,
                      matchTextDirection: true,
                    ),
                    label: Text(
                      translate('global_keywords.about'),
                    ),
                  ),
                  NavigationRailDestination(
                    icon: SvgPicture.asset(
                      "assets/images/mentions_legales.svg",
                      color: Colors.white,
                      height: 50.h,
                      width: 50.w,
                      matchTextDirection: true,
                    ),
                    selectedIcon: SvgPicture.asset(
                      "assets/images/mentions_legales.svg",
                      color: const Color(0XFFFD8B2B),
                      height: 50.h,
                      width: 50.w,
                      matchTextDirection: true,
                    ),
                    label: Text(
                      translate('global_keywords.mentions'),
                    ),
                  ),
                  NavigationRailDestination(
                    icon: SvgPicture.asset(
                      "assets/images/exit.svg",
                      color: Colors.white,
                      height: 50.h,
                      width: 50.w,
                      matchTextDirection: true,
                    ),
                    selectedIcon: SvgPicture.asset(
                      "assets/images/exit.svg",
                      color: const Color(0XFFFD8B2B),
                      height: 50.h,
                      width: 50.w,
                      matchTextDirection: true,
                    ),
                    label: Text(
                      translate('global_keywords.exit'),
                    ),
                  )
                ],
              ),
            ),
            Expanded(
                child: Focus(
                    focusNode: _selectedPageFocusNode, child: buildPages()))
          ],
        ),
      ],
    ));
  }

  Widget buildPages() {
    switch (index) {
      case 0:
        return ChannelsPage(focusNode: firstGridItemFocusNode,);
      case 1:
        return RadioPage();
      case 2:
        return AboutPage();
      case 3:
        return LegalNoticePage();
      case 4:
        return ExitPage();
      default:
        return ChannelsPage(focusNode: firstGridItemFocusNode,);
    }
  }

  void moveToSelectedPage() {
    _navigationRailfocusNode.unfocus();
    _selectedPageFocusNode.requestFocus();
  }

  void moveToRail() {
    _selectedPageFocusNode.unfocus();
    _navigationRailfocusNode.requestFocus();
  }
}

the navigation rail works fine but wenn i select ChannelsPage for exemple the focus dosn't work like i expected and also wenn the app start the first item in the grid view displayed in ChannelsPage dosn't have a focus.

the code for ChannelsPage :

class ChannelsPage extends StatefulWidget {
  final FocusNode focusNode;
  const ChannelsPage({
    required this.focusNode,
    super.key,
  });

  @override
  State<ChannelsPage> createState() => _ChannelsPageState();
}

class _ChannelsPageState extends State<ChannelsPage> {
  late FeedService _feedService;
  late Response response;
  Dio dio = Dio();
  bool loading = false; //for data fetching status
  bool refresh = true; //for forcing refreshing cache
  String baseUrl = Config.baseUrl;

  @override
  void initState() {
    super.initState();
    _feedService = FeedService();
    dio.interceptors
        .add(DioCacheManager(CacheConfig(baseUrl: baseUrl)).interceptor);
    // Setting the initial focus to the first grid item
    WidgetsBinding.instance.addPostFrameCallback((_) {
      widget.focusNode.requestFocus();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder(
          future: _feedService.getFeeds(dio, 1),
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.done &&
                snapshot.hasData) {
              return Padding(
                padding: EdgeInsets.only(
                    left: 263.w, right: 263.w, top: 190.h, bottom: 150.h),
                child: GridView.builder(
                    physics: const NeverScrollableScrollPhysics(),
                    itemCount: snapshot.data!.length,
                    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                        crossAxisCount: 4,
                        crossAxisSpacing: 66.w,
                        mainAxisSpacing: 65.h),
                    itemBuilder: (BuildContext context, int index) {
                      final feed = snapshot.data![index];
                      return Focus(
                        focusNode: widget.focusNode,
                        child: RawMaterialButton(
                          padding: EdgeInsets.only(
                              left: 25.w, top: 20.h, bottom: 15.h),
                          focusColor: const Color(0XFF354765).withOpacity(0.8),
                          shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(10.r),
                            //side: BorderSide(color: Colors.white),
                          ),
                          child: Padding(
                            padding: EdgeInsets.only(right: 10.w),
                            child: Container(
                                //color: const Color(0XFF354765).withOpacity(0.8),
                                alignment: Alignment.center,
                                height: 500.h,
                                width: 500.w,
                                child: Image.network(
                                  feed.icon,
                                  height: 500.h,
                                  width: 500.w,
                                )),
                          ),
                          onPressed: () {
                            Navigator.push(
                                context,
                                MaterialPageRoute(
                                    builder: (context) => CustomVideoPlayer(
                                          feed: feed,
                                        )));
                          },
                        ),
                      );
                    }),
              );
            }
            return Container();
          }),
    );
    //);
  }
}

i would really appreciate your help to solve this problem

Answer

You have an onKey handler in your navigation rail Focus widget, but you don't have any onKey handler in your ChannelPage's Focus widget. The ChannelPage does have Focus when you switch to it, you can test this by adding an onKey handler to its Focus widget

I also suggest updating to the new version named onKeyEvent since onKey is now deprecated (see example code: here)

© 2024 Dagalaxy. All rights reserved.