💄 Lift file list search to appbar

This commit is contained in:
2026-01-17 21:58:27 +08:00
parent 2e90d243de
commit 6487a1ff65
2 changed files with 193 additions and 215 deletions

View File

@@ -1347,232 +1347,194 @@ class FileListView extends HookConsumerWidget {
ValueNotifier<bool> orderDesc,
ObjectRef<Timer?> queryDebounceTimer,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Search bar below chips
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: SearchBar(
constraints: const BoxConstraints(minHeight: 48),
elevation: WidgetStatePropertyAll(2),
hintText: 'Search files...',
onChanged: (value) {
queryDebounceTimer.value?.cancel();
queryDebounceTimer.value = Timer(
const Duration(milliseconds: 300),
() {
query.value = value.isEmpty ? null : value;
},
);
},
leading: const Icon(Symbols.search).padding(horizontal: 24),
),
),
const Gap(12),
// Chips row
SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
child: Row(
children: [
// Pool filter dropdown
Container(
height: 32,
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(
ref.context,
).colorScheme.outline.withOpacity(0.5),
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
child: Row(
children: [
// Pool filter dropdown
Container(
height: 32,
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(ref.context).colorScheme.outline.withOpacity(0.5),
),
borderRadius: BorderRadius.circular(8),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<SnFilePool>(
value: selectedPool.value,
items: [
const DropdownMenuItem<SnFilePool>(
value: null,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Symbols.database, size: 16),
Gap(6),
Text('All files', style: TextStyle(fontSize: 12)),
],
),
),
borderRadius: BorderRadius.circular(8),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<SnFilePool>(
value: selectedPool.value,
items: [
const DropdownMenuItem<SnFilePool>(
value: null,
...poolsAsync.maybeWhen(
data: (pools) => pools.map(
(pool) => DropdownMenuItem<SnFilePool>(
value: pool,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Symbols.database, size: 16),
Gap(6),
Text('All files', style: TextStyle(fontSize: 12)),
Text(
pool.name,
style: const TextStyle(fontSize: 12),
),
],
),
),
...poolsAsync.maybeWhen(
data: (pools) => pools.map(
(pool) => DropdownMenuItem<SnFilePool>(
value: pool,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Symbols.database, size: 16),
Gap(6),
Text(
pool.name,
style: const TextStyle(fontSize: 12),
),
],
),
),
orElse: () => <DropdownMenuItem<SnFilePool>>[],
),
],
onChanged: isRefreshing
? null
: (value) {
selectedPool.value = value;
if (mode.value == FileListMode.unindexed) {
unindexedNotifier.setPool(value?.id);
} else {
cloudNotifier.setPool(value?.id);
}
},
icon: const Icon(Symbols.arrow_drop_down, size: 16),
isDense: true,
),
),
),
const Gap(8),
// Order filter dropdown
Container(
height: 32,
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(ref.context).colorScheme.outline.withOpacity(0.5),
),
borderRadius: BorderRadius.circular(8),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: order.value ?? 'date',
items: [
DropdownMenuItem<String>(
value: 'date',
child: Row(
spacing: 6,
mainAxisSize: MainAxisSize.min,
children: [
Icon(Symbols.schedule, size: 16),
Text('Date', style: const TextStyle(fontSize: 12)),
if (order.value == 'date')
Icon(
orderDesc.value ? Symbols.arrow_downward : Symbols.arrow_upward,
size: 14,
),
),
orElse: () => <DropdownMenuItem<SnFilePool>>[],
),
],
onChanged: isRefreshing
? null
: (value) {
selectedPool.value = value;
if (mode.value == FileListMode.unindexed) {
unindexedNotifier.setPool(value?.id);
} else {
cloudNotifier.setPool(value?.id);
}
},
icon: const Icon(Symbols.arrow_drop_down, size: 16),
isDense: true,
],
),
),
),
),
const Gap(8),
// Order filter dropdown
Container(
height: 32,
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(
ref.context,
).colorScheme.outline.withOpacity(0.5),
DropdownMenuItem<String>(
value: 'size',
child: Row(
spacing: 6,
mainAxisSize: MainAxisSize.min,
children: [
Icon(Symbols.data_usage, size: 16),
Text(
'fileSize'.tr(),
style: const TextStyle(fontSize: 12),
),
if (order.value == 'size')
Icon(
orderDesc.value ? Symbols.arrow_downward : Symbols.arrow_upward,
size: 16,
),
],
),
),
borderRadius: BorderRadius.circular(8),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: order.value ?? 'date',
items: [
DropdownMenuItem<String>(
value: 'date',
child: Row(
spacing: 6,
mainAxisSize: MainAxisSize.min,
children: [
Icon(Symbols.schedule, size: 16),
Text('Date', style: const TextStyle(fontSize: 12)),
if (order.value == 'date')
Icon(
orderDesc.value
? Symbols.arrow_downward
: Symbols.arrow_upward,
size: 14,
),
],
DropdownMenuItem<String>(
value: 'name',
child: Row(
mainAxisSize: MainAxisSize.min,
spacing: 6,
children: [
Icon(Symbols.sort_by_alpha, size: 16),
Text(
'fileName'.tr(),
style: const TextStyle(fontSize: 12),
),
),
DropdownMenuItem<String>(
value: 'size',
child: Row(
spacing: 6,
mainAxisSize: MainAxisSize.min,
children: [
Icon(Symbols.data_usage, size: 16),
Text(
'fileSize'.tr(),
style: const TextStyle(fontSize: 12),
),
if (order.value == 'size')
Icon(
orderDesc.value
? Symbols.arrow_downward
: Symbols.arrow_upward,
size: 16,
),
],
),
),
DropdownMenuItem<String>(
value: 'name',
child: Row(
mainAxisSize: MainAxisSize.min,
spacing: 6,
children: [
Icon(Symbols.sort_by_alpha, size: 16),
Text(
'fileName'.tr(),
style: const TextStyle(fontSize: 12),
),
if (order.value == 'name')
Icon(
orderDesc.value
? Symbols.arrow_downward
: Symbols.arrow_upward,
size: 16,
),
],
),
),
],
onChanged: (value) {
if (value == order.value) {
// Toggle direction if same option selected
final newValue = !orderDesc.value;
orderDesc.value = newValue;
if (mode.value == FileListMode.unindexed) {
unindexedNotifier.setOrderDesc(newValue);
} else {
cloudNotifier.setOrderDesc(newValue);
}
} else {
// Change sort option
order.value = value;
if (mode.value == FileListMode.unindexed) {
unindexedNotifier.setOrder(value);
} else {
cloudNotifier.setOrder(value);
}
}
},
icon: const SizedBox.shrink(),
isDense: true,
if (order.value == 'name')
Icon(
orderDesc.value ? Symbols.arrow_downward : Symbols.arrow_upward,
size: 16,
),
],
),
),
),
),
const Gap(8),
// Refresh chip
FilterChip(
label: const Row(
mainAxisSize: MainAxisSize.min,
spacing: 6,
children: [
Icon(Symbols.refresh, size: 16),
Text('Refresh', style: TextStyle(fontSize: 12)),
],
),
selected: false,
onSelected: (selected) {
if (selected) {
],
onChanged: (value) {
if (value == order.value) {
// Toggle direction if same option selected
final newValue = !orderDesc.value;
orderDesc.value = newValue;
if (mode.value == FileListMode.unindexed) {
ref.invalidate(unindexedFileListProvider);
unindexedNotifier.setOrderDesc(newValue);
} else {
cloudNotifier.setPath(currentPath.value);
cloudNotifier.setOrderDesc(newValue);
}
} else {
// Change sort option
order.value = value;
if (mode.value == FileListMode.unindexed) {
unindexedNotifier.setOrder(value);
} else {
cloudNotifier.setOrder(value);
}
}
},
icon: const SizedBox.shrink(),
isDense: true,
),
],
),
),
),
],
const Gap(8),
// Refresh chip
FilterChip(
label: const Row(
mainAxisSize: MainAxisSize.min,
spacing: 6,
children: [
Icon(Symbols.refresh, size: 16),
Text('Refresh', style: TextStyle(fontSize: 12)),
],
),
selected: false,
onSelected: (selected) {
if (selected) {
if (mode.value == FileListMode.unindexed) {
ref.invalidate(unindexedFileListProvider);
} else {
cloudNotifier.setPath(currentPath.value);
}
}
},
),
],
),
);
}
}
}