@@ -31,27 +31,20 @@ class _DropdownCardState extends State<DropdownCard> {
3131 String ? selectedValue;
3232 late BLEData bleData;
3333 StreamSubscription ? _charSubscription;
34- FixedExtentScrollController wheelScroller = FixedExtentScrollController (initialItem: 0 );
35- double _wheelVisibility = 0.0 ;
36- Timer _wheelVisibilityTimer = Timer .periodic (Duration (milliseconds: 500 ), (_wheelVisibilityTimer) {});
37- int _deviceCounter = 0 ;
34+ final ScrollController _scrollController = ScrollController ();
3835
3936 @override
4037 void initState () {
4138 super .initState ();
4239 bleData = BLEDataManager .forDevice (this .widget.device);
4340 buildDevicesMap ();
4441 selectedValue = ddItems.isNotEmpty ? ddItems[0 ] : null ;
45- _wheelVisibilityTimer = Timer .periodic (Duration (milliseconds: 500 ), (_wheelVisibilityTimer) {
46- _wheelVisibility = 1 ;
47- if (mounted) setState (() {});
48- });
4942 try {
5043 _charSubscription = this .bleData.getMyCharacteristic (this .widget.device).onValueReceived.listen ((data) async {
5144 if (mounted) {
5245 buildDevicesMap ();
5346 setState (() {
54- ddItems;
47+ // Trigger rebuild
5548 });
5649 }
5750 });
@@ -62,14 +55,12 @@ class _DropdownCardState extends State<DropdownCard> {
6255
6356 @override
6457 void dispose () {
65- super .dispose ();
6658 _charSubscription? .cancel ();
67- _wheelVisibilityTimer. cancel ();
59+ super . dispose ();
6860 }
6961
7062 void buildDevicesMap () {
71- _deviceCounter = ddItems.length;
72- late List _items;
63+ List _items = [];
7364 ddItems = [this .widget.c["value" ]];
7465 this
7566 .bleData
@@ -104,10 +95,6 @@ class _DropdownCardState extends State<DropdownCard> {
10495 }
10596 //remove duplicates:
10697 ddItems = ddItems.toSet ().toList (); // Remove duplicates
107- if (ddItems.length != _deviceCounter) {
108- // If the length has changed, update the wheel scroller
109- wheelScroller.jumpToItem (ddItems.length);
110- }
11198 }
11299
113100 Future _changeBLEDevice (BuildContext context) async {
@@ -125,114 +112,137 @@ class _DropdownCardState extends State<DropdownCard> {
125112
126113 @override
127114 Widget build (BuildContext context) {
128- return SingleChildScrollView (
115+ return Center (
129116 child: Container (
130117 constraints: BoxConstraints (
131- maxHeight: MediaQuery .of (context).size.height * 0.3 ,
118+ maxHeight: MediaQuery .of (context).size.height * 0.6 ,
119+ maxWidth: MediaQuery .of (context).size.width * 0.9 ,
132120 ),
133121 child: Card (
134- elevation: 15 ,
122+ elevation: 8 ,
135123 shape: RoundedRectangleBorder (
136- borderRadius: BorderRadius .circular (10 ),
124+ borderRadius: BorderRadius .circular (16 ),
137125 ),
138- child: Column (
139- mainAxisSize: MainAxisSize .min,
140- children: < Widget > [
141- Padding (
142- padding: const EdgeInsets .all (8.0 ),
143- child: Text (
144- this .widget.c["humanReadableName" ],
145- style: TextStyle (fontSize: 20 ),
146- textAlign: TextAlign .left,
126+ child: Padding (
127+ padding: const EdgeInsets .symmetric (vertical: 16.0 ),
128+ child: Column (
129+ mainAxisSize: MainAxisSize .min,
130+ children: < Widget > [
131+ Padding (
132+ padding: const EdgeInsets .symmetric (horizontal: 16.0 ),
133+ child: Text (
134+ this .widget.c["humanReadableName" ],
135+ style: Theme .of (context).textTheme.titleLarge? .copyWith (fontWeight: FontWeight .bold),
136+ textAlign: TextAlign .center,
137+ ),
147138 ),
148- ),
149- Text (this .widget.c["value" ]),
150- SizedBox (height: 20 ),
151- Expanded (
152- child: AnimatedOpacity (
153- opacity: _wheelVisibility,
154- duration: const Duration (seconds: 1 ),
155- child: ListWheelScrollView .useDelegate (
156- itemExtent: 50 ,
157- diameterRatio: 2.5 ,
158- perspective: 0.005 ,
159- clipBehavior: Clip .none,
160- renderChildrenOutsideViewport: true ,
161- physics: FixedExtentScrollPhysics (),
162- controller: wheelScroller,
163- childDelegate: ListWheelChildBuilderDelegate (
164- childCount: ddItems.length,
165- builder: (BuildContext context, int index) {
166- return SizedBox (
167- width: MediaQuery .of (context).size.width * 0.65 ,
168- child: Container (
169- decoration: BoxDecoration (
170- borderRadius: BorderRadius .circular (8 ),
171- border: Border .all (
172- color: Theme .of (context).dividerColor.withOpacity (0.3 ),
173- width: 1 ,
174- ),
175- ),
176- child: ListTile (
177- enableFeedback: true ,
178- shape: RoundedRectangleBorder (
139+ SizedBox (height: 8 ),
140+ Padding (
141+ padding: const EdgeInsets .symmetric (horizontal: 16.0 ),
142+ child: Text (
143+ "Current: ${this .widget .c ["value" ]}" ,
144+ style: Theme .of (context).textTheme.bodyMedium? .copyWith (color: Colors .grey),
145+ ),
146+ ),
147+ Divider (height: 24 ),
148+ Flexible (
149+ child: ddItems.isEmpty
150+ ? Padding (
151+ padding: const EdgeInsets .all (32.0 ),
152+ child: Center (child: Text ("No devices found" )),
153+ )
154+ : ScrollbarTheme (
155+ data: ScrollbarThemeData (
156+ thumbColor: WidgetStatePropertyAll (Colors .blueGrey),
157+ trackVisibility: WidgetStatePropertyAll (true ),
158+ thickness: WidgetStatePropertyAll (20.0 ),
159+ ),
160+ child: Scrollbar (
161+ controller: _scrollController,
162+ thumbVisibility: true ,
163+ child: ListView .separated (
164+ controller: _scrollController,
165+ shrinkWrap: true ,
166+ padding: EdgeInsets .symmetric (horizontal: 8 ),
167+ itemCount: ddItems.length,
168+ separatorBuilder: (ctx, i) => SizedBox (height: 4 ),
169+ itemBuilder: (BuildContext context, int index) {
170+ final item = ddItems[index];
171+ final isSelected = item == this .widget.c["value" ];
172+
173+ return Material (
174+ color: isSelected ? Theme .of (context).primaryColor.withValues (alpha: 0.1 ) : Colors .transparent,
179175 borderRadius: BorderRadius .circular (8 ),
180- ),
181- title: Text (
182- ddItems[index],
183- textAlign: TextAlign .center,
184- textScaler: TextScaler .linear (1.1 ),
185- style: TextStyle (
186- fontWeight: FontWeight .w500,
176+ child: InkWell (
177+ borderRadius: BorderRadius .circular (8 ),
178+ onTap: () {
179+ selectedValue = item;
180+ _changeBLEDevice (context);
181+ },
182+ child: Padding (
183+ padding: const EdgeInsets .symmetric (vertical: 12.0 , horizontal: 16.0 ),
184+ child: Row (
185+ children: [
186+ Expanded (
187+ child: Text (
188+ item,
189+ style: TextStyle (
190+ fontWeight: isSelected ? FontWeight .bold : FontWeight .normal,
191+ fontSize: 16 ,
192+ color: isSelected ? Theme .of (context).primaryColor : null ,
193+ ),
194+ ),
195+ ),
196+ if (isSelected)
197+ Icon (Icons .check_circle, color: Theme .of (context).primaryColor, size: 20 ),
198+ ],
199+ ),
200+ ),
187201 ),
188- ),
189- titleAlignment: ListTileTitleAlignment .center,
190- onTap: () {
191- selectedValue = ddItems[index];
192- _changeBLEDevice (context);
193- },
194- ),
202+ );
203+ },
195204 ),
196- );
197- },
198- ),
205+ ),
206+ ),
207+ ),
208+ Divider (height: 24 ),
209+ Padding (
210+ padding: const EdgeInsets .symmetric (horizontal: 16.0 ),
211+ child: Row (
212+ mainAxisAlignment: MainAxisAlignment .end,
213+ children: < Widget > [
214+ TextButton .icon (
215+ icon: Icon (Icons .refresh),
216+ label: const Text ('SCAN' ),
217+ onPressed: () {
218+ //Find the save command and execute it
219+ this
220+ .bleData
221+ .customCharacteristic
222+ .forEach ((c) => this .bleData.findNSave (this .widget.device, c, scanBLEVname));
223+ }),
224+ Spacer (),
225+ TextButton (
226+ child: const Text ('BACK' ),
227+ onPressed: () {
228+ Navigator .pop (context);
229+ }),
230+ const SizedBox (width: 8 ),
231+ FilledButton (
232+ child: const Text ('SAVE' ),
233+ onPressed: () {
234+ //Find the save command and execute it
235+ this
236+ .bleData
237+ .customCharacteristic
238+ .forEach ((c) => this .bleData.findNSave (this .widget.device, c, saveVname));
239+ Navigator .pop (context);
240+ }),
241+ ],
199242 ),
200243 ),
201- ),
202- SizedBox (height: 20 ),
203- Row (
204- mainAxisAlignment: MainAxisAlignment .end,
205- children: < Widget > [
206- TextButton (
207- child: const Text ('SCAN' ),
208- onPressed: () {
209- //Find the save command and execute it
210- this
211- .bleData
212- .customCharacteristic
213- .forEach ((c) => this .bleData.findNSave (this .widget.device, c, scanBLEVname));
214- }),
215- const SizedBox (width: 8 ),
216- TextButton (
217- child: const Text ('BACK' ),
218- onPressed: () {
219- Navigator .pop (context);
220- }),
221- const SizedBox (width: 8 ),
222- TextButton (
223- child: const Text ('SAVE' ),
224- onPressed: () {
225- //Find the save command and execute it
226- this
227- .bleData
228- .customCharacteristic
229- .forEach ((c) => this .bleData.findNSave (this .widget.device, c, saveVname));
230- Navigator .pop (context);
231- }),
232- const SizedBox (width: 8 ),
233- ],
234- ),
235- ],
244+ ],
245+ ),
236246 ),
237247 ),
238248 ),
0 commit comments