💄 Lift file list search to appbar
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
import 'package:cross_file/cross_file.dart';
|
import 'package:cross_file/cross_file.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@@ -33,21 +32,33 @@ class FileListScreen extends HookConsumerWidget {
|
|||||||
final viewMode = useState(FileListViewMode.list);
|
final viewMode = useState(FileListViewMode.list);
|
||||||
final isSelectionMode = useState<bool>(false);
|
final isSelectionMode = useState<bool>(false);
|
||||||
final recycled = useState<bool>(false);
|
final recycled = useState<bool>(false);
|
||||||
|
final query = useState<String?>(null);
|
||||||
|
|
||||||
final unindexedNotifier = ref.read(unindexedFileListProvider.notifier);
|
final unindexedNotifier = ref.read(unindexedFileListProvider.notifier);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
isNoBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(
|
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'),
|
leading: const PageBackButton(backTo: '/account'),
|
||||||
actions: [
|
actions: [
|
||||||
// Selection mode toggle
|
// Selection mode toggle
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
isSelectionMode.value
|
isSelectionMode.value ? Symbols.close : Symbols.select_check_box,
|
||||||
? Symbols.close
|
|
||||||
: Symbols.select_check_box,
|
|
||||||
),
|
),
|
||||||
onPressed: () => isSelectionMode.value = !isSelectionMode.value,
|
onPressed: () => isSelectionMode.value = !isSelectionMode.value,
|
||||||
tooltip: isSelectionMode.value
|
tooltip: isSelectionMode.value
|
||||||
@@ -82,9 +93,14 @@ class FileListScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
floatingActionButton: mode.value == FileListMode.normal
|
floatingActionButton: mode.value == FileListMode.normal
|
||||||
? FloatingActionButton(
|
? FloatingActionButton(
|
||||||
onPressed: () => _showActionBottomSheet(context, ref, currentPath, selectedPool),
|
onPressed: () => _showActionBottomSheet(
|
||||||
child: const Icon(Symbols.add),
|
context,
|
||||||
|
ref,
|
||||||
|
currentPath,
|
||||||
|
selectedPool,
|
||||||
|
),
|
||||||
tooltip: 'Add files or create directory',
|
tooltip: 'Add files or create directory',
|
||||||
|
child: const Icon(Symbols.add),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
body: usageAsync.when(
|
body: usageAsync.when(
|
||||||
@@ -242,11 +258,11 @@ class FileListScreen extends HookConsumerWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder: (context) => SheetScaffold(
|
builder: (context) => SheetScaffold(
|
||||||
titleText: 'Usage Overview',
|
|
||||||
child: UsageOverviewWidget(
|
child: UsageOverviewWidget(
|
||||||
usage: usage,
|
usage: usage,
|
||||||
quota: quota,
|
quota: quota,
|
||||||
).padding(horizontal: 8, vertical: 16),
|
).padding(horizontal: 8, vertical: 16),
|
||||||
|
titleText: 'Usage Overview',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1347,232 +1347,194 @@ class FileListView extends HookConsumerWidget {
|
|||||||
ValueNotifier<bool> orderDesc,
|
ValueNotifier<bool> orderDesc,
|
||||||
ObjectRef<Timer?> queryDebounceTimer,
|
ObjectRef<Timer?> queryDebounceTimer,
|
||||||
) {
|
) {
|
||||||
return Column(
|
return SingleChildScrollView(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
children: [
|
scrollDirection: Axis.horizontal,
|
||||||
// Search bar below chips
|
child: Row(
|
||||||
Padding(
|
children: [
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
// Pool filter dropdown
|
||||||
child: SearchBar(
|
Container(
|
||||||
constraints: const BoxConstraints(minHeight: 48),
|
height: 32,
|
||||||
elevation: WidgetStatePropertyAll(2),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
hintText: 'Search files...',
|
decoration: BoxDecoration(
|
||||||
onChanged: (value) {
|
border: Border.all(
|
||||||
queryDebounceTimer.value?.cancel();
|
color: Theme.of(ref.context).colorScheme.outline.withOpacity(0.5),
|
||||||
queryDebounceTimer.value = Timer(
|
),
|
||||||
const Duration(milliseconds: 300),
|
borderRadius: BorderRadius.circular(8),
|
||||||
() {
|
),
|
||||||
query.value = value.isEmpty ? null : value;
|
child: DropdownButtonHideUnderline(
|
||||||
},
|
child: DropdownButton<SnFilePool>(
|
||||||
);
|
value: selectedPool.value,
|
||||||
},
|
items: [
|
||||||
leading: const Icon(Symbols.search).padding(horizontal: 24),
|
const DropdownMenuItem<SnFilePool>(
|
||||||
),
|
value: null,
|
||||||
),
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
const Gap(12),
|
children: [
|
||||||
|
Icon(Symbols.database, size: 16),
|
||||||
// Chips row
|
Gap(6),
|
||||||
SingleChildScrollView(
|
Text('All files', style: TextStyle(fontSize: 12)),
|
||||||
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),
|
...poolsAsync.maybeWhen(
|
||||||
),
|
data: (pools) => pools.map(
|
||||||
child: DropdownButtonHideUnderline(
|
(pool) => DropdownMenuItem<SnFilePool>(
|
||||||
child: DropdownButton<SnFilePool>(
|
value: pool,
|
||||||
value: selectedPool.value,
|
|
||||||
items: [
|
|
||||||
const DropdownMenuItem<SnFilePool>(
|
|
||||||
value: null,
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(Symbols.database, size: 16),
|
Icon(Symbols.database, size: 16),
|
||||||
Gap(6),
|
Gap(6),
|
||||||
Text('All files', style: TextStyle(fontSize: 12)),
|
Text(
|
||||||
|
pool.name,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...poolsAsync.maybeWhen(
|
),
|
||||||
data: (pools) => pools.map(
|
orElse: () => <DropdownMenuItem<SnFilePool>>[],
|
||||||
(pool) => DropdownMenuItem<SnFilePool>(
|
),
|
||||||
value: pool,
|
],
|
||||||
child: Row(
|
onChanged: isRefreshing
|
||||||
mainAxisSize: MainAxisSize.min,
|
? null
|
||||||
children: [
|
: (value) {
|
||||||
Icon(Symbols.database, size: 16),
|
selectedPool.value = value;
|
||||||
Gap(6),
|
if (mode.value == FileListMode.unindexed) {
|
||||||
Text(
|
unindexedNotifier.setPool(value?.id);
|
||||||
pool.name,
|
} else {
|
||||||
style: const TextStyle(fontSize: 12),
|
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,
|
|
||||||
),
|
),
|
||||||
),
|
DropdownMenuItem<String>(
|
||||||
),
|
value: 'size',
|
||||||
|
child: Row(
|
||||||
const Gap(8),
|
spacing: 6,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
// Order filter dropdown
|
children: [
|
||||||
Container(
|
Icon(Symbols.data_usage, size: 16),
|
||||||
height: 32,
|
Text(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
'fileSize'.tr(),
|
||||||
decoration: BoxDecoration(
|
style: const TextStyle(fontSize: 12),
|
||||||
border: Border.all(
|
),
|
||||||
color: Theme.of(
|
if (order.value == 'size')
|
||||||
ref.context,
|
Icon(
|
||||||
).colorScheme.outline.withOpacity(0.5),
|
orderDesc.value ? Symbols.arrow_downward : Symbols.arrow_upward,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(8),
|
DropdownMenuItem<String>(
|
||||||
),
|
value: 'name',
|
||||||
child: DropdownButtonHideUnderline(
|
child: Row(
|
||||||
child: DropdownButton<String>(
|
mainAxisSize: MainAxisSize.min,
|
||||||
value: order.value ?? 'date',
|
spacing: 6,
|
||||||
items: [
|
children: [
|
||||||
DropdownMenuItem<String>(
|
Icon(Symbols.sort_by_alpha, size: 16),
|
||||||
value: 'date',
|
Text(
|
||||||
child: Row(
|
'fileName'.tr(),
|
||||||
spacing: 6,
|
style: const TextStyle(fontSize: 12),
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
if (order.value == 'name')
|
||||||
DropdownMenuItem<String>(
|
Icon(
|
||||||
value: 'size',
|
orderDesc.value ? Symbols.arrow_downward : Symbols.arrow_upward,
|
||||||
child: Row(
|
size: 16,
|
||||||
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,
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
onChanged: (value) {
|
||||||
|
if (value == order.value) {
|
||||||
const Gap(8),
|
// Toggle direction if same option selected
|
||||||
|
final newValue = !orderDesc.value;
|
||||||
// Refresh chip
|
orderDesc.value = newValue;
|
||||||
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) {
|
if (mode.value == FileListMode.unindexed) {
|
||||||
ref.invalidate(unindexedFileListProvider);
|
unindexedNotifier.setOrderDesc(newValue);
|
||||||
} else {
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user