import 'package:flutter/material.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:manager_api_new/api.dart'; import 'package:mymuseum_visitapp/Components/video_viewer.dart'; import 'package:mymuseum_visitapp/Helpers/translationHelper.dart'; import 'package:mymuseum_visitapp/app_context.dart'; import 'package:mymuseum_visitapp/constants.dart'; import 'package:provider/provider.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'package:youtube_player_iframe/youtube_player_iframe.dart' as iframe; enum _VideoSourceType { youtube, vimeo, direct } _VideoSourceType _detectSourceType(String url) { if (url.contains('youtube.com') || url.contains('youtu.be')) return _VideoSourceType.youtube; if (url.contains('vimeo.com')) return _VideoSourceType.vimeo; return _VideoSourceType.direct; } String? _extractVimeoId(String url) { final match = RegExp(r'vimeo\.com/(?:.*?/)?(\d+)').firstMatch(url); return match?.group(1); } class VideoPage extends StatefulWidget { final VideoDTO section; VideoPage({required this.section}); @override _VideoPage createState() => _VideoPage(); } class _VideoPage extends State { iframe.YoutubePlayerController? _youtubeController; WebViewController? _vimeoController; _VideoSourceType? _sourceType; bool isFullScreen = false; static const _browserUserAgent = 'Mozilla/5.0 (Linux; Android 10; Tablet) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; @override void initState() { super.initState(); final source = widget.section.source_; if (source == null || source.isEmpty) return; _sourceType = _detectSourceType(source); if (_sourceType == _VideoSourceType.youtube) { _youtubeController = iframe.YoutubePlayerController( params: const iframe.YoutubePlayerParams( mute: false, showControls: true, showFullscreenButton: true, loop: true, showVideoAnnotations: false, strictRelatedVideos: false, enableKeyboard: false, enableCaption: false, pointerEvents: iframe.PointerEvents.auto, userAgent: _browserUserAgent, ), ); _youtubeController!.loadVideo(source); _youtubeController!.listen((value) { if (mounted) { setState(() { isFullScreen = value.fullScreenOption.enabled; }); } }); } else if (_sourceType == _VideoSourceType.vimeo) { final vimeoId = _extractVimeoId(source); if (vimeoId != null) { _vimeoController = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setUserAgent(_browserUserAgent) ..loadRequest(Uri.parse('https://player.vimeo.com/video/$vimeoId')); } } } @override void dispose() { _youtubeController?.close(); super.dispose(); } Widget _buildPlayer() { final source = widget.section.source_; if (source == null || source.isEmpty) { return const Center( child: Text( "La vidéo ne peut pas être affichée, l'url est incorrecte", style: TextStyle(fontSize: kNoneInfoOrIncorrect), ), ); } switch (_sourceType) { case _VideoSourceType.youtube: return iframe.YoutubePlayer(controller: _youtubeController!); case _VideoSourceType.vimeo: if (_vimeoController == null) { return const Center( child: Text( "Impossible d'extraire l'identifiant Vimeo depuis l'URL", style: TextStyle(fontSize: kNoneInfoOrIncorrect), ), ); } return WebViewWidget(controller: _vimeoController!); case _VideoSourceType.direct: return VideoViewer(videoUrl: source, file: null); default: return const SizedBox(); } } @override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; final appContext = Provider.of(context); var title = TranslationHelper.get(widget.section.title, appContext.getContext()); String cleanedTitle = title.replaceAll('\n', ' ').replaceAll('
', ' '); return Stack( children: [ !isFullScreen ? Container( height: size.height * 0.28, decoration: BoxDecoration( boxShadow: const [ BoxShadow( color: kMainGrey, spreadRadius: 0.5, blurRadius: 5, offset: Offset(0, 1), ), ], gradient: const LinearGradient( begin: Alignment.centerRight, end: Alignment.centerLeft, colors: [kMainColor0, kMainColor1, kMainColor2], ), image: widget.section.imageSource != null ? DecorationImage( fit: BoxFit.cover, opacity: 0.65, image: NetworkImage(widget.section.imageSource!), ) : null, ), ) : const SizedBox(), Column( children: [ !isFullScreen ? SizedBox( height: size.height * 0.11, width: size.width, child: Stack( fit: StackFit.expand, children: [ Center( child: Padding( padding: const EdgeInsets.only(top: 22.0), child: SizedBox( width: size.width * 0.7, child: HtmlWidget( cleanedTitle, textStyle: const TextStyle(color: Colors.white, fontFamily: 'Roboto', fontSize: 20), customStylesBuilder: (element) { return {'text-align': 'center', 'font-family': "Roboto", '-webkit-line-clamp': "2"}; }, ), ), ), ), Positioned( top: 35, left: 10, child: SizedBox( width: 50, height: 50, child: InkWell( onTap: () => Navigator.of(context).pop(), child: Container( decoration: const BoxDecoration( color: kMainColor, shape: BoxShape.circle, ), child: const Icon(Icons.arrow_back, size: 23, color: Colors.white), ), ), ), ), ], ), ) : const SizedBox(), Expanded( child: Container( margin: const EdgeInsets.only(top: 0), decoration: const BoxDecoration( boxShadow: [ BoxShadow( color: kMainGrey, spreadRadius: 0.5, blurRadius: 2, offset: Offset(0, 1), ), ], color: kBackgroundColor, borderRadius: BorderRadius.only( topLeft: Radius.circular(30), topRight: Radius.circular(30), ), ), child: ClipRRect( borderRadius: !isFullScreen ? const BorderRadius.only( topLeft: Radius.circular(30), topRight: Radius.circular(30), ) : BorderRadius.zero, child: _buildPlayer(), ), ), ), ], ), if (isFullScreen) Positioned( top: 35, left: 10, child: SizedBox( width: 50, height: 50, child: InkWell( onTap: () { _youtubeController?.exitFullScreen(); Navigator.of(context).pop(); }, child: Container( decoration: const BoxDecoration( color: kMainColor, shape: BoxShape.circle, ), child: const Icon(Icons.arrow_back, size: 23, color: Colors.white), ), ), ), ), ], ); } }