chat search now lists local and server-side hits like the file search, and jumping to old messages pages in history with a progress banner and keeps the hit marked

This commit is contained in:
MineTec committed 2026-10-28 22:40:21 +01:00
1 parent fcfd21bb4d
commit b9cb6a8af1
13 files changed
+926 -322

No files matched your search

+1 -8
View File
@@ -23,7 +23,7 @@ import 'chat_bubble_reactions.dart';
import 'chat_message_options_dialog.dart';
import 'highlighted_linkify.dart';
enum SearchHighlight { none, secondary, active }
enum SearchHighlight { none, active }
const double _kSpacing = 3;
const double _kTimeIconSize = 11;
@@ -139,13 +139,6 @@ class _ChatBubbleState extends State<ChatBubble>
switch (widget.matchHighlight) {
case SearchHighlight.none:
return base;
case SearchHighlight.secondary:
return base.copyWith(
borderWidth: 1.5,
borderColor: Theme.of(
context,
).colorScheme.primary.withValues(alpha: 0.45),
);
case SearchHighlight.active:
return base.copyWith(
borderWidth: 3,
@@ -1,70 +0,0 @@
import 'package:flutter/material.dart';
class ChatSearchAppBar extends StatelessWidget implements PreferredSizeWidget {
final TextEditingController controller;
final int matchCount;
final int activeIndex;
final ValueChanged<String> onChanged;
final VoidCallback onClose;
final VoidCallback? onPrevious;
final VoidCallback? onNext;
const ChatSearchAppBar({
required this.controller,
required this.matchCount,
required this.activeIndex,
required this.onChanged,
required this.onClose,
required this.onPrevious,
required this.onNext,
super.key,
});
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
@override
Widget build(BuildContext context) {
final counterText = matchCount == 0
? '0/0'
: '${activeIndex + 1}/$matchCount';
return AppBar(
leading: IconButton(
tooltip: 'Zurück',
icon: const Icon(Icons.arrow_back),
onPressed: onClose,
),
title: TextField(
controller: controller,
autofocus: true,
textInputAction: TextInputAction.search,
decoration: const InputDecoration(
hintText: 'In Chat suchen…',
border: InputBorder.none,
),
onChanged: onChanged,
),
actions: [
Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Text(
counterText,
style: const TextStyle(fontSize: 12),
),
),
),
IconButton(
tooltip: 'Vorheriges Ergebnis',
icon: const Icon(Icons.keyboard_arrow_up),
onPressed: onPrevious,
),
IconButton(
tooltip: 'Nächstes Ergebnis',
icon: const Icon(Icons.keyboard_arrow_down),
onPressed: onNext,
),
],
);
}
}
@@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
import '../../../../extensions/date_time.dart';
/// Shown while the chat pages back through its history towards a search hit.
/// Progress is estimated by time, since the server reports no message counts.
class HistoryJumpBanner extends StatelessWidget {
final DateTime startedAt;
final DateTime? target;
final DateTime? loadedUntil;
final VoidCallback onCancel;
const HistoryJumpBanner({
required this.startedAt,
required this.target,
required this.loadedUntil,
required this.onCancel,
super.key,
});
double? get _progress {
final target = this.target;
final loadedUntil = this.loadedUntil;
if (target == null || loadedUntil == null) return null;
final total = startedAt.difference(target).inSeconds;
if (total <= 0) return null;
return (startedAt.difference(loadedUntil).inSeconds / total).clamp(0, 1);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final target = this.target;
final loadedUntil = this.loadedUntil;
return Material(
color: theme.colorScheme.surfaceContainer,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 8, 8),
child: Row(
children: [
const Icon(Icons.history, size: 20),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
target != null
? 'Springe zur Nachricht vom ${target.formatDate()}'
: 'Springe zur Nachricht',
style: theme.textTheme.bodyMedium,
),
Text(
loadedUntil != null
? 'Verlauf geladen bis ${loadedUntil.formatDate()}'
: 'Lade älteren Verlauf …',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
),
TextButton(onPressed: onCancel, child: const Text('Abbrechen')),
],
),
),
LinearProgressIndicator(value: _progress),
],
),
);
}
}