diff --git a/lib/pages/login_page/login_page.dart b/lib/pages/login_page/login_page.dart index 98a5ee2..d204e62 100644 --- a/lib/pages/login_page/login_page.dart +++ b/lib/pages/login_page/login_page.dart @@ -232,7 +232,7 @@ class _LoginPageState extends State with TickerProviderStateMixin { flex: 1, child: Container( decoration: BoxDecoration( - color: const Color(0xFF0F172A), + color: const Color.fromARGB(255, 25, 26, 28), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), @@ -278,13 +278,11 @@ class _LoginPageState extends State with TickerProviderStateMixin { begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ - Color(0xFF4EC9F5), // Cyan principal - Color(0xFFFFB733), // Yellow - Color(0xFF6B2F8A), // Purple - Color(0xFFFF7A3D), // Orange - Color(0xFF4EC9F5), // Cyan de vuelta + Color(0xFF42BCEE), + Color(0xFF5865B5), + Color(0xFF653093) ], - stops: [0.0, 0.25, 0.5, 0.75, 1.0], + stops: [0.25, 0.5, 1.0], ), ), child: Stack( diff --git a/lib/pages/login_page/widgets/login_form.dart b/lib/pages/login_page/widgets/login_form.dart index cdc8de5..532688e 100644 --- a/lib/pages/login_page/widgets/login_form.dart +++ b/lib/pages/login_page/widgets/login_form.dart @@ -250,7 +250,7 @@ class _LoginFormState extends State with TickerProviderStateMixin { focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide( - color: Color(0xFF10B981), + color: Color(0xFF4EC9F5), width: 2, ), ), diff --git a/lib/pages/videos/gestor_videos_page.dart b/lib/pages/videos/gestor_videos_page.dart index afe628b..8958687 100644 --- a/lib/pages/videos/gestor_videos_page.dart +++ b/lib/pages/videos/gestor_videos_page.dart @@ -364,6 +364,7 @@ class _GestorVideosPageState extends State { : (video.fileUrl != null && video.fileUrl!.isNotEmpty) ? VideoThumbnailWidget( videoUrl: video.fileUrl!, + height: 100, fit: BoxFit.cover, ) : _buildThumbnailPlaceholder(), diff --git a/lib/pages/videos/videos_layout.dart b/lib/pages/videos/videos_layout.dart index a394147..de1ee0c 100644 --- a/lib/pages/videos/videos_layout.dart +++ b/lib/pages/videos/videos_layout.dart @@ -206,7 +206,7 @@ class _VideosLayoutState extends State { colors: [ Color(0xFF42BCEE), Color(0xFF5865B5), - Color(0xFF653093), + Color(0xFF653093) ], stops: const [0.0, 0.5, 1.0], ), diff --git a/lib/pages/videos/widgets/gestor_videos_widgets/edit_video_dialog.dart b/lib/pages/videos/widgets/gestor_videos_widgets/edit_video_dialog.dart index edb624b..368748d 100644 --- a/lib/pages/videos/widgets/gestor_videos_widgets/edit_video_dialog.dart +++ b/lib/pages/videos/widgets/gestor_videos_widgets/edit_video_dialog.dart @@ -42,6 +42,7 @@ class _EditVideoDialogState extends State { late TextEditingController tagsController; Uint8List? newPosterBytes; String? newPosterFileName; + bool _deletePoster = false; VideoPlayerController? _videoPlayerController; ChewieController? _chewieController; bool _isVideoLoading = false; @@ -162,8 +163,12 @@ class _EditVideoDialogState extends State { ); } - // Actualizar portada si se seleccionó una nueva - if (newPosterBytes != null && newPosterFileName != null) { + // Eliminar portada si se solicitó + if (_deletePoster) { + await widget.provider.deletePoster(widget.video.mediaFileId); + } + // Actualizar portada si se seleccionó una nueva (solo si no se eliminó) + else if (newPosterBytes != null && newPosterFileName != null) { await widget.provider.updateVideoPoster( widget.video.mediaFileId, newPosterBytes!, @@ -437,10 +442,100 @@ class _EditVideoDialogState extends State { } Widget _buildPosterSection() { + final hasPoster = widget.video.posterUrl != null && !_deletePoster; + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (widget.video.posterUrl != null) + if (_deletePoster) + Container( + height: 140, + width: double.infinity, + decoration: BoxDecoration( + color: AppTheme.of(context).tertiaryBackground, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: const Color(0xFFFF2D2D).withOpacity(0.3), + width: 2, + ), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.delete_outline, + size: 48, + color: const Color(0xFFFF2D2D).withOpacity(0.7), + ), + const Gap(8), + Text( + 'Portada marcada para eliminar', + style: AppTheme.of(context).bodyText2.override( + fontFamily: 'Poppins', + color: const Color(0xFFFF2D2D), + fontSize: 12, + ), + ), + ], + ), + ), + ) + else if (newPosterBytes != null) + // Mostrar preview de nueva portada seleccionada + Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Image.memory( + newPosterBytes!, + height: 140, + width: double.infinity, + fit: BoxFit.cover, + ), + ), + Positioned( + top: 8, + right: 8, + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: AppTheme.of(context).success, + borderRadius: BorderRadius.circular(6), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.fiber_new, + size: 14, + color: Colors.white, + ), + const Gap(4), + Text( + 'Nueva', + style: AppTheme.of(context).bodyText2.override( + fontFamily: 'Poppins', + color: Colors.white, + fontSize: 11, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ], + ) + else if (hasPoster) ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.network( @@ -458,30 +553,89 @@ class _EditVideoDialogState extends State { color: AppTheme.of(context).tertiaryBackground, borderRadius: BorderRadius.circular(12), ), - child: Icon( - Icons.image, - size: 48, - color: AppTheme.of(context).tertiaryText, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.video_library, + size: 48, + color: AppTheme.of(context).tertiaryText, + ), + const Gap(8), + Text( + 'Sin portada', + style: AppTheme.of(context).bodyText2.override( + fontFamily: 'Poppins', + color: AppTheme.of(context).tertiaryText, + fontSize: 12, + ), + ), + ], ), ), const Gap(12), - ElevatedButton.icon( - onPressed: _selectPoster, - icon: const Icon(Icons.image, size: 18), - label: Text( - newPosterBytes != null ? 'Portada Seleccionada' : 'Cambiar Portada', - ), - style: ElevatedButton.styleFrom( - backgroundColor: newPosterBytes != null - ? AppTheme.of(context).success - : AppTheme.of(context).primaryColor, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: _deletePoster ? null : _selectPoster, + icon: const Icon(Icons.image, size: 18), + label: Text( + newPosterBytes != null + ? 'Portada Seleccionada' + : 'Cambiar Portada', + ), + style: ElevatedButton.styleFrom( + backgroundColor: newPosterBytes != null + ? AppTheme.of(context).success + : AppTheme.of(context).primaryColor, + foregroundColor: Colors.white, + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + elevation: 0, + disabledBackgroundColor: + AppTheme.of(context).tertiaryText.withOpacity(0.3), + ), + ), ), - elevation: 0, - ), + if (hasPoster || _deletePoster) ...[ + const Gap(8), + ElevatedButton.icon( + onPressed: () { + setState(() { + _deletePoster = !_deletePoster; + if (_deletePoster) { + // Si se marca para eliminar, limpiar nueva portada seleccionada + newPosterBytes = null; + newPosterFileName = null; + } + }); + }, + icon: Icon( + _deletePoster ? Icons.undo : Icons.delete_outline, + size: 18, + ), + label: Text( + _deletePoster ? 'Deshacer' : 'Eliminar', + ), + style: ElevatedButton.styleFrom( + backgroundColor: _deletePoster + ? AppTheme.of(context).warning + : const Color(0xFFFF2D2D), + foregroundColor: Colors.white, + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + elevation: 0, + ), + ), + ], + ], ), if (newPosterBytes != null) ...[ const Gap(8), diff --git a/lib/providers/videos_provider.dart b/lib/providers/videos_provider.dart index 96d9cd7..44cd1db 100644 --- a/lib/providers/videos_provider.dart +++ b/lib/providers/videos_provider.dart @@ -580,6 +580,57 @@ class VideosProvider extends ChangeNotifier { } } + /// Delete video poster only (not the video itself) + Future deletePoster(int mediaFileId) async { + try { + isLoading = true; + notifyListeners(); + + // Get current metadata + final response = await supabaseML + .from('media_files') + .select('metadata_json') + .eq('media_file_id', mediaFileId) + .eq('organization_fk', organizationId) + .single(); + + final metadata = response['metadata_json'] as Map? ?? {}; + final posterUrl = metadata['poster_url'] as String?; + + // Delete poster from storage if exists + if (posterUrl != null && posterUrl.isNotEmpty) { + try { + final uri = Uri.parse(posterUrl); + final pathSegments = uri.pathSegments; + final bucketIndex = pathSegments.indexOf('energymedia'); + if (bucketIndex != -1 && bucketIndex < pathSegments.length - 1) { + final posterPath = pathSegments.sublist(bucketIndex + 1).join('/'); + await supabaseML.storage.from('energymedia').remove([posterPath]); + } + } catch (e) { + print('Error eliminando poster del storage: $e'); + } + } + + // Remove poster references from metadata + metadata.remove('poster_url'); + metadata.remove('poster_file_name'); + + // Update metadata + await updateVideoMetadata(mediaFileId, metadata); + + isLoading = false; + notifyListeners(); + return true; + } catch (e) { + errorMessage = 'Error eliminando portada: $e'; + isLoading = false; + notifyListeners(); + print('Error en deletePoster: $e'); + return false; + } + } + // ========== DELETE METHODS ========== /// Delete video and its storage files