✨ More global filters om file list
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user