// lib/widgets/viewer/fullscreen_cast_viewer_fast.dart // // Viewer FULLSCREEN per FAST backend. // - Nessun seek // - Nessun play/pause // - Nessun volume // - Solo next/prev/stop // - Swipe sincronizzato con backend FAST import 'dart:io'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/widgets/cast/cast_controller.dart'; class FullscreenCastViewerFast extends StatefulWidget { final List entries; final int startIndex; const FullscreenCastViewerFast({ super.key, required this.entries, required this.startIndex, }); @override State createState() => _FullscreenCastViewerFastState(); } class _FullscreenCastViewerFastState extends State { late final PageController _pageController; late int _currentIndex; bool _isUpdatingFromCast = false; bool _controlsVisible = true; @override void initState() { super.initState(); _currentIndex = widget.startIndex.clamp(0, widget.entries.length - 1); _pageController = PageController(initialPage: _currentIndex); // Sync FAST → UI CastController.instance.currentIndexNotifier.addListener(_onCastIndexChanged); } @override void dispose() { CastController.instance.currentIndexNotifier.removeListener(_onCastIndexChanged); _pageController.dispose(); super.dispose(); } // ------------------------------------------------------------ // SYNC CAST → UI // ------------------------------------------------------------ void _onCastIndexChanged() { final newIndex = CastController.instance.currentIndexNotifier.value; if (newIndex == _currentIndex) return; _isUpdatingFromCast = true; _currentIndex = newIndex; if (_pageController.hasClients) { _pageController.jumpToPage(newIndex); } if (mounted) setState(() {}); Future.delayed(const Duration(milliseconds: 100), () { _isUpdatingFromCast = false; }); } // ------------------------------------------------------------ // SYNC UI → CAST // ------------------------------------------------------------ void _onPageChanged(int index) { if (_isUpdatingFromCast) return; if (index > _currentIndex) { CastController.instance.nextManual(); } else if (index < _currentIndex) { CastController.instance.prevManual(); } setState(() => _currentIndex = index); } // ------------------------------------------------------------ // RENDER ENTRY (locale) // ------------------------------------------------------------ Widget _buildEntry(AvesEntry entry) { final isVideo = (entry.durationMillis ?? 0) > 0; final path = entry.path; if (path == null || !File(path).existsSync()) { return const Center( child: Icon(Icons.broken_image, size: 64, color: Colors.white54), ); } if (isVideo) { return Center(child: VideoWidget.file(File(path))); } return InteractiveViewer( child: Center( child: Image.file(File(path), fit: BoxFit.contain), ), ); } // ------------------------------------------------------------ // TOP BAR // ------------------------------------------------------------ Widget _buildTopBar(BuildContext context) { return SafeArea( child: Container( color: Colors.black54, padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Row( children: [ IconButton( icon: const Icon(Icons.close, color: Colors.white), onPressed: () => Navigator.of(context).maybePop(), ), Expanded( child: Text( '${_currentIndex + 1}/${widget.entries.length}', style: const TextStyle(color: Colors.white), textAlign: TextAlign.center, ), ), IconButton( icon: const Icon(Icons.stop, color: Colors.white), onPressed: () async { await CastController.instance.stopCast(); if (mounted) Navigator.of(context).maybePop(); }, ), ], ), ), ); } // ------------------------------------------------------------ // BOTTOM CONTROLS (prev / next / stop) // ------------------------------------------------------------ Widget _buildBottomControls() { final controller = CastController.instance; return SafeArea( top: false, child: Container( color: Colors.black54, padding: const EdgeInsets.fromLTRB(12, 8, 12, 12), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ // PREV IconButton( iconSize: 40, icon: const Icon(Icons.skip_previous, color: Colors.white), onPressed: () => controller.prevManual(), ), const SizedBox(width: 20), // STOP IconButton( iconSize: 40, icon: const Icon(Icons.stop, color: Colors.white), onPressed: () async { await controller.stopCast(); if (mounted) Navigator.of(context).maybePop(); }, ), const SizedBox(width: 20), // NEXT IconButton( iconSize: 40, icon: const Icon(Icons.skip_next, color: Colors.white), onPressed: () => controller.nextManual(), ), ], ), ), ); } // ------------------------------------------------------------ // BUILD // ------------------------------------------------------------ @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, body: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => setState(() => _controlsVisible = !_controlsVisible), child: Stack( children: [ PageView.builder( controller: _pageController, itemCount: widget.entries.length, onPageChanged: _onPageChanged, itemBuilder: (context, i) => _buildEntry(widget.entries[i]), ), if (_controlsVisible) ...[ Align(alignment: Alignment.topCenter, child: _buildTopBar(context)), Align(alignment: Alignment.bottomCenter, child: _buildBottomControls()), ], ], ), ), ); } } // ------------------------------------------------------------ // VIDEO WIDGET (solo locale) // ------------------------------------------------------------ class VideoWidget extends StatefulWidget { final File file; const VideoWidget.file(this.file, {super.key}); @override State createState() => _VideoWidgetState(); } class _VideoWidgetState extends State { VideoPlayerController? controller; @override void initState() { super.initState(); controller = VideoPlayerController.file(widget.file) ..initialize().then((_) { if (mounted) setState(() {}); }); } @override void dispose() { controller?.dispose(); super.dispose(); } @override Widget build(BuildContext context) { if (controller == null || !controller!.value.isInitialized) { return const Center(child: CircularProgressIndicator()); } return AspectRatio( aspectRatio: controller!.value.aspectRatio, child: VideoPlayer(controller!), ); } }