|
|
|
|
@ -1,5 +1,6 @@
|
|
|
|
|
import 'dart:typed_data';
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
import 'package:video_player/video_player.dart';
|
|
|
|
|
import '../../core/service_locator.dart';
|
|
|
|
|
import '../../core/logger.dart';
|
|
|
|
|
import '../../data/recipes/recipe_service.dart';
|
|
|
|
|
@ -1053,6 +1054,73 @@ class _RecipeCard extends StatelessWidget {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget _buildVideoThumbnail(String videoUrl) {
|
|
|
|
|
return _VideoThumbnailPreview(videoUrl: videoUrl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Color _getRatingColor(int rating) {
|
|
|
|
|
if (rating >= 4) return Colors.green;
|
|
|
|
|
if (rating >= 2) return Colors.orange;
|
|
|
|
|
return Colors.red;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Widget that displays a video thumbnail preview with a few seconds of playback.
|
|
|
|
|
class _VideoThumbnailPreview extends StatefulWidget {
|
|
|
|
|
final String videoUrl;
|
|
|
|
|
|
|
|
|
|
const _VideoThumbnailPreview({
|
|
|
|
|
required this.videoUrl,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
State<_VideoThumbnailPreview> createState() => _VideoThumbnailPreviewState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _VideoThumbnailPreviewState extends State<_VideoThumbnailPreview> {
|
|
|
|
|
VideoPlayerController? _controller;
|
|
|
|
|
bool _isInitialized = false;
|
|
|
|
|
bool _hasError = false;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
_initializeVideo();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> _initializeVideo() async {
|
|
|
|
|
try {
|
|
|
|
|
_controller = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl));
|
|
|
|
|
await _controller!.initialize();
|
|
|
|
|
|
|
|
|
|
if (mounted) {
|
|
|
|
|
setState(() {
|
|
|
|
|
_isInitialized = true;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Play continuously in a loop
|
|
|
|
|
_controller!.setLooping(true);
|
|
|
|
|
_controller!.play();
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
Logger.warning('Failed to initialize video thumbnail: $e');
|
|
|
|
|
if (mounted) {
|
|
|
|
|
setState(() {
|
|
|
|
|
_hasError = true;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void dispose() {
|
|
|
|
|
_controller?.dispose();
|
|
|
|
|
super.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
if (_hasError || !_isInitialized || _controller == null) {
|
|
|
|
|
return Stack(
|
|
|
|
|
fit: StackFit.expand,
|
|
|
|
|
children: [
|
|
|
|
|
@ -1066,34 +1134,35 @@ class _RecipeCard extends StatelessWidget {
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Stack(
|
|
|
|
|
fit: StackFit.expand,
|
|
|
|
|
children: [
|
|
|
|
|
AspectRatio(
|
|
|
|
|
aspectRatio: _controller!.value.aspectRatio,
|
|
|
|
|
child: VideoPlayer(_controller!),
|
|
|
|
|
),
|
|
|
|
|
// Play icon overlay
|
|
|
|
|
Positioned(
|
|
|
|
|
bottom: 4,
|
|
|
|
|
left: 4,
|
|
|
|
|
top: 4,
|
|
|
|
|
right: 4,
|
|
|
|
|
child: Container(
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
|
|
|
|
padding: const EdgeInsets.all(4),
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
color: Colors.black.withValues(alpha: 0.7),
|
|
|
|
|
borderRadius: BorderRadius.circular(4),
|
|
|
|
|
color: Colors.black.withValues(alpha: 0.6),
|
|
|
|
|
shape: BoxShape.circle,
|
|
|
|
|
),
|
|
|
|
|
child: const Text(
|
|
|
|
|
'MP4',
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
child: const Icon(
|
|
|
|
|
Icons.play_circle_filled,
|
|
|
|
|
size: 24,
|
|
|
|
|
color: Colors.white,
|
|
|
|
|
fontSize: 10,
|
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
|
),
|
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Color _getRatingColor(int rating) {
|
|
|
|
|
if (rating >= 4) return Colors.green;
|
|
|
|
|
if (rating >= 2) return Colors.orange;
|
|
|
|
|
return Colors.red;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|