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