import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_lucide/flutter_lucide.dart'; import 'package:gal/gal.dart'; import 'package:http/http.dart' as http; import 'package:path_provider/path_provider.dart'; import 'package:video_player/video_player.dart'; import '../../core/theme/app_colors.dart'; import '../../core/theme/app_spacing.dart'; import '../../core/theme/app_typography.dart'; import '../../features/gallery/models/gallery_task_item.dart'; import '../../shared/widgets/top_nav_bar.dart'; /// Video Generation Result screen - matches Pencil cFA4T class GenerationResultScreen extends StatefulWidget { const GenerationResultScreen({super.key, this.mediaItem}); final GalleryMediaItem? mediaItem; @override State createState() => _GenerationResultScreenState(); } class _GenerationResultScreenState extends State { VideoPlayerController? _videoController; bool _saving = false; bool _videoLoading = true; String? _videoLoadError; String? get _videoUrl => widget.mediaItem?.videoUrl; String? get _imageUrl => widget.mediaItem?.imageUrl; @override void initState() { super.initState(); if (_videoUrl != null) { _initVideoFromCache(); } } Future _initVideoFromCache() async { if (_videoUrl == null) return; try { final file = await DefaultCacheManager().getSingleFile(_videoUrl!); if (!mounted) return; final controller = VideoPlayerController.file(file); await controller.initialize(); if (!mounted) return; setState(() { _videoController = controller; _videoLoading = false; }); } catch (e) { if (mounted) { setState(() { _videoLoading = false; _videoLoadError = e.toString(); }); } } } @override void dispose() { _videoController?.dispose(); super.dispose(); } Future _saveToAlbum() async { if (widget.mediaItem == null) return; setState(() => _saving = true); try { final hasAccess = await Gal.hasAccess(toAlbum: true); if (!hasAccess) { final granted = await Gal.requestAccess(toAlbum: true); if (!granted) { throw Exception('Photo library access denied'); } } if (_videoUrl != null) { final tempDir = await getTemporaryDirectory(); final file = File( '${tempDir.path}/gallery_video_${DateTime.now().millisecondsSinceEpoch}.mp4'); final response = await http.get(Uri.parse(_videoUrl!)); if (response.statusCode != 200) { throw Exception('Failed to download video'); } await file.writeAsBytes(response.bodyBytes); await Gal.putVideo(file.path); await file.delete(); } else if (_imageUrl != null) { final tempDir = await getTemporaryDirectory(); final file = File( '${tempDir.path}/gallery_image_${DateTime.now().millisecondsSinceEpoch}.jpg'); final response = await http.get(Uri.parse(_imageUrl!)); if (response.statusCode != 200) { throw Exception('Failed to download image'); } await file.writeAsBytes(response.bodyBytes); await Gal.putImage(file.path); await file.delete(); } if (mounted) { setState(() => _saving = false); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Saved to photo library')), ); } } catch (e) { if (mounted) { setState(() => _saving = false); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Save failed: $e')), ); } } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.background, appBar: PreferredSize( preferredSize: const Size.fromHeight(56), child: TopNavBar( title: 'Video Ready', showBackButton: true, onBack: () => Navigator.of(context).pop(), ), ), body: widget.mediaItem == null ? Center( child: Text( 'No media', style: TextStyle(color: AppColors.textSecondary), ), ) : SingleChildScrollView( padding: const EdgeInsets.all(AppSpacing.screenPaddingLarge), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _MediaDisplay( videoUrl: _videoUrl, imageUrl: _imageUrl, videoController: _videoController, videoLoading: _videoUrl != null ? _videoLoading : false, videoLoadError: _videoLoadError, ), const SizedBox(height: AppSpacing.xxl), _DownloadButton( onDownload: _saving ? null : _saveToAlbum, saving: _saving, ) ], ), ), ); } } class _MediaDisplay extends StatelessWidget { const _MediaDisplay({ this.videoUrl, this.imageUrl, this.videoController, this.videoLoading = false, this.videoLoadError, }); final String? videoUrl; final String? imageUrl; final VideoPlayerController? videoController; final bool videoLoading; final String? videoLoadError; @override Widget build(BuildContext context) { return Container( height: 360, decoration: BoxDecoration( color: AppColors.textPrimary, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.border, width: 1), ), child: ClipRRect( borderRadius: BorderRadius.circular(16), child: videoUrl != null && videoController != null ? _VideoPlayer( controller: videoController!, ) : videoUrl != null && videoLoading ? Container( color: AppColors.textPrimary, alignment: Alignment.center, child: const CircularProgressIndicator( color: AppColors.surface, ), ) : videoUrl != null && videoLoadError != null ? Container( color: AppColors.textPrimary, alignment: Alignment.center, child: Text( 'Load failed', style: TextStyle(color: AppColors.surface), ), ) : imageUrl != null ? CachedNetworkImage( imageUrl: imageUrl!, fit: BoxFit.cover, placeholder: (_, __) => _Placeholder(), errorWidget: (_, __, ___) => _Placeholder(), ) : _Placeholder(), ), ); } } class _VideoPlayer extends StatefulWidget { const _VideoPlayer({required this.controller}); final VideoPlayerController controller; @override State<_VideoPlayer> createState() => _VideoPlayerState(); } class _VideoPlayerState extends State<_VideoPlayer> { @override void initState() { super.initState(); widget.controller.addListener(_listener); } @override void dispose() { widget.controller.removeListener(_listener); super.dispose(); } void _listener() { if (mounted) setState(() {}); } @override Widget build(BuildContext context) { if (!widget.controller.value.isInitialized) { return Container( color: AppColors.textPrimary, child: const Center( child: CircularProgressIndicator(color: AppColors.surface), ), ); } return Stack( fit: StackFit.expand, alignment: Alignment.center, children: [ FittedBox( fit: BoxFit.contain, child: SizedBox( width: widget.controller.value.size.width > 0 ? widget.controller.value.size.width : 16, height: widget.controller.value.size.height > 0 ? widget.controller.value.size.height : 9, child: VideoPlayer(widget.controller), ), ), Center( child: GestureDetector( onTap: () { if (widget.controller.value.isPlaying) { widget.controller.pause(); } else { widget.controller.play(); } }, child: Container( width: 64, height: 64, decoration: BoxDecoration( color: AppColors.surface.withValues(alpha: 0.8), shape: BoxShape.circle, ), child: Icon( widget.controller.value.isPlaying ? LucideIcons.pause : LucideIcons.play, size: 32, color: AppColors.textPrimary, ), ), ), ), ], ); } } class _Placeholder extends StatelessWidget { @override Widget build(BuildContext context) { return Container( color: AppColors.textPrimary, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( LucideIcons.play, size: 72, color: AppColors.surface.withValues(alpha: 0.5), ), const SizedBox(height: AppSpacing.lg), Text( 'Your video is ready', style: AppTypography.bodyRegular.copyWith( color: AppColors.surface.withValues(alpha: 0.6), ), ), ], ), ); } } class _DownloadButton extends StatelessWidget { const _DownloadButton({ required this.onDownload, this.saving = false, }); final VoidCallback? onDownload; final bool saving; @override Widget build(BuildContext context) { return GestureDetector( onTap: onDownload, child: Container( height: 52, decoration: BoxDecoration( color: saving ? AppColors.surfaceAlt : AppColors.primary, borderRadius: BorderRadius.circular(14), boxShadow: saving ? null : [ BoxShadow( color: AppColors.primaryShadow.withValues(alpha: 0.25), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ if (saving) const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: AppColors.textSecondary, ), ) else Icon(LucideIcons.download, size: 20, color: AppColors.surface), const SizedBox(width: AppSpacing.md), Text( saving ? 'Saving...' : 'Save to Album', style: AppTypography.bodyMedium.copyWith( color: AppColors.surface, ), ), ], ), ), ); } } class _ShareButton extends StatelessWidget { const _ShareButton({required this.onShare}); final VoidCallback onShare; @override Widget build(BuildContext context) { return GestureDetector( onTap: onShare, child: Container( height: 52, decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(14), border: Border.all(color: AppColors.border), boxShadow: [ BoxShadow( color: AppColors.shadowLight, blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(LucideIcons.share_2, size: 20, color: AppColors.primary), const SizedBox(width: AppSpacing.md), Text( 'Share', style: AppTypography.bodyMedium.copyWith( color: AppColors.textPrimary, ), ), ], ), ), ); } }