More global filters om file list

This commit is contained in:
2025-11-18 20:33:49 +08:00
parent 26a24b0e41
commit 4409a6fb1e
2 changed files with 293 additions and 70 deletions

View File

@@ -12,6 +12,9 @@ class CloudFileListNotifier extends _$CloudFileListNotifier
with CursorPagingNotifierMixin<FileListItem> {
String _currentPath = '/';
String? _poolId;
String? _query;
String? _order;
bool _orderDesc = false;
void setPath(String path) {
_currentPath = path;
@@ -23,6 +26,21 @@ class CloudFileListNotifier extends _$CloudFileListNotifier
ref.invalidateSelf();
}
void setQuery(String? query) {
_query = query;
ref.invalidateSelf();
}
void setOrder(String? order) {
_order = order;
ref.invalidateSelf();
}
void setOrderDesc(bool orderDesc) {
_orderDesc = orderDesc;
ref.invalidateSelf();
}
@override
Future<CursorPagingData<FileListItem>> build() => fetch(cursor: null);
@@ -38,6 +56,16 @@ class CloudFileListNotifier extends _$CloudFileListNotifier
queryParameters['pool'] = _poolId!;
}
if (_query != null) {
queryParameters['query'] = _query!;
}
if (_order != null) {
queryParameters['order'] = _order!;
}
queryParameters['orderDesc'] = _orderDesc.toString();
final response = await client.get(
'/drive/index/browse',
queryParameters: queryParameters,
@@ -72,6 +100,9 @@ class UnindexedFileListNotifier extends _$UnindexedFileListNotifier
with CursorPagingNotifierMixin<FileListItem> {
String? _poolId;
bool _recycled = false;
String? _query;
String? _order;
bool _orderDesc = false;
void setPool(String? poolId) {
_poolId = poolId;
@@ -83,6 +114,21 @@ class UnindexedFileListNotifier extends _$UnindexedFileListNotifier
ref.invalidateSelf();
}
void setQuery(String? query) {
_query = query;
ref.invalidateSelf();
}
void setOrder(String? order) {
_order = order;
ref.invalidateSelf();
}
void setOrderDesc(bool orderDesc) {
_orderDesc = orderDesc;
ref.invalidateSelf();
}
@override
Future<CursorPagingData<FileListItem>> build() => fetch(cursor: null);
@@ -108,6 +154,16 @@ class UnindexedFileListNotifier extends _$UnindexedFileListNotifier
queryParameters['recycled'] = _recycled.toString();
}
if (_query != null) {
queryParameters['query'] = _query!;
}
if (_order != null) {
queryParameters['order'] = _order!;
}
queryParameters['orderDesc'] = _orderDesc.toString();
final response = await client.get(
'/drive/index/unindexed',
queryParameters: queryParameters,

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:easy_localization/easy_localization.dart';
@@ -17,6 +19,7 @@ import 'package:island/services/file_uploader.dart';
import 'package:island/services/responsive.dart';
import 'package:island/utils/file_icon_utils.dart';
import 'package:island/utils/format.dart';
import 'package:island/utils/text.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
@@ -120,6 +123,10 @@ class FileListView extends HookConsumerWidget {
final cloudNotifier = ref.read(cloudFileListNotifierProvider.notifier);
final recycled = useState<bool>(false);
final poolsAsync = ref.watch(poolsProvider);
final query = useState<String?>(null);
final order = useState<String?>('date');
final orderDesc = useState<bool>(true);
final queryDebounceTimer = useRef<Timer?>(null);
useEffect(() {
// Sync pool when mode or selectedPool changes
@@ -131,75 +138,19 @@ class FileListView extends HookConsumerWidget {
return null;
}, [selectedPool.value, mode.value]);
final poolDropdownItems = poolsAsync.when(
data:
(pools) => [
const DropdownMenuItem<SnFilePool>(
value: null,
child: Text('All Pools', style: TextStyle(fontSize: 14)),
),
...pools.map(
(p) => DropdownMenuItem<SnFilePool>(
value: p,
child: Text(p.name, style: const TextStyle(fontSize: 14)),
),
),
],
loading: () => const <DropdownMenuItem<SnFilePool>>[],
error: (err, stack) => const <DropdownMenuItem<SnFilePool>>[],
);
final poolDropdown = DropdownButtonHideUnderline(
child: DropdownButton2<SnFilePool>(
value: selectedPool.value,
items: poolDropdownItems,
onChanged:
isRefreshing
? null
: (value) {
selectedPool.value = value;
if (mode.value == FileListMode.unindexed) {
unindexedNotifier.setPool(value?.id);
} else {
cloudNotifier.setPool(value?.id);
}
},
customButton: Container(
height: 28,
width: 200,
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(ref.context).colorScheme.outline,
),
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 6,
children: [
const Icon(Symbols.pool, size: 16),
Flexible(
child: Text(
selectedPool.value?.name ?? 'All files',
maxLines: 1,
overflow: TextOverflow.ellipsis,
).fontSize(12),
),
],
).height(24),
),
buttonStyleData: const ButtonStyleData(
padding: EdgeInsets.zero,
height: 28,
width: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
),
dropdownStyleData: const DropdownStyleData(maxHeight: 200),
),
);
useEffect(() {
// Sync query, order, and orderDesc filters
if (mode.value == FileListMode.unindexed) {
unindexedNotifier.setQuery(query.value);
unindexedNotifier.setOrder(order.value);
unindexedNotifier.setOrderDesc(orderDesc.value);
} else {
cloudNotifier.setQuery(query.value);
cloudNotifier.setOrder(order.value);
cloudNotifier.setOrderDesc(orderDesc.value);
}
return null;
}, [query.value, order.value, orderDesc.value, mode.value]);
late Widget pathContent;
if (mode.value == FileListMode.unindexed) {
@@ -310,7 +261,20 @@ class FileListView extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Gap(12),
poolDropdown.padding(horizontal: 16),
_buildGlobalFilters(
ref,
poolsAsync,
selectedPool,
mode,
currentPath,
isRefreshing,
unindexedNotifier,
cloudNotifier,
query,
order,
orderDesc,
queryDebounceTimer,
),
const Gap(6),
Card(
child: Padding(
@@ -1147,4 +1111,207 @@ class FileListView extends HookConsumerWidget {
),
);
}
Widget _buildGlobalFilters(
WidgetRef ref,
AsyncValue<List<SnFilePool>> poolsAsync,
ValueNotifier<SnFilePool?> selectedPool,
ValueNotifier<FileListMode> mode,
ValueNotifier<String> currentPath,
bool isRefreshing,
dynamic unindexedNotifier,
dynamic cloudNotifier,
ValueNotifier<String?> query,
ValueNotifier<String?> order,
ValueNotifier<bool> orderDesc,
ObjectRef<Timer?> queryDebounceTimer,
) {
final poolDropdownItems = poolsAsync.when(
data:
(pools) => [
const DropdownMenuItem<SnFilePool>(
value: null,
child: Text('All Pools', style: TextStyle(fontSize: 14)),
),
...pools.map(
(p) => DropdownMenuItem<SnFilePool>(
value: p,
child: Text(p.name, style: const TextStyle(fontSize: 14)),
),
),
],
loading: () => const <DropdownMenuItem<SnFilePool>>[],
error: (err, stack) => const <DropdownMenuItem<SnFilePool>>[],
);
final poolDropdown = DropdownButtonHideUnderline(
child: DropdownButton2<SnFilePool>(
value: selectedPool.value,
items: poolDropdownItems,
onChanged:
isRefreshing
? null
: (value) {
selectedPool.value = value;
if (mode.value == FileListMode.unindexed) {
unindexedNotifier.setPool(value?.id);
} else {
cloudNotifier.setPool(value?.id);
}
},
customButton: Container(
height: 28,
width: 200,
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(ref.context).colorScheme.outline,
),
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 6,
children: [
const Icon(Symbols.pool, size: 16),
Flexible(
child: Text(
selectedPool.value?.name ?? 'All files',
maxLines: 1,
overflow: TextOverflow.ellipsis,
).fontSize(12),
),
],
).height(24),
),
buttonStyleData: const ButtonStyleData(
padding: EdgeInsets.zero,
height: 28,
width: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
),
dropdownStyleData: const DropdownStyleData(maxHeight: 200),
),
);
final queryField = SizedBox(
width: 200,
height: 28,
child: TextField(
decoration: InputDecoration(
hintText: 'fileName'.tr(),
isDense: true,
contentPadding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 6,
),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
),
style: const TextStyle(fontSize: 13, height: 1),
onChanged: (value) {
queryDebounceTimer.value?.cancel();
queryDebounceTimer.value = Timer(
const Duration(milliseconds: 300),
() {
query.value = value.isEmpty ? null : value;
},
);
},
),
);
final orderDropdown = DropdownButtonHideUnderline(
child: DropdownButton2<String>(
value: order.value,
items:
['date', 'size', 'name']
.map(
(e) => DropdownMenuItem(
value: e,
child:
Text(
e == 'date' ? e : 'file${e.capitalizeEachWord()}',
style: const TextStyle(fontSize: 14),
).tr(),
),
)
.toList(),
onChanged: (value) => order.value = value,
customButton: Container(
height: 28,
width: 80,
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(ref.context).colorScheme.outline,
),
borderRadius: BorderRadius.circular(8),
),
child: Center(
child:
Text(
(order.value ?? 'date') == 'date'
? (order.value ?? 'date')
: 'file${order.value?.capitalizeEachWord()}',
style: const TextStyle(fontSize: 12),
).tr(),
),
),
buttonStyleData: const ButtonStyleData(
height: 28,
width: 80,
padding: EdgeInsets.zero,
),
dropdownStyleData: const DropdownStyleData(maxHeight: 200),
),
);
final orderDescToggle = IconButton(
icon: Icon(
orderDesc.value ? Symbols.arrow_upward : Symbols.arrow_downward,
),
onPressed: () {
final newValue = !orderDesc.value;
orderDesc.value = newValue;
if (mode.value == FileListMode.unindexed) {
unindexedNotifier.setOrderDesc(newValue);
} else {
cloudNotifier.setOrderDesc(newValue);
}
},
tooltip: orderDesc.value ? 'descendingOrder'.tr() : 'ascendingOrder'.tr(),
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
);
final refreshButton = IconButton(
icon: const Icon(Symbols.refresh),
onPressed: () {
if (mode.value == FileListMode.unindexed) {
ref.invalidate(unindexedFileListNotifierProvider);
} else {
cloudNotifier.setPath(currentPath.value);
}
},
tooltip: 'Refresh',
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
);
return Card(
margin: EdgeInsets.zero,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
spacing: 12,
children: [
poolDropdown,
queryField,
orderDropdown,
orderDescToggle,
refreshButton,
],
).padding(horizontal: 20, vertical: 8),
),
).padding(horizontal: 12);
}
}