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