From fa5fd124a45aee14d9dc630f69d3f33c14623839 Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Tue, 20 Oct 2026 15:59:23 +0800 Subject: [PATCH] =?UTF-8?q?fix(flutter):=20=E4=BC=98=E5=8C=96=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E5=8A=A0=E8=BD=BD=E5=8D=A0=E4=BD=8D=E4=B8=8E=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/player/player_view.dart | 48 +++++++++++++++++++++---- test/player/player_fullscreen_test.dart | 48 +++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 10 deletions(-) diff --git a/lib/pages/player/player_view.dart b/lib/pages/player/player_view.dart index 6b77976..c08ab0a 100644 --- a/lib/pages/player/player_view.dart +++ b/lib/pages/player/player_view.dart @@ -20,6 +20,7 @@ import '../../core/theme/app_colors.dart'; import '../../core/theme/app_theme.dart'; import '../../core/widgets/auth_gate.dart'; import '../../core/widgets/download_action.dart'; +import '../../core/widgets/remote_image.dart'; import '../../l10n/app_localizations.dart'; import 'player_binding.dart'; import 'player_logic.dart'; @@ -298,11 +299,7 @@ class _PlayerPageState extends State with WidgetsBindingObserver { return Stack( fit: StackFit.expand, children: [ - if (!ready) - const ColoredBox( - key: Key('player-fallback-background'), - color: Colors.black, - ), + if (!ready) _PlayerFallbackBackground(posterUrl: drama.posterUrl), ?video, if (widget.homeMode && _chromeVisible && !logic.chromeHidden) Positioned.fill( @@ -316,7 +313,10 @@ class _PlayerPageState extends State with WidgetsBindingObserver { // 票据三态之一:起播前正在向后端要签名地址 if (selected && logic.ticketLoading) const Center( - child: CircularProgressIndicator(color: AppColors.blueSoft), + child: CircularProgressIndicator( + key: Key('player-ticket-loading'), + color: AppColors.blue, + ), ), // 票据三态之二:拿票失败(断网/服务异常)→ 可重试,不假装在加载 if (selected && !logic.ticketLoading && logic.ticketError != null) @@ -348,7 +348,10 @@ class _PlayerPageState extends State with WidgetsBindingObserver { ), if (selected && logic.session.loading) const Center( - child: CircularProgressIndicator(color: AppColors.blueSoft), + child: CircularProgressIndicator( + key: Key('player-session-loading'), + color: AppColors.blue, + ), ), if (selected && !ready && logic.session.error != null) Center( @@ -550,3 +553,34 @@ class _PlayerPageState extends State with WidgetsBindingObserver { ); } } + +class _PlayerFallbackBackground extends StatelessWidget { + const _PlayerFallbackBackground({required this.posterUrl}); + + final String posterUrl; + + @override + Widget build(BuildContext context) { + if (posterUrl.trim().isEmpty) { + return const ColoredBox( + key: Key('player-fallback-background'), + color: Colors.black, + ); + } + return Stack( + key: const Key('player-fallback-background'), + fit: StackFit.expand, + children: [ + RemoteImage( + posterUrl, + key: const Key('player-fallback-cover'), + fit: BoxFit.cover, + ), + const ColoredBox( + key: Key('player-fallback-scrim'), + color: Color(0x66000000), + ), + ], + ); + } +} diff --git a/test/player/player_fullscreen_test.dart b/test/player/player_fullscreen_test.dart index 345d15a..92896d9 100644 --- a/test/player/player_fullscreen_test.dart +++ b/test/player/player_fullscreen_test.dart @@ -49,6 +49,26 @@ void main() { expect(tester.takeException(), isNull); }); + testWidgets('should_fall_back_to_black_when_poster_is_empty', (tester) async { + await _pumpApp( + tester, + catalog: FakeCatalogRepository(dramas: const [_noCoverDrama]), + ); + await _openSeriesPlayer( + tester, + cardKey: const Key('home-drama-card-no-cover-demo'), + ); + + expect( + tester + .widget( + find.byKey(const Key('player-fallback-background')), + ) + .color, + Colors.black, + ); + }); + testWidgets('should_show_center_toggle_only_when_paused', (tester) async { await _pumpApp(tester); await _openSeriesPlayer(tester); @@ -283,9 +303,14 @@ void main() { ); expect(find.byKey(const Key('player-background')), findsNothing); final fallback = find.byKey(const Key('player-fallback-background')); - if (fallback.evaluate().isNotEmpty) { - expect(tester.widget(fallback).color, Colors.black); - } + expect(fallback, findsOneWidget); + expect(find.byKey(const Key('player-fallback-cover')), findsOneWidget); + expect( + tester + .widget(find.byKey(const Key('player-fallback-scrim'))) + .color, + const Color(0x66000000), + ); expect( tester .widget(find.byKey(const Key('player-scaffold'))) @@ -1024,3 +1049,20 @@ const _lockedDrama = Drama( ), ], ); + +const _noCoverDrama = Drama( + id: 'no-cover-demo', + title: 'No Cover Demo', + synopsis: 'No cover synopsis', + posterUrl: '', + detailLoaded: true, + episodes: [ + Episode( + id: 'no-cover-demo-1', + number: 1, + title: 'No Cover Episode', + isFree: true, + videoUrl: 'https://example.test/no-cover-demo-1.mp4', + ), + ], +);