import 'dart:io'; import 'package:better_player_plus/better_player_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:go_router/go_router.dart'; import 'package:news_app/constant/config.dart'; import 'package:news_app/constant/size_res.dart'; import 'package:news_app/extension/base.dart'; import 'package:news_app/model/video_new_model.dart'; import 'package:news_app/ui/video/comment_page.dart'; import 'package:news_app/ui/video/video_recommend_list_page.dart'; import 'package:news_app/util/device_util.dart'; import 'package:news_app/util/share_util.dart'; import 'package:news_app/widget/my_txt.dart'; import '../../constant/color_res.dart'; import '../../provider/video_detail_provider.dart'; import '../../util/theme_util.dart'; import '../../widget/auth_gesture_detector.dart'; import '../../widget/load_image.dart'; /// @author: bo.zeng /// @email: cnhbwds@gmail.com /// @date: 2025 2025/4/9 16:00 /// @description: class VideoParam { final String id; final String? videoUrl; VideoParam({required this.id, this.videoUrl}); } class VideoDetailPage extends ConsumerStatefulWidget { final VideoParam param; const VideoDetailPage({super.key, required this.param}); @override ConsumerState createState() => _VideoDetailPageState(); } final videoDetailProvider = NotifierProvider( () => VideoDetailProvider(), ); class _VideoDetailPageState extends ConsumerState with WidgetsBindingObserver { BetterPlayerController? _betterPlayerController; ProviderSubscription? subscription; bool _isWeChatInstalled = false; Future _checkWeChatInstallation() async { if (Platform.isAndroid) { final installed = await isWeChatInstalledOnlyAndroid(); setState(() => _isWeChatInstalled = installed); } else if (Platform.isIOS) { final installed = await fluwx.isWeChatInstalled; setState(() => _isWeChatInstalled = installed); } } Future shareAction(VideoNewModel data) async { showModalBottomSheet( context: context, builder: (context) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SizedBox(height: 10.h), Container( width: double.infinity, height: 80.h, decoration: BoxDecoration( borderRadius: BorderRadius.only( topLeft: Radius.circular(10.r), topRight: Radius.circular(10.r), ), color: Colors.white, ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ if (_isWeChatInstalled) GestureDetector( onTap: () { Navigator.pop(context); shareWeiXinUrl( title: data.shareDesc ?? "", url: data.shareUrl ?? "", ); }, child: Container( width: 100.w, height: 80.h, child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ LoadAssetImage( 'share_wxhy', width: 40.w, height: 40.h, ), SizedBox(height: 10.h), myTxt( text: "微信好友", color: Colors.black, fontSize: 12.sp, fontWeight: FontWeight.bold, ), ], ), ), ), if (_isWeChatInstalled) GestureDetector( onTap: () { Navigator.pop(context); shareWeiXinPYUrl( title: data.shareDesc ?? "", url: data.shareUrl ?? "", ); }, child: Container( width: 100.w, height: 80.h, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ LoadAssetImage( 'share_pyq', width: 40.w, height: 40.h, ), SizedBox(height: 10.h), myTxt( text: "朋友圈", color: Colors.black, fontSize: 12.sp, fontWeight: FontWeight.bold, ), ], ), ), ), GestureDetector( onTap: () { Navigator.pop(context); shareUrl( title: data.shareDesc ?? "", url: data.shareUrl ?? "", ); }, child: Container( width: 100.w, height: 80.h, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ LoadAssetImage( 'share_xtfx', width: 40.w, height: 40.h, ), SizedBox(height: 10.h), myTxt( text: "系统分享", color: Colors.black, fontSize: 12.sp, fontWeight: FontWeight.bold, ), ], ), ), ), ], ), ), ], ), ), ); } @override void dispose() { _betterPlayerController?.dispose(); subscription?.close(); super.dispose(); } @override void deactivate() { super.deactivate(); _betterPlayerController?.pause(); } @override void initState() { super.initState(); setImmersiveStatusBar(); _checkWeChatInstallation(); ref.read(videoDetailProvider.notifier).fetchVideoDetail(widget.param.id); if (widget.param.videoUrl?.isNotEmpty == true) { _initPlayer(widget.param.videoUrl); } else { subscription = ref.listenManual(videoDetailProvider, (pre, next) { if (pre?.url != next.url && next.url?.isNotEmpty == true) { _initPlayer(next.url); } }); } // 监听 App 生命周期变化 WidgetsBinding.instance.addObserver(this); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); if (_betterPlayerController == null) return; if (state == AppLifecycleState.inactive || state == AppLifecycleState.paused) { // App进入后台或锁屏,暂停播放 _betterPlayerController?.pause(); } else if (state == AppLifecycleState.resumed) { // App回到前台(是否继续播放可根据需求选择)如果希望回到前台自动播放,就取消注释 // _betterPlayerController?.play(); } } void _initPlayer(String? url) { if (url == null || url.isEmpty) { return; } _betterPlayerController?.dispose(); // dispose 旧的 final dataSource = BetterPlayerDataSource( BetterPlayerDataSourceType.network, url, videoFormat: BetterPlayerVideoFormat.other, // 避免格式识别失败 ); _betterPlayerController = BetterPlayerController( BetterPlayerConfiguration( errorBuilder: (context, errorMessage) { return Center( child: myTxt(text: "视频加载失败", color: Colors.white, fontSize: 14.sp), ); }, autoPlay: true, aspectRatio: 16 / 9, fit: BoxFit.fitWidth, controlsConfiguration: const BetterPlayerControlsConfiguration( showControls: false, ), ), betterPlayerDataSource: dataSource, ); } @override Widget build(BuildContext context) { final video = ref.watch(videoDetailProvider); return Material( color: Colors.black, child: Stack( fit: StackFit.expand, alignment: Alignment.centerRight, children: [ if (_betterPlayerController != null && video.url?.isNotEmpty == true) ColoredBox( color: Colors.black, child: AspectRatio( aspectRatio: 16 / 9, child: BetterPlayer(controller: _betterPlayerController!), ), ) else Center( child: myTxt( text: "视频加载中...", color: Colors.white, fontSize: 14.sp, ), ), Positioned( left: 20.w, top: 54.h, child: GestureDetector( onTap: () => context.pop(), child: Icon(Icons.arrow_back_ios, color: Colors.white), ), ), Positioned( right: 20.h, bottom: 150.h, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox(height: 100.h), AuthGestureDetector( onTap: () { ref .read(videoDetailProvider.notifier) .likeVideo(video.isLiked ?? false); ref .read(recommendListProvider.notifier) .fetchVideoLike( videoId: widget.param.id, current: video.isLiked ?? false, ); }, child: Icon( Icons.favorite, color: video.isLiked == true ? Colors.red : Colors.white, size: 25.sp, ), ), myTxt( text: video.likeCount.toSafeString, color: Colors.white, fontSize: 12.sp, ), SizedBox(height: 15.h), AuthGestureDetector( onTap: () { String? contentId = video.contentId; String? title = video.title; //1.活动中的视频 2.视频Tab进来的普通视频 _showBottomCommentDialog( context, contentId, title, CommentType.video, ); }, child: Icon(Icons.message, color: Colors.white, size: 25.sp), ), myTxt( text: video.commentCount.toSafeString, color: Colors.white, fontSize: 12.sp, ), SizedBox(height: 15.h), AuthGestureDetector( onTap: () { ref .read(videoDetailProvider.notifier) .favoriteVideo(video.isFavorite ?? false); ref .read(recommendListProvider.notifier) .fetchVideoFavorite( videoId: widget.param.id, current: video.isFavorite ?? false, ); }, child: Icon( Icons.star, color: video.isFavorite == true ? Colors.red : Colors.white, size: 25.sp, ), ), myTxt( text: video.favoriteCount.toSafeString, color: Colors.white, fontSize: 12.sp, ), SizedBox(height: 15.h), GestureDetector( onTap: () { // shareUrl( // title: video.shareDesc ?? "", // url: video.shareUrl ?? "", // ); shareAction(video); if (uuid.isNotEmpty) { ref .read(recommendListProvider.notifier) .fetchVideoShare(contentId: widget.param.id); } }, child: Icon( Icons.screen_share, color: Colors.white, size: 25.sp, ), ), myTxt( text: video.shareCount.toSafeString, color: Colors.white, fontSize: 12.sp, ), SizedBox(height: 15.h), ], ), ), ], ), ); } void _showBottomCommentDialog( BuildContext ctx, String? contentId, String? title, CommentType commentType, ) { showModalBottomSheet( useSafeArea: true, backgroundColor: Colors.white, context: ctx, isScrollControlled: true, // 允许内容滚动并控制高度 builder: (context) { return Container( padding: EdgeInsets.all(horizontalPadding), height: screenHeight * 0.7, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 15.h), myTxt(text: title ?? "", color: color333333, fontSize: 15.sp), Container( height: 0.5, width: screenWidth, color: colorE5E5E5, margin: EdgeInsets.symmetric(vertical: 10.h), ), Expanded( child: CommentPage( type: commentType, articleId: contentId ?? "", ), ), ], ), ); }, ); } }