import 'package:flutter/material.dart'; import 'package:flutter_mjpeg/flutter_mjpeg.dart'; import '../utils/translations.dart'; class MultiCameraScreen extends StatefulWidget { final List> cameras; final String selectedLanguage; const MultiCameraScreen({ super.key, required this.cameras, required this.selectedLanguage, }); @override State createState() => _MultiCameraScreenState(); } class _MultiCameraScreenState extends State { int? _fullscreenIndex; // null = grid view, index = fullscreen camera String _t(String key) { return AppTranslations.translate(key, widget.selectedLanguage); } // Calculate grid columns based on camera count int get _crossAxisCount { final count = widget.cameras.length; if (count == 1) return 1; if (count <= 4) return 2; return 3; // 5-6 cameras } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text( _fullscreenIndex == null ? _t('Multi-Camera View') : "${_t('Camera')} ${widget.cameras[_fullscreenIndex!]['index']}", ), backgroundColor: Colors.grey[900], foregroundColor: Colors.white, leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { if (_fullscreenIndex != null) { setState(() { _fullscreenIndex = null; // Exit fullscreen first }); } else { Navigator.pop(context); // Exit screen } }, ), ), backgroundColor: Colors.black, body: _fullscreenIndex == null ? _buildGridView() : _buildFullscreenView(_fullscreenIndex!), ); } Widget _buildGridView() { return GridView.builder( padding: const EdgeInsets.all(8), itemCount: widget.cameras.length, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: _crossAxisCount, mainAxisSpacing: 8, crossAxisSpacing: 8, childAspectRatio: 4 / 3, // 4:3 aspect ratio for camera feeds ), itemBuilder: (context, index) { return _buildCameraCell(index); }, ); } Widget _buildCameraCell(int index) { final camera = widget.cameras[index]; final cameraIndex = camera['index'] as int; final cameraIp = camera['ip'] as String; final streamUrl = cameraIp.startsWith(RegExp(r"https?://")) ? cameraIp : "http://$cameraIp"; return GestureDetector( onTap: () { setState(() { _fullscreenIndex = index; // Enter fullscreen }); }, child: Container( decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey[700]!, width: 1), ), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Stack( children: [ // MJPEG Stream Mjpeg( stream: streamUrl, isLive: true, fit: BoxFit.contain, error: (context, error, stackTrace) { return _buildErrorWidget(cameraIndex); }, ), // Camera label overlay Positioned( top: 8, left: 8, child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.6), borderRadius: BorderRadius.circular(4), ), child: Text( "${_t('Camera')} $cameraIndex", style: const TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.w600, ), ), ), ), ], ), ), ), ); } Widget _buildFullscreenView(int index) { final camera = widget.cameras[index]; final cameraIndex = camera['index'] as int; final cameraIp = camera['ip'] as String; final streamUrl = cameraIp.startsWith(RegExp(r"https?://")) ? cameraIp : "http://$cameraIp"; return GestureDetector( onTap: () { setState(() { _fullscreenIndex = null; // Exit fullscreen, return to grid }); }, child: Container( color: Colors.black, child: Stack( children: [ Center( child: Mjpeg( stream: streamUrl, isLive: true, fit: BoxFit.contain, error: (context, error, stackTrace) { return _buildErrorWidget(cameraIndex); }, ), ), // Back indicator overlay Positioned( bottom: 16, left: 0, right: 0, child: Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.6), borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.touch_app, color: Colors.white, size: 16), const SizedBox(width: 8), Text( _t('Tap to return to grid'), style: const TextStyle(color: Colors.white, fontSize: 14), ), ], ), ), ), ), // Camera label Positioned( top: 16, left: 16, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.6), borderRadius: BorderRadius.circular(6), ), child: Text( "${_t('Camera')} $cameraIndex", style: const TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ), ], ), ), ); } Widget _buildErrorWidget(int cameraIndex) { return Container( color: Colors.black, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.videocam_off, color: Colors.red, size: 32), const SizedBox(height: 4), Flexible( child: Text( "${_t('Camera')} $cameraIndex\n${_t('Stream unavailable')}", style: const TextStyle( color: Colors.white70, fontSize: 11, ), textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, ), ), ], ), ); } }