feat: schwebende Eingabeleiste - Chat laeuft darunter weiter (Milchglas), Flaeche faengt Beruehrungen ab
Bernds Wunsch 2026-10-07: Die Nachrichtenliste reicht jetzt bis ganz unten; Tipp-Anzeige und Eingabefeld schweben darueber. Um das Feld herum ist die Flaeche klar (Chat sichtbar), aber beruehrungsdicht - dort reagiert nur die Eingabe. Hinter dem Feld selbst Milchglas (Unschaerfe + 78 % Flaeche), Rahmen und Fokus-Ring aussen. Die Liste bekommt unten genau die gemessene Hoehe der Leiste als Abstand (MeasureSize; waechst mit Antwort-Leiste/mehrzeiligem Text), damit die neueste Nachricht ganz ueber die Leiste scrollt; der Nach-unten-Knopf sitzt darueber. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7063ebfceb
commit
223c9448f6
@@ -19,6 +19,10 @@ class _MessageList extends StatelessWidget {
|
||||
final String? highlightEventId;
|
||||
final bool highlightOn;
|
||||
|
||||
/// Höhe der darüber schwebenden Eingabeleiste: so viel Platz bleibt unten
|
||||
/// frei, damit die neueste Nachricht ganz über die Leiste scrollen kann.
|
||||
final double bottomInset;
|
||||
|
||||
const _MessageList({
|
||||
required this.timeline,
|
||||
required this.scrollCtrl,
|
||||
@@ -35,6 +39,7 @@ class _MessageList extends StatelessWidget {
|
||||
this.jumpKey,
|
||||
this.highlightEventId,
|
||||
this.highlightOn = false,
|
||||
this.bottomInset = 0,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -241,7 +246,7 @@ class _MessageList extends StatelessWidget {
|
||||
controller: scrollCtrl,
|
||||
reverse: true,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
padding: EdgeInsets.only(bottom: 8 + bottomInset),
|
||||
scrollCacheExtent: const ScrollCacheExtent.pixels(2500),
|
||||
itemCount: itemKeys.length,
|
||||
findChildIndexCallback: (Key key) {
|
||||
|
||||
@@ -129,3 +129,25 @@ class NoChatSelected extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Fläche der schwebenden Eingabeleiste: um das Eingabefeld herum völlig
|
||||
/// klar – der Chat ist dort normal zu sehen –, fängt aber alle Berührungen
|
||||
/// ab (ColoredBox ist auch transparent „opak“ für Treffer), damit dort nur
|
||||
/// die Eingabe reagiert und nicht der Chat dahinter. Die Unschärfe sitzt nur
|
||||
/// hinter dem Eingabefeld selbst (chat_composer.dart).
|
||||
class _FloatingComposerArea extends StatelessWidget {
|
||||
final PyramidTheme pt;
|
||||
final Widget child;
|
||||
const _FloatingComposerArea({required this.pt, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ColoredBox(
|
||||
color: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@ class _ChatViewState extends ConsumerState<ChatView> {
|
||||
|
||||
// Jump-to-message (from search): scroll to + briefly highlight a target event.
|
||||
final _jumpKey = GlobalKey();
|
||||
|
||||
// Höhe der schwebenden Eingabeleiste (gemessen, wächst mit Antwort-Leiste
|
||||
// oder mehrzeiligem Text).
|
||||
double _composerHeight = 72;
|
||||
String? _jumpEventId;
|
||||
String? _highlightEventId;
|
||||
bool _highlightOn = false;
|
||||
@@ -445,13 +449,14 @@ class _ChatViewState extends ConsumerState<ChatView> {
|
||||
jumpKey: _jumpKey,
|
||||
highlightEventId: _highlightEventId,
|
||||
highlightOn: _highlightOn,
|
||||
bottomInset: room != null ? _composerHeight : 0,
|
||||
);
|
||||
},
|
||||
),
|
||||
if (_showScrollDown)
|
||||
Positioned(
|
||||
right: 16,
|
||||
bottom: 12,
|
||||
bottom: (room != null ? _composerHeight : 0) + 12,
|
||||
child: AnimatedOpacity(
|
||||
opacity: _showScrollDown ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
@@ -481,16 +486,45 @@ class _ChatViewState extends ConsumerState<ChatView> {
|
||||
),
|
||||
),
|
||||
),
|
||||
// Schwebende Eingabeleiste (Bernds Wunsch 2026-10-07):
|
||||
// der Chat läuft optisch darunter weiter
|
||||
// (durchscheinend + Unschärfe), die ganze Fläche
|
||||
// fängt aber Berührungen ab – dort reagiert nur
|
||||
// die Eingabe, nicht der Chat dahinter.
|
||||
if (room != null)
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: MeasureSize(
|
||||
onChange: (size) {
|
||||
if (mounted &&
|
||||
(size.height - _composerHeight).abs() >
|
||||
0.5) {
|
||||
setState(
|
||||
() => _composerHeight = size.height);
|
||||
}
|
||||
},
|
||||
child: _FloatingComposerArea(
|
||||
pt: pt,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_TypingIndicator(room: room, pt: pt),
|
||||
ChatComposer(
|
||||
room: room,
|
||||
replyTo: _replyTo,
|
||||
onClearReply: () =>
|
||||
setState(() => _replyTo = null),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (room != null) _TypingIndicator(room: room, pt: pt),
|
||||
if (room != null)
|
||||
ChatComposer(
|
||||
room: room,
|
||||
replyTo: _replyTo,
|
||||
onClearReply: () => setState(() => _replyTo = null),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user