video_preview.dart 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import 'dart:typed_data';
  2. import 'package:flutter/material.dart';
  3. import 'package:veloe_kemono_party_flutter/video_thumbnail_generator';
  4. import '../../main.dart';
  5. class VideoPreview extends StatefulWidget {
  6. final String videoUrl;
  7. final VoidCallback onTap;
  8. const VideoPreview({
  9. super.key,
  10. required this.videoUrl,
  11. required this.onTap,
  12. });
  13. @override
  14. State<VideoPreview> createState() => _VideoPreviewState();
  15. }
  16. class _VideoPreviewState extends State<VideoPreview> {
  17. Future<Uint8List?>? _thumbnailFuture;
  18. bool _isGenerating = false;
  19. bool _isCached = false;
  20. @override
  21. void initState() {
  22. super.initState();
  23. _checkCacheStatus();
  24. _loadThumbnail();
  25. }
  26. void _loadThumbnail() {
  27. setState(() => _isGenerating = true);
  28. _thumbnailFuture = VideoThumbnailGenerator.getFirstFrameThumbnail(widget.videoUrl)
  29. .whenComplete(() => setState(() => _isGenerating = false));
  30. }
  31. Future<void> _checkCacheStatus() async {
  32. final cachedFile = await videoCacheManager.getFileFromCache(widget.videoUrl);
  33. if (cachedFile != null && mounted) {
  34. setState(() => _isCached = true);
  35. }
  36. }
  37. @override
  38. Widget build(BuildContext context) {
  39. return GestureDetector(
  40. onTap: widget.onTap,
  41. child: FutureBuilder<Uint8List?>(
  42. future: _thumbnailFuture,
  43. builder: (context, snapshot) {
  44. return AspectRatio(
  45. aspectRatio: 16/9,
  46. child: Stack(
  47. fit: StackFit.expand,
  48. children: [
  49. // Thumbnail display
  50. if (snapshot.hasData)
  51. Image.memory(snapshot.data!, fit: BoxFit.cover)
  52. else if (_isGenerating)
  53. const Center(child: CircularProgressIndicator())
  54. else
  55. Container(color: Colors.black),
  56. // Play button overlay
  57. const Center(
  58. child: Icon(Icons.play_circle_filled,
  59. size: 64,
  60. color: Colors.white70,
  61. ),
  62. ),
  63. // Video indicator
  64. Positioned(
  65. bottom: 8,
  66. right: 8,
  67. child: Container(
  68. padding: const EdgeInsets.all(4),
  69. color: Colors.black54,
  70. child: const Text('VIDEO',
  71. style: TextStyle(color: Colors.white)),
  72. ),
  73. ),
  74. if (_isCached)
  75. Positioned(
  76. top: 8,
  77. right: 8,
  78. child: Container(
  79. padding: const EdgeInsets.all(4),
  80. decoration: const BoxDecoration(
  81. color: Colors.green,
  82. shape: BoxShape.circle,
  83. ),
  84. child: const Icon(Icons.download_done, size: 16, color: Colors.white),
  85. ),
  86. ),
  87. ],
  88. ),
  89. );
  90. },
  91. ),
  92. );
  93. }
  94. }