@@ -7,6 +7,7 @@ import 'package:nipaplay/services/emby_service.dart';
77import 'package:nipaplay/models/watch_history_model.dart' ;
88import 'package:nipaplay/themes/nipaplay/widgets/cached_network_image_widget.dart' ;
99import 'package:nipaplay/themes/nipaplay/widgets/blur_snackbar.dart' ;
10+ import 'package:nipaplay/themes/nipaplay/widgets/blur_dialog.dart' ;
1011import 'package:kmbal_ionicons/kmbal_ionicons.dart' ;
1112import 'package:nipaplay/themes/nipaplay/widgets/switchable_view.dart' ;
1213import 'package:provider/provider.dart' ;
@@ -93,6 +94,10 @@ class _MediaServerDetailPageState extends State<MediaServerDetailPage> with Sing
9394 String ? _error;
9495 bool _isMovie = false ; // 新增状态,判断是否为电影
9596
97+ bool _isDetailAutoMatching = false ;
98+ bool _detailAutoMatchDialogVisible = false ;
99+ bool _detailAutoMatchCancelled = false ;
100+
96101 TabController ? _tabController;
97102
98103 // 辅助方法:获取演员头像URL
@@ -357,35 +362,36 @@ class _MediaServerDetailPageState extends State<MediaServerDetailPage> with Sing
357362
358363 Future <void > _playMovie () async {
359364 if (_mediaDetail == null || ! _isMovie) return ;
365+ if (_isDetailAutoMatching) {
366+ BlurSnackBar .show (context, '正在自动匹配,请稍候' );
367+ return ;
368+ }
360369
361370 try {
362- dynamic matcher;
363- dynamic playableItem;
364-
365- if (widget.serverType == MediaServerType .jellyfin) {
366- // 将MediaItemDetail转换为JellyfinMovieInfo
367- final movieInfo = JellyfinMovieInfo (
368- id: _mediaDetail! .id,
369- name: _mediaDetail! .name,
370- overview: _mediaDetail! .overview,
371- originalTitle: _mediaDetail! .originalTitle,
372- imagePrimaryTag: _mediaDetail! .imagePrimaryTag,
373- imageBackdropTag: _mediaDetail! .imageBackdropTag,
374- productionYear: _mediaDetail! .productionYear,
375- dateAdded: _mediaDetail! .dateAdded,
376- premiereDate: _mediaDetail! .premiereDate,
377- communityRating: _mediaDetail! .communityRating,
378- genres: _mediaDetail! .genres,
379- officialRating: _mediaDetail! .officialRating,
380- cast: _mediaDetail! .cast,
381- directors: _mediaDetail! .directors,
382- runTimeTicks: _mediaDetail! .runTimeTicks,
383- studio: _mediaDetail! .seriesStudio,
384- );
385- matcher = JellyfinDandanplayMatcher .instance;
386- playableItem = await matcher.createPlayableHistoryItemFromMovie (context, movieInfo);
387- } else {
388- // 将MediaItemDetail转换为EmbyMovieInfo
371+ final playableItem = await _runDetailAutoMatchTask <WatchHistoryItem ?>(() async {
372+ if (widget.serverType == MediaServerType .jellyfin) {
373+ final movieInfo = JellyfinMovieInfo (
374+ id: _mediaDetail! .id,
375+ name: _mediaDetail! .name,
376+ overview: _mediaDetail! .overview,
377+ originalTitle: _mediaDetail! .originalTitle,
378+ imagePrimaryTag: _mediaDetail! .imagePrimaryTag,
379+ imageBackdropTag: _mediaDetail! .imageBackdropTag,
380+ productionYear: _mediaDetail! .productionYear,
381+ dateAdded: _mediaDetail! .dateAdded,
382+ premiereDate: _mediaDetail! .premiereDate,
383+ communityRating: _mediaDetail! .communityRating,
384+ genres: _mediaDetail! .genres,
385+ officialRating: _mediaDetail! .officialRating,
386+ cast: _mediaDetail! .cast,
387+ directors: _mediaDetail! .directors,
388+ runTimeTicks: _mediaDetail! .runTimeTicks,
389+ studio: _mediaDetail! .seriesStudio,
390+ );
391+ return JellyfinDandanplayMatcher .instance
392+ .createPlayableHistoryItemFromMovie (context, movieInfo);
393+ }
394+
389395 final movieInfo = EmbyMovieInfo (
390396 id: _mediaDetail! .id,
391397 name: _mediaDetail! .name,
@@ -404,19 +410,21 @@ class _MediaServerDetailPageState extends State<MediaServerDetailPage> with Sing
404410 runTimeTicks: _mediaDetail! .runTimeTicks,
405411 studio: _mediaDetail! .seriesStudio,
406412 );
407- matcher = EmbyDandanplayMatcher .instance;
408- playableItem = await matcher.createPlayableHistoryItemFromMovie (context, movieInfo);
413+ return EmbyDandanplayMatcher .instance
414+ .createPlayableHistoryItemFromMovie (context, movieInfo);
415+ });
416+
417+ if (playableItem == null ) {
418+ if (! _detailAutoMatchCancelled && mounted) {
419+ BlurSnackBar .show (context, '未能找到匹配的弹幕信息,但仍可播放。' );
420+ final basicItem = _mediaDetail! .toWatchHistoryItem ();
421+ Navigator .of (context).pop (basicItem);
422+ }
423+ return ;
409424 }
410-
411- if (playableItem == null ) return ; // 用户取消,彻底中断
425+
412426 if (mounted) {
413427 Navigator .of (context).pop (playableItem);
414- } else if (mounted) {
415- // 如果匹配失败,可以给用户一个提示
416- BlurSnackBar .show (context, '未能找到匹配的弹幕信息,但仍可播放。' );
417- // 即使没有弹幕,也创建一个基本的播放项
418- final basicItem = _mediaDetail! .toWatchHistoryItem ();
419- Navigator .of (context).pop (basicItem);
420428 }
421429 } catch (e) {
422430 if (mounted) {
@@ -441,6 +449,95 @@ class _MediaServerDetailPageState extends State<MediaServerDetailPage> with Sing
441449 }
442450 }
443451
452+ Future <T ?> _runDetailAutoMatchTask <T >(Future <T ?> Function () task) async {
453+ if (_isDetailAutoMatching) {
454+ if (mounted) {
455+ BlurSnackBar .show (context, '正在自动匹配,请稍候' );
456+ }
457+ return null ;
458+ }
459+
460+ _updateDetailAutoMatchingState (true );
461+ _detailAutoMatchCancelled = false ;
462+ _showDetailAutoMatchingDialog ();
463+
464+ try {
465+ final result = await task ();
466+ if (_detailAutoMatchCancelled) {
467+ if (mounted) {
468+ BlurSnackBar .show (context, '已取消自动匹配' );
469+ }
470+ return null ;
471+ }
472+ return result;
473+ } finally {
474+ _hideDetailAutoMatchingDialog ();
475+ _updateDetailAutoMatchingState (false );
476+ }
477+ }
478+
479+ void _updateDetailAutoMatchingState (bool value) {
480+ if (! mounted) {
481+ _isDetailAutoMatching = value;
482+ return ;
483+ }
484+ if (_isDetailAutoMatching == value) {
485+ return ;
486+ }
487+ setState (() {
488+ _isDetailAutoMatching = value;
489+ });
490+ }
491+
492+ void _showDetailAutoMatchingDialog () {
493+ if (_detailAutoMatchDialogVisible || ! mounted) {
494+ return ;
495+ }
496+ _detailAutoMatchDialogVisible = true ;
497+ BlurDialog .show (
498+ context: context,
499+ title: '正在自动匹配' ,
500+ barrierDismissible: false ,
501+ contentWidget: Column (
502+ mainAxisSize: MainAxisSize .min,
503+ children: const [
504+ SizedBox (height: 8 ),
505+ CircularProgressIndicator (
506+ valueColor: AlwaysStoppedAnimation <Color >(Colors .white),
507+ ),
508+ SizedBox (height: 16 ),
509+ Text (
510+ '正在为当前条目匹配弹幕,请稍候…' ,
511+ style: TextStyle (color: Colors .white, fontSize: 14 ),
512+ textAlign: TextAlign .center,
513+ ),
514+ ],
515+ ),
516+ actions: [
517+ TextButton (
518+ onPressed: () {
519+ _detailAutoMatchCancelled = true ;
520+ Navigator .of (context, rootNavigator: true ).pop ();
521+ },
522+ child: const Text ('中断匹配' , style: TextStyle (color: Colors .redAccent)),
523+ ),
524+ ],
525+ ).whenComplete (() {
526+ _detailAutoMatchDialogVisible = false ;
527+ });
528+ }
529+
530+ void _hideDetailAutoMatchingDialog () {
531+ if (! _detailAutoMatchDialogVisible) {
532+ return ;
533+ }
534+ if (! mounted) {
535+ _detailAutoMatchDialogVisible = false ;
536+ return ;
537+ }
538+ Navigator .of (context, rootNavigator: true ).pop ();
539+ }
540+
444541 @override
445542 Widget build (BuildContext context) {
446543 Widget pageContent;
@@ -1059,7 +1156,13 @@ style: TextStyle(
10591156 BlurButton (
10601157 icon: Icons .play_arrow,
10611158 text: '播放' ,
1062- onTap: _playMovie,
1159+ onTap: () {
1160+ if (_isDetailAutoMatching) {
1161+ BlurSnackBar .show (context, '正在自动匹配,请稍候' );
1162+ return ;
1163+ }
1164+ _playMovie ();
1165+ },
10631166 padding: const EdgeInsets .symmetric (horizontal: 24 , vertical: 12 ),
10641167 fontSize: 18 ,
10651168 ),
@@ -1199,6 +1302,7 @@ style: TextStyle(color: Colors.white70)));
11991302
12001303 return ListTile (
12011304 contentPadding: const EdgeInsets .symmetric (horizontal: 16 , vertical: 8 ),
1305+ enabled: ! _isDetailAutoMatching,
12021306 leading: SizedBox (
12031307 width: 100 , // 调整图片宽度
12041308 height: 60 , // 调整图片高度,保持宽高比
@@ -1270,6 +1374,10 @@ style: TextStyle(fontSize: 12, color: Colors.grey[400]), // 调整颜色
12701374 ),
12711375 trailing: const Icon (Ionicons .play_circle_outline, color: Colors .white70, size: 22 ), // 添加播放按钮指示
12721376 onTap: () async {
1377+ if (_isDetailAutoMatching) {
1378+ BlurSnackBar .show (context, '正在自动匹配,请稍候' );
1379+ return ;
1380+ }
12731381 try {
12741382 BlurSnackBar .show (context, '准备播放: ${episode .name }' );
12751383
@@ -1289,7 +1397,7 @@ style: TextStyle(fontSize: 12, color: Colors.grey[400]), // 调整颜色
12891397
12901398 // 使用JellyfinDandanplayMatcher创建增强的WatchHistoryItem
12911399 // 这一步会显示匹配对话框,阻塞直到用户完成选择或跳过
1292- final historyItem = await _createWatchHistoryItem (episode);
1400+ final historyItem = await _runDetailAutoMatchTask < WatchHistoryItem ?>(() => _createWatchHistoryItem (episode) );
12931401 if (historyItem == null ) return ; // 用户关闭弹窗,什么都不做
12941402
12951403 // 用户已完成匹配选择,现在可以继续播放流程
0 commit comments