@@ -2,13 +2,15 @@ import 'package:nocterm/nocterm.dart';
22import 'package:simutil/components/simutil_icons.dart' ;
33import 'package:simutil/components/simutil_theme.dart' ;
44import 'package:simutil/models/device.dart' ;
5+ import 'package:simutil/models/device_type.dart' ;
56
67class DeviceListComponent extends StatefulComponent {
78 const DeviceListComponent ({
89 super .key,
910 required this .devices,
1011 this .focused = false ,
1112 this .selectedIndex = 0 ,
13+ this .scrollBufferItems = 2 ,
1214 this .onSelectionChanged,
1315 this .onDeviceLaunch,
1416 this .onDeviceShowOptions,
@@ -20,6 +22,7 @@ class DeviceListComponent extends StatefulComponent {
2022 final List <Device > devices;
2123 final bool focused;
2224 final int selectedIndex;
25+ final int scrollBufferItems;
2326 final ValueChanged <int >? onSelectionChanged;
2427 final ValueChanged <Device >? onDeviceLaunch;
2528 final ValueChanged <Device >? onDeviceShowOptions;
@@ -32,6 +35,14 @@ class DeviceListComponent extends StatefulComponent {
3235}
3336
3437class _DeviceListComponentState extends State <DeviceListComponent > {
38+ late final ScrollController _scrollController = ScrollController ();
39+
40+ @override
41+ void dispose () {
42+ _scrollController.dispose ();
43+ super .dispose ();
44+ }
45+
3546 @override
3647 Component build (BuildContext context) {
3748 final st = SimutilTheme .of (context);
@@ -60,6 +71,7 @@ class _DeviceListComponentState extends State<DeviceListComponent> {
6071 focused: component.focused,
6172 onKeyEvent: _handleKeyEvent,
6273 child: ListView .builder (
74+ controller: _scrollController,
6375 itemCount: component.devices.length,
6476 itemBuilder: (context, index) {
6577 final device = component.devices[index];
@@ -76,44 +88,64 @@ class _DeviceListComponentState extends State<DeviceListComponent> {
7688
7789 bool _handleKeyEvent (KeyboardEvent event) {
7890 if (component.devices.isEmpty) return false ;
79-
80- if (event.logicalKey == LogicalKey .arrowUp) {
81- final newIndex = (component.selectedIndex - 1 ).clamp (
82- 0 ,
83- component.devices.length - 1 ,
84- );
85- component.onSelectionChanged? .call (newIndex);
86- return true ;
91+ switch (event.logicalKey) {
92+ case LogicalKey .arrowUp:
93+ _handleArrowUp ();
94+ return true ;
95+ case LogicalKey .arrowDown:
96+ _handleArrowDown ();
97+ return true ;
98+ case LogicalKey .enter:
99+ _handleEnter ();
100+ return true ;
101+ case LogicalKey .space:
102+ _handleSpace ();
103+ return true ;
104+ default :
105+ return false ;
87106 }
107+ }
88108
89- if (event.logicalKey == LogicalKey .arrowDown) {
90- final newIndex = (component.selectedIndex + 1 ).clamp (
91- 0 ,
92- component.devices.length - 1 ,
93- );
94- component.onSelectionChanged? .call (newIndex);
95- return true ;
96- }
109+ void _handleArrowUp () {
110+ final newIndex = (component.selectedIndex - 1 ).clamp (
111+ 0 ,
112+ component.devices.length - 1 ,
113+ );
114+ component.onSelectionChanged? .call (newIndex);
115+ final scrollTarget = (newIndex - component.scrollBufferItems).clamp (
116+ 0 ,
117+ component.devices.length - 1 ,
118+ );
119+ _scrollController.ensureIndexVisible (index: scrollTarget);
120+ }
97121
98- if (event.logicalKey == LogicalKey .enter) {
99- if (component.selectedIndex < component.devices.length) {
100- component.onDeviceLaunch? .call (
101- component.devices[component.selectedIndex],
102- );
103- }
104- return true ;
105- }
122+ void _handleArrowDown () {
123+ final newIndex = (component.selectedIndex + 1 ).clamp (
124+ 0 ,
125+ component.devices.length - 1 ,
126+ );
127+ component.onSelectionChanged? .call (newIndex);
128+ final scrollTarget = (newIndex + component.scrollBufferItems).clamp (
129+ 0 ,
130+ component.devices.length - 1 ,
131+ );
132+ _scrollController.ensureIndexVisible (index: scrollTarget);
133+ }
106134
107- if (event.logicalKey == LogicalKey .space) {
108- if (component.selectedIndex < component.devices.length) {
109- component.onDeviceShowOptions? .call (
110- component.devices[component.selectedIndex],
111- );
112- }
113- return true ;
135+ void _handleEnter () {
136+ if (component.selectedIndex < component.devices.length) {
137+ component.onDeviceLaunch? .call (
138+ component.devices[component.selectedIndex],
139+ );
114140 }
141+ }
115142
116- return false ;
143+ void _handleSpace () {
144+ if (component.selectedIndex < component.devices.length) {
145+ component.onDeviceShowOptions? .call (
146+ component.devices[component.selectedIndex],
147+ );
148+ }
117149 }
118150}
119151
@@ -135,7 +167,10 @@ class _DeviceRow extends StatelessComponent {
135167 child: Text (device.name, style: isSelected ? st.selected : st.body),
136168 ),
137169 Text ('${device .platform } ' , style: st.muted),
138- Text ('${device .state .label } ' , style: stateStyle),
170+ if (device.type == DeviceType .simulator)
171+ Text ('${device .state .label } ' , style: stateStyle)
172+ else
173+ Text ('Physical' , style: st.muted),
139174 ],
140175 );
141176 }
0 commit comments