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:
1 parent
fcfd21bb4d
commit
b9cb6a8af1
13 files changed
+926
-322
No files matched your search
@@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user