aves_mio100/lib/widgets/viewer/fullscreen_cast_viewer_fast.dart
2026-07-06 14:52:45 +02:00

271 lines
7.5 KiB
Dart

// 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<AvesEntry> entries;
final int startIndex;
const FullscreenCastViewerFast({
super.key,
required this.entries,
required this.startIndex,
});
@override
State<FullscreenCastViewerFast> createState() =>
_FullscreenCastViewerFastState();
}
class _FullscreenCastViewerFastState extends State<FullscreenCastViewerFast> {
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<VideoWidget> createState() => _VideoWidgetState();
}
class _VideoWidgetState extends State<VideoWidget> {
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!),
);
}
}