part of '../voice_channel.dart'; class _UnifiedCallView extends StatefulWidget { final List<_ParticipantData> participants; final bool isLiveKit; final String displayName; final bool isConnecting; final VoidCallback onHangUp; final VoidCallback onMinimize; final VoidCallback onToggleMic; final VoidCallback onToggleDeafen; final VoidCallback onToggleCam; final VoidCallback onToggleScreen; final bool isMuted; final bool isDeafened; final bool isCameraOff; final bool isScreenSharing; final Function(Offset)? onQualityMenu; final PyramidTheme pt; const _UnifiedCallView({ required this.participants, required this.isLiveKit, required this.displayName, required this.isConnecting, required this.onHangUp, required this.onMinimize, required this.onToggleMic, required this.onToggleDeafen, required this.onToggleCam, required this.onToggleScreen, required this.isMuted, required this.isDeafened, required this.isCameraOff, required this.isScreenSharing, this.onQualityMenu, required this.pt, }); @override State<_UnifiedCallView> createState() => _UnifiedCallViewState(); } class _UnifiedCallViewState extends State<_UnifiedCallView> { String? _heroId; bool _isGridView = false; bool _stripCollapsed = false; @override void initState() { super.initState(); _autoSelectHero(); } @override void didUpdateWidget(_UnifiedCallView oldWidget) { super.didUpdateWidget(oldWidget); _autoSelectHero(); } void _autoSelectHero() { if (_isGridView) return; final screenshare = widget.participants.firstWhereOrNull((p) => p.isScreenShare); if (screenshare != null && (_heroId == null || !widget.participants.any((p) => p.id == _heroId && p.hasVideo))) { _heroId = screenshare.id; } else if (_heroId == null && widget.participants.isNotEmpty) { final firstVideo = widget.participants.firstWhereOrNull((p) => p.hasVideo); if (firstVideo != null) _heroId = firstVideo.id; } } List<_ParticipantData> _sorted(List<_ParticipantData> all) { int rank(_ParticipantData p) { if (p.isScreenShare) return 0; if (p.hasVideo) return 1; if (p.isSpeaking) return 2; return 3; } return [...all]..sort((a, b) => rank(a).compareTo(rank(b))); } @override Widget build(BuildContext context) { final pt = widget.pt; final all = _sorted(widget.participants); final hero = all.firstWhereOrNull((p) => p.id == _heroId) ?? (all.isNotEmpty ? all.first : null); if (widget.isConnecting) { return Scaffold( backgroundColor: pt.bg1, body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const PyramidLoader(size: 80), const SizedBox(height: 20), Text('Verbinde…', style: TextStyle(color: pt.fgMuted, fontSize: 14)), const SizedBox(height: 16), TextButton.icon( style: TextButton.styleFrom(foregroundColor: pt.danger), onPressed: widget.onHangUp, icon: const Icon(Icons.call_end_rounded, size: 16), label: const Text('Abbrechen'), ), ], ), ), ); } return Scaffold( backgroundColor: pt.bg1, body: Column( children: [ _CallHeader( displayName: widget.displayName, isGridView: _isGridView, onToggleView: () => setState(() => _isGridView = !_isGridView), onMinimize: widget.onMinimize, pt: pt, ), Expanded( child: AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: _isGridView || hero == null ? _ParticipantGrid(participants: all, pt: pt, onSelect: (p) => setState(() { _heroId = p.id; _isGridView = false; })) : _HeroLayout( hero: hero, others: all.where((p) => p.id != hero.id).toList(), stripCollapsed: _stripCollapsed, onToggleStrip: () => setState(() => _stripCollapsed = !_stripCollapsed), onSetStripCollapsed: (v) => setState(() => _stripCollapsed = v), onSelectOther: (p) => setState(() => _heroId = p.id), onQualityMenu: widget.onQualityMenu, pt: pt, ), ), ), _CallControls( isMuted: widget.isMuted, isDeafened: widget.isDeafened, isCameraOff: widget.isCameraOff, isScreenSharing: widget.isScreenSharing, onToggleMic: widget.onToggleMic, onToggleDeafen: widget.onToggleDeafen, onToggleCam: widget.onToggleCam, onToggleScreen: widget.onToggleScreen, onHangUp: widget.onHangUp, pt: pt, ), ], ), ); } } class _CallHeader extends StatelessWidget { final String displayName; final bool isGridView; final VoidCallback onToggleView; final VoidCallback onMinimize; final PyramidTheme pt; const _CallHeader({required this.displayName, required this.isGridView, required this.onToggleView, required this.onMinimize, required this.pt}); @override Widget build(BuildContext context) { return Container( height: 52, padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration(border: Border(bottom: BorderSide(color: pt.border))), child: Row( children: [ Icon(Icons.headphones_rounded, size: 16, color: pt.fgMuted), const SizedBox(width: 8), Text(displayName, style: TextStyle(color: pt.fg, fontSize: 15, fontWeight: FontWeight.w600)), const SizedBox(width: 8), _LiveBadge(pt: pt), const Spacer(), PyrIconBtn( icon: Icon(isGridView ? Icons.grid_view_rounded : Icons.featured_video_rounded, color: isGridView ? pt.accent : pt.fg), tooltip: isGridView ? 'Hero mode' : 'Grid mode', onPressed: onToggleView, ), const SizedBox(width: 4), PyrIconBtn( icon: Icon(Icons.close_fullscreen_rounded, color: pt.fg), tooltip: 'Minimieren', onPressed: onMinimize, ), ], ), ); } } class _LiveBadge extends StatelessWidget { final PyramidTheme pt; const _LiveBadge({required this.pt}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration(borderRadius: BorderRadius.circular(999), color: pt.online.withAlpha(30)), child: Row(children: [ Container(width: 6, height: 6, decoration: BoxDecoration(color: pt.online, shape: BoxShape.circle)), const SizedBox(width: 4), Text('Live', style: TextStyle(color: pt.online, fontSize: 11, fontWeight: FontWeight.w600)), ]), ); } }