✨ More global filters om file list
This commit is contained in:
@@ -12,6 +12,9 @@ class CloudFileListNotifier extends _$CloudFileListNotifier
|
|||||||
with CursorPagingNotifierMixin<FileListItem> {
|
with CursorPagingNotifierMixin<FileListItem> {
|
||||||
String _currentPath = '/';
|
String _currentPath = '/';
|
||||||
String? _poolId;
|
String? _poolId;
|
||||||
|
String? _query;
|
||||||
|
String? _order;
|
||||||
|
bool _orderDesc = false;
|
||||||
|
|
||||||
void setPath(String path) {
|
void setPath(String path) {
|
||||||
_currentPath = path;
|
_currentPath = path;
|
||||||
@@ -23,6 +26,21 @@ class CloudFileListNotifier extends _$CloudFileListNotifier
|
|||||||
ref.invalidateSelf();
|
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
|
@override
|
||||||
Future<CursorPagingData<FileListItem>> build() => fetch(cursor: null);
|
Future<CursorPagingData<FileListItem>> build() => fetch(cursor: null);
|
||||||
|
|
||||||
@@ -38,6 +56,16 @@ class CloudFileListNotifier extends _$CloudFileListNotifier
|
|||||||
queryParameters['pool'] = _poolId!;
|
queryParameters['pool'] = _poolId!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_query != null) {
|
||||||
|
queryParameters['query'] = _query!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_order != null) {
|
||||||
|
queryParameters['order'] = _order!;
|
||||||
|
}
|
||||||
|
|
||||||
|
queryParameters['orderDesc'] = _orderDesc.toString();
|
||||||
|
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
'/drive/index/browse',
|
'/drive/index/browse',
|
||||||
queryParameters: queryParameters,
|
queryParameters: queryParameters,
|
||||||
@@ -72,6 +100,9 @@ class UnindexedFileListNotifier extends _$UnindexedFileListNotifier
|
|||||||
with CursorPagingNotifierMixin<FileListItem> {
|
with CursorPagingNotifierMixin<FileListItem> {
|
||||||
String? _poolId;
|
String? _poolId;
|
||||||
bool _recycled = false;
|
bool _recycled = false;
|
||||||
|
String? _query;
|
||||||
|
String? _order;
|
||||||
|
bool _orderDesc = false;
|
||||||
|
|
||||||
void setPool(String? poolId) {
|
void setPool(String? poolId) {
|
||||||
_poolId = poolId;
|
_poolId = poolId;
|
||||||
@@ -83,6 +114,21 @@ class UnindexedFileListNotifier extends _$UnindexedFileListNotifier
|
|||||||
ref.invalidateSelf();
|
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
|
@override
|
||||||
Future<CursorPagingData<FileListItem>> build() => fetch(cursor: null);
|
Future<CursorPagingData<FileListItem>> build() => fetch(cursor: null);
|
||||||
|
|
||||||
@@ -108,6 +154,16 @@ class UnindexedFileListNotifier extends _$UnindexedFileListNotifier
|
|||||||
queryParameters['recycled'] = _recycled.toString();
|
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(
|
final response = await client.get(
|
||||||
'/drive/index/unindexed',
|
'/drive/index/unindexed',
|
||||||
queryParameters: queryParameters,
|
queryParameters: queryParameters,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:desktop_drop/desktop_drop.dart';
|
import 'package:desktop_drop/desktop_drop.dart';
|
||||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:easy_localization/easy_localization.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/services/responsive.dart';
|
||||||
import 'package:island/utils/file_icon_utils.dart';
|
import 'package:island/utils/file_icon_utils.dart';
|
||||||
import 'package:island/utils/format.dart';
|
import 'package:island/utils/format.dart';
|
||||||
|
import 'package:island/utils/text.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
|
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
|
||||||
@@ -120,6 +123,10 @@ class FileListView extends HookConsumerWidget {
|
|||||||
final cloudNotifier = ref.read(cloudFileListNotifierProvider.notifier);
|
final cloudNotifier = ref.read(cloudFileListNotifierProvider.notifier);
|
||||||
final recycled = useState<bool>(false);
|
final recycled = useState<bool>(false);
|
||||||
final poolsAsync = ref.watch(poolsProvider);
|
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(() {
|
useEffect(() {
|
||||||
// Sync pool when mode or selectedPool changes
|
// Sync pool when mode or selectedPool changes
|
||||||
@@ -131,75 +138,19 @@ class FileListView extends HookConsumerWidget {
|
|||||||
return null;
|
return null;
|
||||||
}, [selectedPool.value, mode.value]);
|
}, [selectedPool.value, mode.value]);
|
||||||
|
|
||||||
final poolDropdownItems = poolsAsync.when(
|
useEffect(() {
|
||||||
data:
|
// Sync query, order, and orderDesc filters
|
||||||
(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) {
|
if (mode.value == FileListMode.unindexed) {
|
||||||
unindexedNotifier.setPool(value?.id);
|
unindexedNotifier.setQuery(query.value);
|
||||||
|
unindexedNotifier.setOrder(order.value);
|
||||||
|
unindexedNotifier.setOrderDesc(orderDesc.value);
|
||||||
} else {
|
} else {
|
||||||
cloudNotifier.setPool(value?.id);
|
cloudNotifier.setQuery(query.value);
|
||||||
|
cloudNotifier.setOrder(order.value);
|
||||||
|
cloudNotifier.setOrderDesc(orderDesc.value);
|
||||||
}
|
}
|
||||||
},
|
return null;
|
||||||
customButton: Container(
|
}, [query.value, order.value, orderDesc.value, mode.value]);
|
||||||
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),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
late Widget pathContent;
|
late Widget pathContent;
|
||||||
if (mode.value == FileListMode.unindexed) {
|
if (mode.value == FileListMode.unindexed) {
|
||||||
@@ -310,7 +261,20 @@ class FileListView extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
poolDropdown.padding(horizontal: 16),
|
_buildGlobalFilters(
|
||||||
|
ref,
|
||||||
|
poolsAsync,
|
||||||
|
selectedPool,
|
||||||
|
mode,
|
||||||
|
currentPath,
|
||||||
|
isRefreshing,
|
||||||
|
unindexedNotifier,
|
||||||
|
cloudNotifier,
|
||||||
|
query,
|
||||||
|
order,
|
||||||
|
orderDesc,
|
||||||
|
queryDebounceTimer,
|
||||||
|
),
|
||||||
const Gap(6),
|
const Gap(6),
|
||||||
Card(
|
Card(
|
||||||
child: Padding(
|
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