Files
youtube_personal/lib/widgets/video_result_card.dart
T

237 lines
6.5 KiB
Dart

import 'package:flutter/material.dart';
import '../models/video_item.dart';
class VideoResultCard extends StatelessWidget {
const VideoResultCard({
super.key,
required this.video,
required this.onTap,
this.trailing,
});
final VideoItem video;
final VoidCallback onTap;
final Widget? trailing;
@override
Widget build(BuildContext context) {
final isWide = MediaQuery.sizeOf(context).width >= 700;
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(14),
child: Padding(
padding: EdgeInsets.all(8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: isWide ? _buildWide(context) : _buildCompact(context)),
if (trailing != null) trailing!,
],
),
),
),
);
}
Widget _buildWide(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 300,
child: _Thumbnail(video: video),
),
SizedBox(width: 18),
Expanded(
child: Padding(
padding: EdgeInsets.only(top: 4, right: 12),
child: _VideoText(video: video),
),
),
],
);
}
Widget _buildCompact(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_Thumbnail(video: video),
SizedBox(height: 10),
Padding(
padding: EdgeInsets.symmetric(horizontal: 2),
child: _VideoText(video: video),
),
],
);
}
}
class _Thumbnail extends StatelessWidget {
const _Thumbnail({required this.video});
final VideoItem video;
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 16 / 9,
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Stack(
fit: StackFit.expand,
children: [
ColoredBox(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
child: Image.network(
video.thumbnailUrl,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Center(
child: Icon(
Icons.broken_image_outlined,
size: 42,
color: Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.62),
),
);
},
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) {
return child;
}
return Center(
child: CircularProgressIndicator(strokeWidth: 2),
);
},
),
),
if (video.durationLabel.isNotEmpty)
Positioned(
right: 7,
bottom: 7,
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.black87,
borderRadius: BorderRadius.circular(5),
),
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 6,
vertical: 3,
),
child: Text(
video.durationLabel,
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
),
),
],
),
),
);
}
}
class _VideoText extends StatelessWidget {
const _VideoText({required this.video});
final VideoItem video;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
video.title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
height: 1.25,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 7),
Row(
children: [
Icon(
Icons.account_circle_outlined,
size: 18,
color: Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.86),
),
SizedBox(width: 6),
Expanded(
child: Text(
video.author,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.86),
fontSize: 14,
),
),
),
],
),
if (video.viewCount != null ||
video.likeCount != null) ...[
SizedBox(height: 6),
Text(
[
if (video.viewCount != null)
'${_compact(video.viewCount!)} vues',
if (video.likeCount != null)
'${_compact(video.likeCount!)} likes',
].join(' • '),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Color(0x73FFFFFF),
fontSize: 12,
),
),
],
if (video.description.trim().isNotEmpty) ...[
SizedBox(height: 6),
Text(
video.description
.replaceAll(RegExp(r'\s+'), ' ')
.trim(),
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.62),
fontSize: 12,
height: 1.3,
),
),
],
],
);
}
String _compact(int value) {
if (value >= 1000000000) {
return '${(value / 1000000000).toStringAsFixed(value >= 10000000000 ? 0 : 1)} Md';
}
if (value >= 1000000) {
return '${(value / 1000000).toStringAsFixed(value >= 10000000 ? 0 : 1)} M';
}
if (value >= 1000) {
return '${(value / 1000).toStringAsFixed(value >= 10000 ? 0 : 1)} k';
}
return '$value';
}
}