feat: add a dedicated ScrollController shared between the Scrollbar and ListView inside

This commit is contained in:
Udhay-Adithya
2025-09-27 20:27:17 +05:30
parent 3ba416796d
commit d2a5a774df

View File

@@ -42,113 +42,120 @@ Future<List<OpenApiOperationItem>?> showOpenApiOperationPickerDialog({
bool selectAll = ops.isNotEmpty; bool selectAll = ops.isNotEmpty;
String searchQuery = ''; String searchQuery = '';
return showDialog<List<OpenApiOperationItem>>( final scrollController = ScrollController();
context: context, try {
useRootNavigator: true, return await showDialog<List<OpenApiOperationItem>>(
builder: (ctx) { context: context,
return StatefulBuilder(builder: (ctx, setState) { useRootNavigator: true,
// Filter operations based on search query builder: (ctx) {
final filteredOps = <int>[]; return StatefulBuilder(builder: (ctx, setState) {
for (int i = 0; i < ops.length; i++) { // Filter operations based on search query
final o = ops[i]; final filteredOps = <int>[];
final label = '${o.method} ${o.path}'.toLowerCase(); for (int i = 0; i < ops.length; i++) {
if (searchQuery.isEmpty || final o = ops[i];
label.contains(searchQuery.toLowerCase())) { final label = '${o.method} ${o.path}'.toLowerCase();
filteredOps.add(i); if (searchQuery.isEmpty ||
label.contains(searchQuery.toLowerCase())) {
filteredOps.add(i);
}
} }
}
return AlertDialog( return AlertDialog(
title: Text('Import from $title'), title: Text('Import from $title'),
content: SizedBox( content: SizedBox(
width: 520, width: 520,
height: 420, height: 420,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
// TODO: Create a common Search field widget // TODO: Create a common Search field widget
Padding( Padding(
padding: const EdgeInsets.only(bottom: 16.0), padding: const EdgeInsets.only(bottom: 16.0),
child: TextField( child: TextField(
onChanged: (value) { onChanged: (value) {
setState(() {
searchQuery = value;
});
},
decoration: const InputDecoration(
labelText: 'Search routes',
hintText: 'Type to filter routes...',
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(),
),
),
),
// Select all checkbox
CheckboxListTile(
value: selectAll,
onChanged: (v) {
setState(() { setState(() {
searchQuery = value; selectAll = v ?? false;
selected
..clear()
..addAll(selectAll
? List<int>.generate(ops.length, (i) => i)
: const <int>{});
}); });
}, },
decoration: const InputDecoration( title: const Text('Select all'),
labelText: 'Search routes', controlAffinity: ListTileControlAffinity.leading,
hintText: 'Type to filter routes...', ),
prefixIcon: Icon(Icons.search), // Routes list
border: OutlineInputBorder(), Expanded(
child: Scrollbar(
controller: scrollController,
thumbVisibility: true,
child: ListView.builder(
controller: scrollController,
itemCount: filteredOps.length,
itemBuilder: (c, index) {
final i = filteredOps[index];
final o = ops[i];
final label = '${o.method} ${o.path}';
final checked = selected.contains(i);
return CheckboxListTile(
value: checked,
onChanged: (v) {
setState(() {
if (v == true) {
selected.add(i);
} else {
selected.remove(i);
}
selectAll = selected.length == ops.length;
});
},
title: Text(label),
controlAffinity: ListTileControlAffinity.leading,
);
},
),
), ),
), ),
), ],
// Select all checkbox ),
CheckboxListTile( ),
value: selectAll, actions: [
onChanged: (v) { TextButton(
setState(() { onPressed: () => Navigator.of(ctx).pop(null),
selectAll = v ?? false; child: const Text('Cancel'),
selected ),
..clear() FilledButton(
..addAll(selectAll onPressed: selected.isEmpty
? List<int>.generate(ops.length, (i) => i) ? null
: const <int>{}); : () {
}); final result = selected.map((i) => ops[i]).toList();
}, Navigator.of(ctx).pop(result);
title: const Text('Select all'),
controlAffinity: ListTileControlAffinity.leading,
),
// Routes list
Expanded(
child: Scrollbar(
thumbVisibility: true,
child: ListView.builder(
itemCount: filteredOps.length,
itemBuilder: (c, index) {
final i = filteredOps[index];
final o = ops[i];
final label = '${o.method} ${o.path}';
final checked = selected.contains(i);
return CheckboxListTile(
value: checked,
onChanged: (v) {
setState(() {
if (v == true) {
selected.add(i);
} else {
selected.remove(i);
}
selectAll = selected.length == ops.length;
});
},
title: Text(label),
controlAffinity: ListTileControlAffinity.leading,
);
}, },
), child: const Text('Import'),
), ),
), ],
], );
), });
), },
actions: [ );
TextButton( } finally {
onPressed: () => Navigator.of(ctx).pop(null), scrollController.dispose();
child: const Text('Cancel'), }
),
FilledButton(
onPressed: selected.isEmpty
? null
: () {
final result = selected.map((i) => ops[i]).toList();
Navigator.of(ctx).pop(result);
},
child: const Text('Import'),
),
],
);
});
},
);
} }