implemented file search with local cache and server-side support, added result highlighting, and integrated search delegate into files page

This commit is contained in:
2026-05-09 23:20:11 +02:00
parent 8e6b1877cc
commit 14090b96f4
10 changed files with 767 additions and 7 deletions
+49 -7
View File
@@ -14,13 +14,25 @@ import '../../../../widget/centered_leading.dart';
import '../../../../widget/confirm_dialog.dart';
import '../../../../widget/details_bottom_sheet.dart';
import '../../../../widget/info_dialog.dart';
import '../../talk/widgets/highlighted_linkify.dart';
import 'file_details_sheet.dart';
class FileElement extends StatefulWidget {
final CacheableFile file;
final List<String> path;
final void Function() refetch;
const FileElement(this.file, this.path, this.refetch, {super.key});
/// When non-null, occurrences of this string in the file name are visually
/// highlighted in the tile title. Used by the Files search delegate.
final String? highlight;
const FileElement(
this.file,
this.path,
this.refetch, {
this.highlight,
super.key,
});
@override
State<FileElement> createState() => _FileElementState();
@@ -118,7 +130,7 @@ class _FileElementState extends State<FileElement> {
);
}
Widget _subtitle() {
Widget? _subtitle() {
final status = _job?.status.value;
if (status is DownloadInProgress) {
return Row(
@@ -135,10 +147,16 @@ class _FileElementState extends State<FileElement> {
],
);
}
final modified = widget.file.modifiedAt ?? DateTime.now();
return widget.file.isDirectory
? Text('geändert ${modified.formatRelative()}')
: Text('${filesize(widget.file.size)}, ${modified.formatRelative()}');
final modified = widget.file.modifiedAt;
final size = widget.file.size;
if (widget.file.isDirectory) {
if (modified == null) return null;
return Text('geändert ${modified.formatRelative()}');
}
if (size == null && modified == null) return null;
if (size == null) return Text(modified!.formatRelative());
if (modified == null) return Text(filesize(size));
return Text('${filesize(size)}, ${modified.formatRelative()}');
}
void _onTap() {
@@ -328,12 +346,36 @@ class _FileElementState extends State<FileElement> {
);
}
Widget _title(BuildContext context) {
final base =
Theme.of(context).textTheme.bodyLarge ??
DefaultTextStyle.of(context).style;
if (widget.highlight == null || widget.highlight!.trim().isEmpty) {
return Text(
widget.file.name,
maxLines: 2,
overflow: TextOverflow.ellipsis,
);
}
return Text.rich(
TextSpan(
children: buildHighlightedSpans(
text: widget.file.name,
query: widget.highlight,
baseStyle: base,
),
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
);
}
@override
Widget build(BuildContext context) => ListTile(
leading: CenteredLeading(
Icon(widget.file.isDirectory ? Icons.folder : Icons.description_outlined),
),
title: Text(widget.file.name, maxLines: 2, overflow: TextOverflow.ellipsis),
title: _title(context),
subtitle: _subtitle(),
trailing: Icon(widget.file.isDirectory ? Icons.arrow_right : null),
onTap: _onTap,