import 'dart:typed_data'; import 'dart:html' as html; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; import 'package:chewie/chewie.dart'; import 'package:get_thumbnail_video/index.dart'; import 'package:get_thumbnail_video/video_thumbnail.dart'; import 'package:energy_media/models/media/media_models.dart'; import 'package:energy_media/providers/videos_provider.dart'; import 'package:energy_media/theme/theme.dart'; import 'package:energy_media/widgets/premium_button.dart'; import 'package:gap/gap.dart'; class PremiumUploadDialog extends StatefulWidget { final VideosProvider provider; final VoidCallback onSuccess; const PremiumUploadDialog({ Key? key, required this.provider, required this.onSuccess, }) : super(key: key); @override State createState() => _PremiumUploadDialogState(); } class _PremiumUploadDialogState extends State { final titleController = TextEditingController(); final descriptionController = TextEditingController(); final tagsController = TextEditingController(); Uint8List? selectedVideo; String? videoFileName; Uint8List? selectedPoster; String? posterFileName; VideoPlayerController? _videoController; ChewieController? _chewieController; bool isUploading = false; bool _isVideoLoading = false; String? _videoBlobUrl; int? _videoDurationSeconds; // Duración capturada del video @override void dispose() { titleController.dispose(); descriptionController.dispose(); tagsController.dispose(); _chewieController?.dispose(); _videoController?.dispose(); // Limpiar blob URL if (_videoBlobUrl != null) { html.Url.revokeObjectUrl(_videoBlobUrl!); } super.dispose(); } Future _selectVideo() async { final result = await widget.provider.selectVideo(); if (result) { setState(() { selectedVideo = widget.provider.webVideoBytes; videoFileName = widget.provider.videoName; titleController.text = widget.provider.tituloController.text; }); // Crear video player para preview en web await _initializeVideoPlayer(); } } Future _initializeVideoPlayer() async { if (selectedVideo == null) return; setState(() => _isVideoLoading = true); try { // Limpiar blob URL anterior si existe if (_videoBlobUrl != null) { html.Url.revokeObjectUrl(_videoBlobUrl!); } // Crear Blob desde bytes final blob = html.Blob([selectedVideo!]); _videoBlobUrl = html.Url.createObjectUrlFromBlob(blob); // Inicializar video player _videoController = VideoPlayerController.network(_videoBlobUrl!); await _videoController!.initialize(); _chewieController = ChewieController( videoPlayerController: _videoController!, autoPlay: false, looping: false, showControls: true, aspectRatio: _videoController!.value.aspectRatio, materialProgressColors: ChewieProgressColors( playedColor: const Color(0xFF4EC9F5), handleColor: const Color(0xFFFFB733), backgroundColor: Colors.grey.shade800, bufferedColor: Colors.grey.shade600, ), placeholder: Container( color: Colors.black, child: const Center( child: CircularProgressIndicator( color: Color(0xFF4EC9F5), ), ), ), ); // Capturar duración del video _videoDurationSeconds = _videoController!.value.duration.inSeconds; debugPrint( '🕒 Duración del video capturada: $_videoDurationSeconds segundos'); setState(() => _isVideoLoading = false); } catch (e) { setState(() => _isVideoLoading = false); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Error al cargar preview: $e'), backgroundColor: const Color(0xFFFF2D2D), ), ); } } } Future _selectPoster() async { final result = await widget.provider.selectPoster(); if (result) { setState(() { selectedPoster = widget.provider.webPosterBytes; posterFileName = widget.provider.posterName; }); } } /// Genera un thumbnail automáticamente desde el video si no se seleccionó poster Future _generateThumbnailFromVideo() async { if (_videoBlobUrl == null || selectedVideo == null) return; try { // Generar thumbnail desde el video final thumbnailBytes = await VideoThumbnail.thumbnailData( video: _videoBlobUrl!, imageFormat: ImageFormat.JPEG, maxWidth: 1280, // Alta calidad para poster quality: 85, ); if (thumbnailBytes != null && thumbnailBytes.isNotEmpty) { // Actualizar el provider con el thumbnail generado (sin mostrar en UI) widget.provider.webPosterBytes = thumbnailBytes; widget.provider.posterName = 'thumbnail_${videoFileName ?? 'video'}.jpg'; widget.provider.posterFileExtension = '.jpg'; // NO actualizar selectedPoster ni posterFileName para que no se muestre en UI // El thumbnail está disponible en el provider para subir automáticamente debugPrint('✅ Thumbnail generado automáticamente (oculto en UI)'); } } catch (e) { debugPrint('⚠️ Error generando thumbnail automático: $e'); // No es crítico, el video se subirá sin poster } } Future _uploadVideo() async { if (titleController.text.isEmpty || selectedVideo == null || videoFileName == null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('Por favor completa los campos requeridos'), backgroundColor: const Color(0xFFFF2D2D), behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), ); return; } setState(() => isUploading = true); // Si no hay poster seleccionado, generar thumbnail automáticamente desde el video if (selectedPoster == null && _videoBlobUrl != null) { await _generateThumbnailFromVideo(); } // Procesar tags: separar por comas o espacios List? tags; if (tagsController.text.isNotEmpty) { tags = tagsController.text .split(RegExp(r'[,\s]+')) // Separar por comas o espacios .map((tag) => tag.trim()) .where((tag) => tag.isNotEmpty) .toList(); } final success = await widget.provider.uploadVideo( title: titleController.text, description: descriptionController.text.isEmpty ? null : descriptionController.text, tags: tags, durationSeconds: _videoDurationSeconds, ); if (!mounted) return; setState(() => isUploading = false); Navigator.pop(context); if (success) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Row( children: [ Icon(Icons.check_circle, color: Colors.white), Gap(12), Text('Video subido exitosamente'), ], ), backgroundColor: Colors.green, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), ); widget.onSuccess(); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Row( children: [ Icon(Icons.error, color: Colors.white), Gap(12), Text('Error al subir el video'), ], ), backgroundColor: const Color(0xFFFF2D2D), behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), ); } } @override Widget build(BuildContext context) { final isMobile = MediaQuery.of(context).size.width <= 800; return Dialog( backgroundColor: Colors.transparent, child: Container( width: isMobile ? double.infinity : 900, constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.9, ), decoration: BoxDecoration( color: AppTheme.of(context).secondaryBackground, borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: AppTheme.of(context).primaryColor.withOpacity(0.2), blurRadius: 40, offset: const Offset(0, 10), ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ _buildHeader(), Flexible( child: SingleChildScrollView( padding: const EdgeInsets.all(32), child: isMobile ? _buildMobileContent() : _buildDesktopContent(), ), ), _buildActions(), ], ), ), ); } Widget _buildHeader() { return Container( padding: const EdgeInsets.all(24), decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Color(0xFF4EC9F5), Color(0xFFFFB733), ], ), borderRadius: BorderRadius.only( topLeft: Radius.circular(24), topRight: Radius.circular(24), ), ), child: Row( children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), child: const Icon( Icons.cloud_upload, color: Color(0xFF0B0B0D), size: 28, ), ), const Gap(16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Subir Nuevo Video', style: AppTheme.of(context).title2.override( fontFamily: 'Poppins', color: const Color(0xFF0B0B0D), fontWeight: FontWeight.bold, fontSize: 22, ), ), const Gap(4), Text( 'Comparte tu contenido con el mundo', style: AppTheme.of(context).bodyText2.override( fontFamily: 'Poppins', color: const Color(0xFF0B0B0D).withOpacity(0.7), fontSize: 13, ), ), ], ), ), IconButton( onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close, color: Color(0xFF0B0B0D)), tooltip: 'Cerrar', ), ], ), ); } Widget _buildDesktopContent() { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(flex: 3, child: _buildFormFields()), const Gap(24), Expanded(flex: 2, child: _buildPreviewSection()), ], ); } Widget _buildMobileContent() { return Column( children: [ _buildFormFields(), const Gap(24), _buildPreviewSection(), ], ); } Widget _buildFormFields() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildLabel('Título del Video *'), const Gap(8), _buildTextField( controller: titleController, hintText: 'Ej: Tutorial de energía solar', prefixIcon: Icons.title, ), const Gap(20), _buildLabel('Descripción'), const Gap(8), _buildTextField( controller: descriptionController, hintText: 'Describe el contenido del video...', prefixIcon: Icons.description, maxLines: 4, ), const Gap(20), _buildLabel('Etiquetas (Tags)'), const Gap(4), Text( 'Separa las etiquetas con comas o espacios', style: AppTheme.of(context).bodyText2.override( fontFamily: 'Poppins', color: AppTheme.of(context).tertiaryText, fontSize: 12, ), ), const Gap(8), _buildTextField( controller: tagsController, hintText: 'Ej: deportes, fútbol, entrenamiento', prefixIcon: Icons.label, ), ], ); } Widget _buildPreviewSection() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildLabel('Vista Previa'), const Gap(12), _buildVideoSelector(), const Gap(16), _buildPosterSelector(), ], ); } Widget _buildLabel(String text) { return Text( text, style: AppTheme.of(context).bodyText1.override( fontFamily: 'Poppins', color: AppTheme.of(context).primaryText, fontWeight: FontWeight.w600, fontSize: 14, ), ); } Widget _buildTextField({ required TextEditingController controller, required String hintText, required IconData prefixIcon, int maxLines = 1, }) { return Container( decoration: BoxDecoration( color: AppTheme.of(context).tertiaryBackground, borderRadius: BorderRadius.circular(12), border: Border.all( color: AppTheme.of(context).primaryColor.withOpacity(0.1), ), ), child: TextField( controller: controller, maxLines: maxLines, style: AppTheme.of(context).bodyText1.override( fontFamily: 'Poppins', color: AppTheme.of(context).primaryText, ), decoration: InputDecoration( hintText: hintText, hintStyle: AppTheme.of(context).bodyText1.override( fontFamily: 'Poppins', color: AppTheme.of(context).tertiaryText, ), prefixIcon: Icon( prefixIcon, color: AppTheme.of(context).primaryColor, ), border: InputBorder.none, contentPadding: const EdgeInsets.all(16), ), ), ); } Widget _buildVideoSelector() { return GestureDetector( onTap: _selectVideo, child: Container( height: 200, decoration: BoxDecoration( color: AppTheme.of(context).tertiaryBackground, borderRadius: BorderRadius.circular(16), border: Border.all( color: videoFileName != null ? Colors.green.withOpacity(0.5) : AppTheme.of(context).primaryColor.withOpacity(0.3), width: 2, strokeAlign: BorderSide.strokeAlignInside, ), ), child: selectedVideo != null ? _buildVideoPreview() : _buildUploadPlaceholder( icon: Icons.video_file, title: 'Seleccionar Video', subtitle: 'Click para elegir archivo', ), ), ); } Widget _buildPosterSelector() { return GestureDetector( onTap: _selectPoster, child: Container( height: 150, decoration: BoxDecoration( color: AppTheme.of(context).tertiaryBackground, borderRadius: BorderRadius.circular(16), border: Border.all( color: posterFileName != null ? Colors.green.withOpacity(0.5) : AppTheme.of(context).primaryColor.withOpacity(0.3), width: 2, ), ), child: selectedPoster != null ? _buildPosterPreview() : _buildUploadPlaceholder( icon: Icons.image, title: 'Miniatura (Opcional)', subtitle: 'Click para elegir imagen', ), ), ); } Widget _buildVideoPreview() { if (_isVideoLoading) { return Container( color: Colors.black, child: const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator( color: Color(0xFF4EC9F5), ), Gap(12), Text( 'Cargando preview...', style: TextStyle( color: Colors.white, fontFamily: 'Poppins', ), ), ], ), ), ); } if (_chewieController != null && _videoController != null) { return Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(14), child: Container( color: Colors.black, child: Chewie( controller: _chewieController!, ), ), ), Positioned( top: 12, right: 12, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Colors.green, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.3), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.check_circle, size: 16, color: Colors.white), Gap(4), Text( 'Listo para subir', style: TextStyle( color: Colors.white, fontSize: 12, fontFamily: 'Poppins', fontWeight: FontWeight.w600, ), ), ], ), ), ), ], ); } return Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(14), child: Container( color: Colors.black, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.play_circle_outline, size: 64, color: Colors.white.withOpacity(0.8), ), const Gap(12), Text( videoFileName ?? 'Video seleccionado', style: const TextStyle( color: Colors.white, fontFamily: 'Poppins', fontWeight: FontWeight.w500, ), textAlign: TextAlign.center, ), ], ), ), ), ), Positioned( top: 12, right: 12, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Colors.green, borderRadius: BorderRadius.circular(20), ), child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.check_circle, size: 16, color: Colors.white), Gap(4), Text( 'Cargado', style: TextStyle( color: Colors.white, fontSize: 12, fontFamily: 'Poppins', fontWeight: FontWeight.w600, ), ), ], ), ), ), ], ); } Widget _buildPosterPreview() { return Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(14), child: Image.memory( selectedPoster!, width: double.infinity, height: double.infinity, fit: BoxFit.cover, ), ), Positioned( top: 12, right: 12, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Colors.green, borderRadius: BorderRadius.circular(20), ), child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.check_circle, size: 16, color: Colors.white), Gap(4), Text( 'Cargado', style: TextStyle( color: Colors.white, fontSize: 12, fontFamily: 'Poppins', fontWeight: FontWeight.w600, ), ), ], ), ), ), ], ); } Widget _buildUploadPlaceholder({ required IconData icon, required String title, required String subtitle, }) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppTheme.of(context).primaryColor.withOpacity(0.1), shape: BoxShape.circle, ), child: Icon( icon, size: 40, color: AppTheme.of(context).primaryColor, ), ), const Gap(12), Text( title, style: AppTheme.of(context).bodyText1.override( fontFamily: 'Poppins', color: AppTheme.of(context).primaryText, fontWeight: FontWeight.w600, ), ), const Gap(4), Text( subtitle, style: AppTheme.of(context).bodyText2.override( fontFamily: 'Poppins', color: AppTheme.of(context).tertiaryText, fontSize: 12, ), ), ], ), ); } Widget _buildActions() { return Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: AppTheme.of(context).tertiaryBackground.withOpacity(0.5), borderRadius: const BorderRadius.only( bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ PremiumButton( text: 'Cancelar', isOutlined: true, onPressed: () => Navigator.pop(context), width: 120, ), const Gap(12), PremiumButton( text: 'Subir Video', icon: Icons.cloud_upload, onPressed: _uploadVideo, isLoading: isUploading, width: 160, ), ], ), ); } }