import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; // 비디오 패키지 임포트 class DashboardGrid extends StatefulWidget { final VoidCallback onCharacterTap; const DashboardGrid({super.key, required this.onCharacterTap}); @override State createState() => _DashboardGridState(); } class _DashboardGridState extends State { late VideoPlayerController _videoController; bool _isVideoInitialized = false; bool _hasVideoError = false; @override void initState() { super.initState(); _initializeWebVideo(); } void _initializeWebVideo() { const String videoAssetPath = 'assets/assets/videos/basic.mp4'; _videoController = VideoPlayerController.asset(videoAssetPath); _videoController.initialize().then((_) { if (!mounted) return; setState(() { _isVideoInitialized = true; }); _videoController.setLooping(true); // 🔄 무한 반복 재생 _videoController.setVolume(0.0); // 🔇 크롬 자동재생 정책 우회 _videoController.play(); // ▶️ 자동 재생 시작 }).catchError((error) { debugPrint("1차 웹 비디오 로딩 실패, 백업 경로 시도 중...: $error"); _videoController = VideoPlayerController.asset('assets/videos/basic.mp4'); _videoController.initialize().then((_) { if (!mounted) return; setState(() { _isVideoInitialized = true; }); _videoController.setLooping(true); _videoController.setVolume(0.0); _videoController.play(); }).catchError((retryError) { if (!mounted) return; setState(() { _hasVideoError = true; }); debugPrint("최종 비디오 초기화 에러: $retryError"); }); }); } @override void dispose() { _videoController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // 🤖 1. 좌측 영역: 푸미 캐릭터 비디오 화면 (✨ flex: 60 으로 수정) Expanded( flex: 60, child: GestureDetector( onTap: widget.onCharacterTap, child: _buildCharacterCard(), ), ), const SizedBox(width: 20), // 📊 2. 우측 영역: 센서 데이터 모음 (✨ flex: 40 으로 수정) Expanded( flex: 40, child: Column( children: [ Expanded( flex: 10, child: Row( children: [ Expanded(child: _buildSensorCard('온도', '24.5°C', Icons.thermostat, Colors.orangeAccent)), const SizedBox(width: 16), Expanded(child: _buildSensorCard('습도', '65%', Icons.water_drop, Colors.blueAccent)), ], ), ), const SizedBox(height: 16), Expanded( flex: 10, child: Row( children: [ Expanded(child: _buildSensorCard('이산화탄소', '420 ppm', Icons.cloud, Colors.greenAccent)), const SizedBox(width: 16), Expanded(child: _buildSensorCard('TVOC', '0.15 mg/m³', Icons.biotech, Colors.tealAccent)), ], ), ), const SizedBox(height: 16), Expanded( flex: 11, child: _buildDustCard(), ), ], ), ), ], ); } // 🤖 [좌측] 캐릭터 전용 카드 빌더 Widget _buildCharacterCard() { return Container( decoration: BoxDecoration( color: const Color(0xFF1F242C), borderRadius: BorderRadius.circular(16), border: Border.all(color: Colors.white10), ), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.start, children: [ _buildMiniInfo(Icons.eco_outlined, '학습된 식물: 51종'), const SizedBox(width: 20), _buildMiniInfo(Icons.trending_up, '예측 정확도: 98.7%'), ], ), // 🎬 중앙 로봇 에셋 비디오 플레이어 Expanded( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ if (_hasVideoError) const Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.video_library_outlined, size: 80, color: Colors.white24), SizedBox(height: 12), Text('비디오 에셋을 로드할 수 없습니다.', style: TextStyle(color: Colors.white38, fontSize: 13)), ], ) else if (_isVideoInitialized) SizedBox( width: 600, child: AspectRatio( aspectRatio: _videoController.value.aspectRatio, child: ClipRRect( borderRadius: BorderRadius.circular(16), child: VideoPlayer(_videoController), ), ), ) else const SizedBox( height: 280, child: Center(child: CircularProgressIndicator(color: Color(0xFFB4E49C))), ), const SizedBox(height: 16), // 안내 뱃지 문구 Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: const Color(0xFFB4E49C).withOpacity(0.06), borderRadius: BorderRadius.circular(20), border: Border.all(color: const Color(0xFFB4E49C).withOpacity(0.2)), ), child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.touch_app_outlined, size: 14, color: Color(0xFFB4E49C)), SizedBox(width: 6), Text( '여기를 클릭하면 AI 채팅창으로 이동합니다.', style: TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold), ), ], ), ), ], ), ), ), const Divider(color: Colors.white10, height: 24), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('푸미', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white)), const SizedBox(height: 4), Row( children: [ const Text('상태: ', style: TextStyle(color: Colors.white54, fontSize: 13)), Text('최상 - 모니터링 중', style: TextStyle(color: const Color(0xFFB4E49C), fontSize: 13, fontWeight: FontWeight.bold)), ], ), ], ), ], ), ], ), ); } // 📊 센서 카드 빌더 Widget _buildSensorCard(String title, String value, IconData icon, Color accentColor) { return Container( decoration: BoxDecoration( color: const Color(0xFF1F242C), borderRadius: BorderRadius.circular(16), border: Border.all(color: Colors.white10), ), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(title, style: const TextStyle(fontSize: 16, color: Colors.white70, fontWeight: FontWeight.w500)), Icon(icon, color: accentColor.withOpacity(0.8), size: 22), ], ), Text(value, style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.white)), Container( height: 4, width: double.infinity, decoration: BoxDecoration(color: Colors.white10, borderRadius: BorderRadius.circular(2)), child: FractionallySizedBox( alignment: Alignment.centerLeft, widthFactor: 0.65, child: Container(decoration: BoxDecoration(color: accentColor, borderRadius: BorderRadius.circular(2))), ), ) ], ), ); } // 💨 미세먼지 카드 빌더 Widget _buildDustCard() { return Container( width: double.infinity, decoration: BoxDecoration( color: const Color(0xFF1F242C), borderRadius: BorderRadius.circular(16), border: Border.all(color: Colors.white10), ), padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('미세먼지', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white)), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Row( children: [ Icon(Icons.air, color: Colors.white54, size: 36), SizedBox(width: 16), Text('15', style: TextStyle(fontSize: 52, fontWeight: FontWeight.bold, color: Colors.white)), SizedBox(width: 4), Text('µg/m³', style: TextStyle(fontSize: 14, color: Colors.white38)), ], ), Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: Colors.white.withOpacity(0.03), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white10), ), child: const Row( children: [ Icon(Icons.check_circle, color: Colors.greenAccent, size: 28), SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text('PM2.5 : 1.50', style: TextStyle(color: Colors.white70, fontSize: 13)), SizedBox(height: 2), Text('PM10 : 1.30', style: TextStyle(color: Colors.white70, fontSize: 13)), ], ) ], ), ) ], ), const SizedBox(height: 2), ], ), ); } Widget _buildMiniInfo(IconData icon, String text) { return Row( children: [ Icon(icon, size: 14, color: const Color(0xFFB4E49C)), const SizedBox(width: 6), Text(text, style: const TextStyle(color: Colors.white54, fontSize: 12)), ], ); } }