Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
import 'package:flutter/material.dart'; | |
import 'dart:convert'; | |
import '../theme/colors.dart'; | |
import '../models/video_result.dart'; | |
import './video_player/index.dart'; | |
class VideoCard extends StatelessWidget { | |
final VideoResult video; | |
const VideoCard({ | |
super.key, | |
required this.video, | |
}); | |
Widget _buildThumbnail() { | |
if (video.thumbnailUrl.isEmpty) { | |
return Container( | |
color: AiTubeColors.surfaceVariant, | |
child: const Center( | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
Icon( | |
Icons.movie_creation, | |
color: AiTubeColors.onSurfaceVariant, | |
size: 32, | |
), | |
SizedBox(height: 8), | |
Text( | |
'(TODO: thumbnails)', | |
style: TextStyle( | |
color: AiTubeColors.onSurfaceVariant, | |
fontSize: 12, | |
), | |
), | |
], | |
), | |
), | |
); | |
} | |
try { | |
// Handle image thumbnails | |
if (video.thumbnailUrl.startsWith('data:image')) { | |
final uri = Uri.parse(video.thumbnailUrl); | |
final base64Data = uri.data?.contentAsBytes(); | |
if (base64Data == null) { | |
debugPrint('Invalid image data in thumbnailUrl'); | |
throw Exception('Invalid image data'); | |
} | |
return Image.memory( | |
base64Data, | |
fit: BoxFit.cover, | |
errorBuilder: (context, error, stackTrace) { | |
debugPrint('Error loading image thumbnail: $error'); | |
return _buildErrorThumbnail(); | |
}, | |
); | |
} | |
// Handle video thumbnails | |
else if (video.thumbnailUrl.startsWith('data:video')) { | |
return NanoVideoPlayer( | |
video: video, | |
autoPlay: true, | |
muted: true, | |
loop: true, | |
borderRadius: 0, | |
showLoadingIndicator: true, | |
playbackSpeed: 0.7, | |
); | |
} | |
// Regular URL thumbnail | |
else if (video.thumbnailUrl.isNotEmpty) { | |
return Image.network( | |
video.thumbnailUrl, | |
fit: BoxFit.cover, | |
errorBuilder: (context, error, stackTrace) { | |
debugPrint('Error loading network thumbnail: $error'); | |
return _buildErrorThumbnail(); | |
}, | |
); | |
} else { | |
return _buildErrorThumbnail(); | |
} | |
} catch (e) { | |
debugPrint('Unexpected error in thumbnail rendering: $e'); | |
return _buildErrorThumbnail(); | |
} | |
} | |
Widget _buildErrorThumbnail() { | |
return Container( | |
color: AiTubeColors.surfaceVariant, | |
child: const Center( | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
Icon( | |
Icons.broken_image, | |
color: AiTubeColors.onSurfaceVariant, | |
size: 32, | |
), | |
SizedBox(height: 8), | |
Text( | |
'Preview unavailable', | |
style: TextStyle( | |
color: AiTubeColors.onSurfaceVariant, | |
fontSize: 12, | |
), | |
), | |
], | |
), | |
), | |
); | |
} | |
Widget build(BuildContext context) { | |
return Card( | |
clipBehavior: Clip.antiAlias, | |
margin: EdgeInsets.zero, | |
child: Column( | |
mainAxisSize: MainAxisSize.min, | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
AspectRatio( | |
aspectRatio: 16 / 9, | |
child: Stack( | |
fit: StackFit.expand, | |
children: [ | |
_buildThumbnail(), | |
/* | |
Will be used in the future release | |
Positioned( | |
right: 8, | |
top: 8, | |
child: Container( | |
padding: const EdgeInsets.symmetric( | |
horizontal: 8, | |
vertical: 4, | |
), | |
decoration: BoxDecoration( | |
color: Colors.black.withOpacity(0.7), | |
borderRadius: BorderRadius.circular(4), | |
), | |
child: const Row( | |
mainAxisSize: MainAxisSize.min, | |
children: [ | |
Text( | |
'LTX Video', | |
style: TextStyle( | |
color: AiTubeColors.onBackground, | |
fontSize: 12, | |
), | |
), | |
], | |
), | |
), | |
), | |
*/ | |
], | |
), | |
), | |
Container( | |
padding: const EdgeInsets.all(12), | |
color: AiTubeColors.surface, | |
child: Row( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
const CircleAvatar( | |
radius: 16, | |
backgroundColor: AiTubeColors.surfaceVariant, | |
child: Icon( | |
Icons.play_arrow, | |
color: AiTubeColors.onSurfaceVariant, | |
size: 20, | |
), | |
), | |
const SizedBox(width: 12), | |
Expanded( | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
mainAxisSize: MainAxisSize.min, | |
children: [ | |
Text( | |
video.title, | |
style: const TextStyle( | |
color: AiTubeColors.onBackground, | |
fontWeight: FontWeight.w500, | |
fontSize: 14, | |
), | |
maxLines: 2, | |
overflow: TextOverflow.ellipsis, | |
), | |
const SizedBox(height: 4), | |
SizedBox( | |
height: 36, // Approximately height for 3 lines of text with fontSize 12 | |
child: Text( | |
video.description, | |
style: const TextStyle( | |
color: AiTubeColors.onSurfaceVariant, | |
fontSize: 12, | |
), | |
maxLines: 3, | |
overflow: TextOverflow.ellipsis, | |
), | |
), | |
], | |
), | |
), | |
], | |
), | |
), | |
], | |
), | |
); | |
} | |
} |