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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user