mirror of
https://github.com/foss42/apidash.git
synced 2025-12-02 02:39:19 +08:00
feat: add a dedicated ScrollController shared between the Scrollbar and ListView inside
This commit is contained in:
@@ -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'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user