feat(ui): untere Leiste (Chats/Server/Du) als Option, Server-Uebersicht, Du-Tab, Feinschliff

- Einstellung 'Navigation: Seitenmenue | Untere Leiste' (Handy, Standard
  bleibt Seitenmenue): Tabs Chats/Server/Du, Server-Uebersicht als Karten,
  Du-Tab mit Profil + Einstellungen, Zurueck-Taste ueber die Tabs
- CallDock als eigenes Widget (Anrufleiste auch ueber der Tab-Leiste)
- Sprachkanaele am Handy als Kachel mit 'wer ist drin'
- Suchfeld mit Lupe, 'Kanaele durchsuchen' im Server, Filter-Chips am PC
  umbrechend, '#' nur bei Server-Kanaelen, englische Reste entfernt
- Anrufansicht merkt sich Bubbles/Kacheln, Bubbles mit leichtem Glanz
- UI-Tour: Gruppenanruf (echter zweiter LiveKit-Teilnehmer), Bubbles mit
  5/7 Personen, untere Leiste; Version 0.1.42

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Bernd Steckmeister
2026-10-10 14:22:02 +02:00
co-authored by Claude Opus 5.5
parent de24053167
commit 391ebf0ffa
19 changed files with 1139 additions and 87 deletions
+25 -10
View File
@@ -15,6 +15,7 @@ class _Header extends ConsumerWidget {
final isRealSpace = spaceId != null && spaceId != 'dms' && spaceId != 'rooms';
final size = MediaQuery.sizeOf(context);
final isMobile = size.width < 900 || size.height < 500;
final bottomNav = ref.watch(bottomNavProvider);
final spaces = ref.watch(spacesProvider).valueOrNull ?? [];
Room? activeSpace;
@@ -162,7 +163,18 @@ class _Header extends ConsumerWidget {
Widget controlsRow = Row(
children: [
if (isMobile)
if (isMobile && bottomNav) ...[
if (isRealSpace)
PyrIconBtn(
icon: Icon(Icons.arrow_back_rounded,
color: hasBanner ? Colors.white : null),
tooltip: 'Alle Server',
onPressed: () =>
ref.read(activeSpaceIdProvider.notifier).state = null,
)
else
const SizedBox(width: 2),
] else if (isMobile)
PyrIconBtn(
icon: const Icon(Icons.menu_rounded),
tooltip: 'Spaces',
@@ -171,7 +183,7 @@ class _Header extends ConsumerWidget {
else ...[
PyrIconBtn(
icon: const Icon(Icons.menu_rounded),
tooltip: 'Toggle sidebar',
tooltip: 'Seitenleiste ein/aus',
onPressed: () =>
ref.read(railCollapsedProvider.notifier).update((s) => !s),
),
@@ -311,7 +323,7 @@ class _Header extends ConsumerWidget {
height: isRealSpace ? 56 : 52,
padding: const EdgeInsets.symmetric(horizontal: 14),
decoration: BoxDecoration(
color: isRealSpace ? pt.bg0 : pt.bg1,
color: isRealSpace && !isMobile ? pt.bg0 : pt.bg1,
border: Border(bottom: BorderSide(color: pt.border)),
),
child: controlsRow,
@@ -453,9 +465,13 @@ class _NotificationsButton extends ConsumerWidget {
class _FilterInput extends StatelessWidget {
final TextEditingController controller;
final PyramidTheme pt;
final String hint;
final ValueChanged<String> onChanged;
const _FilterInput(
{required this.controller, required this.pt, required this.onChanged});
{required this.controller,
required this.pt,
required this.hint,
required this.onChanged});
@override
Widget build(BuildContext context) {
@@ -464,18 +480,17 @@ class _FilterInput extends StatelessWidget {
child: Stack(
alignment: Alignment.centerLeft,
children: [
Padding(
padding: const EdgeInsets.only(left: 8),
child: Icon(Icons.search_rounded, size: 14, color: pt.fgDim),
),
TextField(
controller: controller,
onChanged: onChanged,
style: TextStyle(color: pt.fg, fontSize: 13),
decoration: InputDecoration(
hintText: 'Chats durchsuchen',
hintText: hint,
hintStyle: TextStyle(color: pt.fgDim, fontSize: 13),
contentPadding: const EdgeInsets.fromLTRB(28, 7, 10, 7),
prefixIcon: Icon(Icons.search_rounded, size: 16, color: pt.fgDim),
prefixIconConstraints:
const BoxConstraints(minWidth: 32, minHeight: 20),
contentPadding: const EdgeInsets.fromLTRB(0, 7, 10, 7),
filled: true,
fillColor: pt.bg2,
border: OutlineInputBorder(