Navigation with Dpad events in flutter tv app

70 views Asked by At

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

0

There are 0 answers