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]>
154 lines
4.4 KiB
Dart
154 lines
4.4 KiB
Dart
part of '../chat_view.dart';
|
||
|
||
class _DragOverlay extends StatefulWidget {
|
||
final PyramidTheme pt;
|
||
final bool active;
|
||
const _DragOverlay({required this.pt, required this.active});
|
||
|
||
@override
|
||
State<_DragOverlay> createState() => _DragOverlayState();
|
||
}
|
||
|
||
class _DragOverlayState extends State<_DragOverlay>
|
||
with SingleTickerProviderStateMixin {
|
||
// Sanftes Auf-und-ab des Icons, solange das Overlay aktiv ist.
|
||
late final AnimationController _bob = AnimationController(
|
||
vsync: this,
|
||
duration: const Duration(milliseconds: 900),
|
||
);
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
if (widget.active) _bob.repeat(reverse: true);
|
||
}
|
||
|
||
@override
|
||
void didUpdateWidget(_DragOverlay old) {
|
||
super.didUpdateWidget(old);
|
||
if (widget.active && !old.active) {
|
||
_bob.repeat(reverse: true);
|
||
} else if (!widget.active && old.active) {
|
||
_bob.stop();
|
||
}
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_bob.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final pt = widget.pt;
|
||
return Container(
|
||
color: pt.bg0.withAlpha(200),
|
||
child: Center(
|
||
child: AnimatedScale(
|
||
scale: widget.active ? 1.0 : 0.88,
|
||
duration: const Duration(milliseconds: 220),
|
||
curve: Curves.easeOutBack,
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 32),
|
||
decoration: BoxDecoration(
|
||
color: pt.bg2,
|
||
borderRadius: BorderRadius.circular(20),
|
||
border: Border.all(color: pt.accent, width: 2),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: pt.accent.withAlpha(60),
|
||
blurRadius: 32,
|
||
spreadRadius: 4,
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
AnimatedBuilder(
|
||
animation: _bob,
|
||
builder: (_, child) => Transform.translate(
|
||
offset: Offset(
|
||
0, -7 * Curves.easeInOut.transform(_bob.value)),
|
||
child: child,
|
||
),
|
||
child:
|
||
Icon(Icons.upload_file_rounded, size: 56, color: pt.accent),
|
||
),
|
||
const SizedBox(height: 16),
|
||
Text(
|
||
'Datei hier ablegen',
|
||
style: TextStyle(
|
||
color: pt.fg,
|
||
fontSize: 20,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
'Loslassen zum Hochladen',
|
||
style: TextStyle(color: pt.fgMuted, fontSize: 14),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
// Placeholder shown when no room is selected
|
||
class NoChatSelected extends StatelessWidget {
|
||
const NoChatSelected({super.key});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final pt = PyramidTheme.of(context);
|
||
return Container(
|
||
color: pt.bg1,
|
||
child: Center(
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Icon(Icons.forum_outlined, size: 64, color: pt.fgDim),
|
||
const SizedBox(height: 16),
|
||
Text(
|
||
'Wähle einen Chat aus',
|
||
style: TextStyle(color: pt.fgMuted, fontSize: 16),
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
'Choose from the sidebar on the left',
|
||
style: TextStyle(color: pt.fgDim, fontSize: 13),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 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,
|
||
),
|
||
);
|
||
}
|
||
}
|