💄 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

@@ -1,5 +1,4 @@
import 'package:cross_file/cross_file.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -33,21 +32,33 @@ class FileListScreen extends HookConsumerWidget {
final viewMode = useState(FileListViewMode.list);
final isSelectionMode = useState<bool>(false);
final recycled = useState<bool>(false);
final query = useState<String?>(null);
final unindexedNotifier = ref.read(unindexedFileListProvider.notifier);
return AppScaffold(
isNoBackground: false,
appBar: AppBar(
title: Text('files').tr(),
title: SearchBar(
constraints: const BoxConstraints(maxWidth: 400, minHeight: 32),
hintText: 'Search files...',
hintStyle: WidgetStatePropertyAll(TextStyle(fontSize: 14)),
onChanged: (value) {
// Update the query state that will be passed to FileListView
query.value = value.isEmpty ? null : value;
},
leading: Icon(
Symbols.search,
size: 20,
color: Theme.of(context).colorScheme.onSurface,
),
),
leading: const PageBackButton(backTo: '/account'),
actions: [
// Selection mode toggle
IconButton(
icon: Icon(
isSelectionMode.value
? Symbols.close
: Symbols.select_check_box,
isSelectionMode.value ? Symbols.close : Symbols.select_check_box,
),
onPressed: () => isSelectionMode.value = !isSelectionMode.value,
tooltip: isSelectionMode.value
@@ -82,9 +93,14 @@ class FileListScreen extends HookConsumerWidget {
),
floatingActionButton: mode.value == FileListMode.normal
? FloatingActionButton(
onPressed: () => _showActionBottomSheet(context, ref, currentPath, selectedPool),
child: const Icon(Symbols.add),
onPressed: () => _showActionBottomSheet(
context,
ref,
currentPath,
selectedPool,
),
tooltip: 'Add files or create directory',
child: const Icon(Symbols.add),
)
: null,
body: usageAsync.when(
@@ -242,11 +258,11 @@ class FileListScreen extends HookConsumerWidget {
context: context,
isScrollControlled: true,
builder: (context) => SheetScaffold(
titleText: 'Usage Overview',
child: UsageOverviewWidget(
usage: usage,
quota: quota,
).padding(horizontal: 8, vertical: 16),
titleText: 'Usage Overview',
),
);
}
@@ -289,4 +305,4 @@ class FileListScreen extends HookConsumerWidget {
),
);
}
}
}

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);
}
}
},
),
],
),
);
}
}
}