fix gestor de contenido, wip reproductor y diseño
This commit is contained in:
@@ -131,24 +131,42 @@ class VideosProvider extends ChangeNotifier {
|
||||
videosRows.add(
|
||||
PlutoRow(
|
||||
cells: {
|
||||
'id': PlutoCell(value: media.mediaFileId),
|
||||
'thumbnail':
|
||||
PlutoCell(value: media.fileUrl), // Para mostrar thumbnail
|
||||
'video': PlutoCell(value: media), // Objeto completo para renderers
|
||||
'thumbnail': PlutoCell(value: media.fileUrl),
|
||||
'title': PlutoCell(value: media.title ?? media.fileName),
|
||||
'description': PlutoCell(value: media.fileDescription ?? ''),
|
||||
'file_description': PlutoCell(value: media.fileDescription),
|
||||
'category':
|
||||
PlutoCell(value: _getCategoryName(media.mediaCategoryFk)),
|
||||
'reproducciones': PlutoCell(value: media.reproducciones),
|
||||
'duration': PlutoCell(value: media.seconds ?? 0),
|
||||
'size': PlutoCell(value: _formatFileSize(media.fileSizeBytes)),
|
||||
'created_at': PlutoCell(value: media.createdAt),
|
||||
'actions': PlutoCell(value: media.mediaFileId),
|
||||
'duration': PlutoCell(
|
||||
value: media.seconds != null
|
||||
? _formatDuration(media.seconds!)
|
||||
: '-'),
|
||||
'createdAt': PlutoCell(
|
||||
value: media.createdAt?.toString().split('.')[0] ?? '-'),
|
||||
'actions': PlutoCell(value: media),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Format duration in seconds to human readable
|
||||
String _formatDuration(int seconds) {
|
||||
final duration = Duration(seconds: seconds);
|
||||
final hours = duration.inHours;
|
||||
final minutes = duration.inMinutes.remainder(60);
|
||||
final secs = duration.inSeconds.remainder(60);
|
||||
|
||||
if (hours > 0) {
|
||||
return '${hours}h ${minutes}m';
|
||||
} else if (minutes > 0) {
|
||||
return '${minutes}m ${secs}s';
|
||||
} else {
|
||||
return '${secs}s';
|
||||
}
|
||||
}
|
||||
|
||||
/// Get category name by ID
|
||||
String _getCategoryName(int? categoryId) {
|
||||
if (categoryId == null) return 'Sin categoría';
|
||||
@@ -258,21 +276,24 @@ class VideosProvider extends ChangeNotifier {
|
||||
.from('energymedia')
|
||||
.getPublicUrl(videoStoragePath!);
|
||||
|
||||
// 3. Upload poster if exists
|
||||
int? posterFileId;
|
||||
// 3. Upload poster if exists (solo storage, no DB)
|
||||
String? posterUrlUploaded;
|
||||
if (webPosterBytes != null && posterName != null) {
|
||||
posterFileId = await _uploadPoster();
|
||||
posterUrlUploaded = await _uploadPoster();
|
||||
}
|
||||
|
||||
// 4. Create media_files record
|
||||
// 4. Create media_files record (UN SOLO REGISTRO con poster en metadata_json)
|
||||
final metadataJson = {
|
||||
'uploaded_at': DateTime.now().toIso8601String(),
|
||||
'reproducciones': 0,
|
||||
'original_file_name': videoName,
|
||||
'duration_seconds': durationSeconds,
|
||||
'file_size_bytes': webVideoBytes!.length, // Peso del video
|
||||
if (posterUrlUploaded != null) 'poster_url': posterUrlUploaded,
|
||||
if (posterUrlUploaded != null) 'poster_file_name': posterName,
|
||||
};
|
||||
|
||||
final response = await supabaseML.from('media_files').insert({
|
||||
await supabaseML.from('media_files').insert({
|
||||
'file_name': fileName,
|
||||
'title': title,
|
||||
'file_description': description,
|
||||
@@ -288,16 +309,7 @@ class VideosProvider extends ChangeNotifier {
|
||||
'seconds': durationSeconds,
|
||||
'is_public_file': true,
|
||||
'uploaded_by_user_id': currentUser?.id,
|
||||
}).select();
|
||||
|
||||
// 5. Create poster relationship if exists
|
||||
if (posterFileId != null && response.isNotEmpty) {
|
||||
final mediaFileId = response[0]['media_file_id'];
|
||||
await supabaseML.from('media_posters').insert({
|
||||
'media_file_id': mediaFileId,
|
||||
'poster_file_id': posterFileId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up
|
||||
_clearUploadState();
|
||||
@@ -317,8 +329,9 @@ class VideosProvider extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Upload poster image (internal helper)
|
||||
Future<int?> _uploadPoster() async {
|
||||
/// Upload poster image to storage only (NO database record)
|
||||
/// Returns the public URL of the uploaded poster
|
||||
Future<String?> _uploadPoster() async {
|
||||
if (webPosterBytes == null || posterName == null) return null;
|
||||
|
||||
try {
|
||||
@@ -326,6 +339,7 @@ class VideosProvider extends ChangeNotifier {
|
||||
final fileName = '${timestamp}_$posterName';
|
||||
posterStoragePath = 'imagenes/$fileName';
|
||||
|
||||
// Solo subir al storage, NO crear registro en media_files
|
||||
await supabaseML.storage.from('energymedia').uploadBinary(
|
||||
posterStoragePath!,
|
||||
webPosterBytes!,
|
||||
@@ -335,26 +349,12 @@ class VideosProvider extends ChangeNotifier {
|
||||
),
|
||||
);
|
||||
|
||||
// Obtener URL pública del poster
|
||||
posterUrl = supabaseML.storage
|
||||
.from('energymedia')
|
||||
.getPublicUrl(posterStoragePath!);
|
||||
|
||||
// Create media_files record for poster
|
||||
final response = await supabaseML.from('media_files').insert({
|
||||
'file_name': fileName,
|
||||
'title': 'Poster',
|
||||
'file_type': 'image',
|
||||
'mime_type': _getMimeType(posterFileExtension),
|
||||
'file_extension': posterFileExtension,
|
||||
'file_size_bytes': webPosterBytes!.length,
|
||||
'file_url': posterUrl,
|
||||
'storage_path': posterStoragePath,
|
||||
'organization_fk': organizationId,
|
||||
'is_public_file': true,
|
||||
'uploaded_by_user_id': currentUser?.id,
|
||||
}).select();
|
||||
|
||||
return response[0]['media_file_id'] as int;
|
||||
return posterUrl; // Retornar solo la URL, no el ID
|
||||
} catch (e) {
|
||||
print('Error en _uploadPoster: $e');
|
||||
return null;
|
||||
@@ -500,23 +500,30 @@ class VideosProvider extends ChangeNotifier {
|
||||
.single();
|
||||
|
||||
final storagePath = response['storage_path'] as String?;
|
||||
final metadataJson = response['metadata_json'] as Map<String, dynamic>?;
|
||||
|
||||
// Delete from storage if path exists
|
||||
// Delete video from storage if path exists
|
||||
if (storagePath != null) {
|
||||
await supabaseML.storage.from('energymedia').remove([storagePath]);
|
||||
}
|
||||
|
||||
// Delete associated posters
|
||||
final posters = await supabaseML
|
||||
.from('media_posters')
|
||||
.select('poster_file_id')
|
||||
.eq('media_file_id', mediaFileId);
|
||||
// Delete poster from storage if exists in metadata_json
|
||||
if (metadataJson != null && metadataJson['poster_url'] != null) {
|
||||
final posterUrl = metadataJson['poster_url'] as String;
|
||||
// Extraer el path del storage desde la URL
|
||||
// URL format: https://xxx.supabase.co/storage/v1/object/public/energymedia/imagenes/filename.png
|
||||
final uri = Uri.parse(posterUrl);
|
||||
final pathSegments = uri.pathSegments;
|
||||
|
||||
for (var poster in posters) {
|
||||
await _deletePosterFile(poster['poster_file_id']);
|
||||
// Encontrar el índice después de 'energymedia' y construir el path
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete database record (cascade will delete posters relationship)
|
||||
// Delete database record
|
||||
await supabaseML
|
||||
.from('media_files')
|
||||
.delete()
|
||||
@@ -537,30 +544,6 @@ class VideosProvider extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete poster file (internal helper)
|
||||
Future<void> _deletePosterFile(int posterFileId) async {
|
||||
try {
|
||||
final response = await supabaseML
|
||||
.from('media_files')
|
||||
.select('storage_path')
|
||||
.eq('media_file_id', posterFileId)
|
||||
.single();
|
||||
|
||||
final storagePath = response['storage_path'] as String?;
|
||||
|
||||
if (storagePath != null) {
|
||||
await supabaseML.storage.from('energymedia').remove([storagePath]);
|
||||
}
|
||||
|
||||
await supabaseML
|
||||
.from('media_files')
|
||||
.delete()
|
||||
.eq('media_file_id', posterFileId);
|
||||
} catch (e) {
|
||||
print('Error en _deletePosterFile: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// ========== ANALYTICS METHODS ==========
|
||||
|
||||
/// Increment view count
|
||||
@@ -659,17 +642,20 @@ class VideosProvider extends ChangeNotifier {
|
||||
videosRows.add(
|
||||
PlutoRow(
|
||||
cells: {
|
||||
'id': PlutoCell(value: media.mediaFileId),
|
||||
'video': PlutoCell(value: media),
|
||||
'thumbnail': PlutoCell(value: media.fileUrl),
|
||||
'title': PlutoCell(value: media.title ?? media.fileName),
|
||||
'description': PlutoCell(value: media.fileDescription ?? ''),
|
||||
'fileName': PlutoCell(value: media.fileName),
|
||||
'category':
|
||||
PlutoCell(value: _getCategoryName(media.mediaCategoryFk)),
|
||||
'reproducciones': PlutoCell(value: media.reproducciones),
|
||||
'duration': PlutoCell(value: media.seconds ?? 0),
|
||||
'size': PlutoCell(value: _formatFileSize(media.fileSizeBytes)),
|
||||
'created_at': PlutoCell(value: media.createdAt),
|
||||
'actions': PlutoCell(value: media.mediaFileId),
|
||||
'duration': PlutoCell(
|
||||
value: media.seconds != null
|
||||
? _formatDuration(media.seconds!)
|
||||
: '-'),
|
||||
'createdAt': PlutoCell(
|
||||
value: media.createdAt?.toString().split('.')[0] ?? '-'),
|
||||
'actions': PlutoCell(value: media),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user