fix(player): 独立全屏路由复用播放会话

This commit is contained in:
Zhou Weidong committed 2026-10-26 16:08:15 +08:00
1 parent 23e80dd637
commit 9d2d3b97a1
4 files changed
+219 -103

No files matched your search

+150 -91
View File
@@ -55,6 +55,20 @@ const double _playerLoadingSize = 24;
const double _playerLoadingStrokeWidth = 2; const double _playerLoadingStrokeWidth = 2;
const double _feedRefreshTopGap = 56; const double _feedRefreshTopGap = 56;
class PlayerPageHandoff {
PlayerPageHandoff({
required this.logic,
required this.initialIndex,
required this.prepareSource,
});
final PlayerLogic logic;
final int initialIndex;
final Future<void> Function() prepareSource;
PageController? destinationController;
VoidCallback? activateDestination;
}
class PlayerPage extends StatefulWidget { class PlayerPage extends StatefulWidget {
const PlayerPage({ const PlayerPage({
super.key, super.key,
@@ -82,6 +96,7 @@ class PlayerPage extends StatefulWidget {
this.onChannelSelected, this.onChannelSelected,
this.wallet, this.wallet,
this.downloads, this.downloads,
this.handoff,
}); });
final Drama drama; final Drama drama;
@@ -108,6 +123,7 @@ class PlayerPage extends StatefulWidget {
final ValueChanged<HomeFeedChannel>? onChannelSelected; final ValueChanged<HomeFeedChannel>? onChannelSelected;
final WalletController? wallet; final WalletController? wallet;
final DownloadController? downloads; final DownloadController? downloads;
final PlayerPageHandoff? handoff;
@override @override
State<PlayerPage> createState() => _PlayerPageState(); State<PlayerPage> createState() => _PlayerPageState();
@@ -116,6 +132,8 @@ class PlayerPage extends StatefulWidget {
class _PlayerPageState extends State<PlayerPage> class _PlayerPageState extends State<PlayerPage>
with WidgetsBindingObserver, RouteAware { with WidgetsBindingObserver, RouteAware {
late final PlayerLogic logic; late final PlayerLogic logic;
late final bool _ownsLogic;
late PageController _pageController;
late final PlaybackThumbnailService _thumbnailService; late final PlaybackThumbnailService _thumbnailService;
late final SpriteThumbnailAxisService _spriteAxisService; late final SpriteThumbnailAxisService _spriteAxisService;
PageRoute<dynamic>? _subscribedRoute; PageRoute<dynamic>? _subscribedRoute;
@@ -128,7 +146,11 @@ class _PlayerPageState extends State<PlayerPage>
DateTime? _lastDoubleTapAt; DateTime? _lastDoubleTapAt;
DateTime? _swipeStartedAt; DateTime? _swipeStartedAt;
bool _swipePrewarmCaptured = false; bool _swipePrewarmCaptured = false;
bool _fullPlayerPresented = false; bool _presentationActive = false;
bool _presentationSuspended = false;
bool _presentationReturning = false;
bool _leavingHandoff = false;
PageController? _returnPageController;
final Set<String> _precachedPosterUrls = {}; final Set<String> _precachedPosterUrls = {};
/// 收藏切换后的本地增量:基准是详情快照,切换成功按服务端状态 ±1。 /// 收藏切换后的本地增量:基准是详情快照,切换成功按服务端状态 ±1。
@@ -138,13 +160,17 @@ class _PlayerPageState extends State<PlayerPage>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
logic = PlayerBinding().create( final handoff = widget.handoff;
history: context.read<WatchHistoryRepository>(), _ownsLogic = handoff == null;
playback: context.read<PlaybackRepository>(), logic =
positionCache: context.read<PlaybackPositionCache>(), handoff?.logic ??
wallet: widget.wallet, PlayerBinding().create(
downloads: widget.downloads, history: context.read<WatchHistoryRepository>(),
); playback: context.read<PlaybackRepository>(),
positionCache: context.read<PlaybackPositionCache>(),
wallet: widget.wallet,
downloads: widget.downloads,
);
logic.attach(widget, isMounted: () => mounted); logic.attach(widget, isMounted: () => mounted);
_thumbnailService = PlaybackThumbnailService( _thumbnailService = PlaybackThumbnailService(
sourceResolver: _resolveThumbnailSource, sourceResolver: _resolveThumbnailSource,
@@ -152,10 +178,20 @@ class _PlayerPageState extends State<PlayerPage>
_spriteAxisService = SpriteThumbnailAxisService(spriteCacheCapacity: 8) _spriteAxisService = SpriteThumbnailAxisService(spriteCacheCapacity: 8)
..addListener(_handleSpriteFrameChanged); ..addListener(_handleSpriteFrameChanged);
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
logic.start( if (handoff == null) {
factory: context.read<PlayerFactory>(), logic.start(
coordinator: context.read<PlaybackCoordinator>(), factory: context.read<PlayerFactory>(),
); coordinator: context.read<PlaybackCoordinator>(),
);
_pageController = logic.pages;
} else {
final position = logic.visibleIndices.indexOf(handoff.initialIndex);
_pageController = PageController(
initialPage: position < 0 ? 0 : position,
);
handoff.destinationController = _pageController;
logic.session.setRouteVisible(widget.active);
}
} }
@override @override
@@ -258,13 +294,13 @@ class _PlayerPageState extends State<PlayerPage>
@override @override
void didPushNext() { void didPushNext() {
if (_fullPlayerPresented) return; if (_presentationActive) return;
logic.onRouteCovered(); logic.onRouteCovered();
} }
@override @override
void didPopNext() { void didPopNext() {
if (_fullPlayerPresented) return; if (_presentationActive) return;
logic.onRouteRevealed(); logic.onRouteRevealed();
} }
@@ -276,7 +312,11 @@ class _PlayerPageState extends State<PlayerPage>
..removeListener(_handleSpriteFrameChanged) ..removeListener(_handleSpriteFrameChanged)
..dispose(); ..dispose();
_thumbnailService.dispose(); _thumbnailService.dispose();
logic.dispose(); if (_ownsLogic) {
logic.dispose();
} else {
_pageController.dispose();
}
super.dispose(); super.dispose();
} }
@@ -513,6 +553,8 @@ class _PlayerPageState extends State<PlayerPage>
} }
Widget _buildPlayer(BuildContext context) { Widget _buildPlayer(BuildContext context) {
if (_presentationSuspended) return const SizedBox.expand();
if (_presentationReturning) return _buildPresentationReturnLayer();
final interactions = context.watch<InteractionController>(); final interactions = context.watch<InteractionController>();
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
final indices = logic.visibleIndices; final indices = logic.visibleIndices;
@@ -523,7 +565,7 @@ class _PlayerPageState extends State<PlayerPage>
final pages = NotificationListener<ScrollNotification>( final pages = NotificationListener<ScrollNotification>(
onNotification: _handlePageScroll, onNotification: _handlePageScroll,
child: PageView.builder( child: PageView.builder(
controller: logic.pages, controller: _pageController,
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
allowImplicitScrolling: true, allowImplicitScrolling: true,
physics: widget.onRefreshFeed == null physics: widget.onRefreshFeed == null
@@ -563,7 +605,7 @@ class _PlayerPageState extends State<PlayerPage>
final pageLayer = widget.onRefreshFeed == null final pageLayer = widget.onRefreshFeed == null
? pages ? pages
: FeedPullRefresh( : FeedPullRefresh(
pageController: logic.pages, pageController: _pageController,
topInset: topInset:
MediaQuery.paddingOf(context).top + _feedRefreshTopGap, MediaQuery.paddingOf(context).top + _feedRefreshTopGap,
onRefresh: _refreshHomeFeed, onRefresh: _refreshHomeFeed,
@@ -573,7 +615,7 @@ class _PlayerPageState extends State<PlayerPage>
fit: StackFit.expand, fit: StackFit.expand,
children: [ children: [
PlayerRenderStack( PlayerRenderStack(
pageController: logic.pages, pageController: _pageController,
slots: _renderSlots(indices), slots: _renderSlots(indices),
), ),
pageLayer, pageLayer,
@@ -620,7 +662,7 @@ class _PlayerPageState extends State<PlayerPage>
), ),
], ],
); );
return AnnotatedRegion<SystemUiOverlayStyle>( final player = AnnotatedRegion<SystemUiOverlayStyle>(
key: const Key('player-system-overlay'), key: const Key('player-system-overlay'),
value: SystemUiOverlayStyle.light.copyWith( value: SystemUiOverlayStyle.light.copyWith(
statusBarColor: AppColors.transparent, statusBarColor: AppColors.transparent,
@@ -641,6 +683,14 @@ class _PlayerPageState extends State<PlayerPage>
), ),
), ),
); );
if (widget.handoff == null) return player;
return PopScope<void>(
canPop: _leavingHandoff,
onPopInvokedWithResult: (didPop, _) {
if (!didPop) unawaited(_leaveHandoff(context));
},
child: player,
);
} }
void _openCurrentMore() { void _openCurrentMore() {
@@ -701,6 +751,18 @@ class _PlayerPageState extends State<PlayerPage>
}; };
} }
Widget _buildPresentationReturnLayer() {
final indices = logic.visibleIndices;
final controller = _returnPageController ?? _pageController;
return AnimatedBuilder(
animation: logic.session,
builder: (context, _) => PlayerRenderStack(
pageController: controller,
slots: _renderSlots(indices),
),
);
}
PlayerRenderSlotData _renderSlot( PlayerRenderSlotData _renderSlot(
PlayerRenderSlotRole role, PlayerRenderSlotRole role,
int? index, int? index,
@@ -933,7 +995,7 @@ class _PlayerPageState extends State<PlayerPage>
), ),
if (logic.pip || logic.pipPreparing) if (logic.pip || logic.pipPreparing)
const SizedBox.expand() const SizedBox.expand()
else if (widget.homeMode && !_fullPlayerPresented) else if (widget.homeMode && !_presentationActive)
HomeFeedOverlay( HomeFeedOverlay(
l10n: l10n, l10n: l10n,
drama: drama, drama: drama,
@@ -1024,9 +1086,8 @@ class _PlayerPageState extends State<PlayerPage>
Widget _standardPlaybackOverlay( Widget _standardPlaybackOverlay(
BuildContext context, BuildContext context,
int index, int index,
InteractionController interactions, { InteractionController interactions,
bool presentation = false, ) {
}) {
if (logic.pip || logic.pipPreparing) return const SizedBox.expand(); if (logic.pip || logic.pipPreparing) return const SizedBox.expand();
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
final entry = logic.queue.entryAt(index); final entry = logic.queue.entryAt(index);
@@ -1067,8 +1128,8 @@ class _PlayerPageState extends State<PlayerPage>
), ),
scrubbing: _scrubbing, scrubbing: _scrubbing,
actions: PlayerOverlayActions( actions: PlayerOverlayActions(
onBack: presentation onBack: widget.handoff != null
? () => Navigator.of(context).pop() ? () => unawaited(_leaveHandoff(context))
: widget.embedded : widget.embedded
? null ? null
: () => unawaited(logic.leave(context)), : () => unawaited(logic.leave(context)),
@@ -1098,56 +1159,82 @@ class _PlayerPageState extends State<PlayerPage>
onBoostEnd: _endBoost, onBoostEnd: _endBoost,
), ),
controlsVisible: _chromeVisible, controlsVisible: _chromeVisible,
onVerticalDragUpdate: presentation
? _handleFullPlayerDragUpdate
: null,
onVerticalDragEnd: presentation ? _handleFullPlayerDragEnd : null,
); );
} }
Future<void> _presentFullPlayer(BuildContext context, int index) async { Future<void> _presentFullPlayer(BuildContext context, int index) async {
if (_fullPlayerPresented || !logic.pages.hasClients) return; if (_presentationActive || !_pageController.hasClients) return;
// The route owns presentation only; PlaybackSession keeps the controller. final sourcePageController = _pageController;
final interactions = context.read<InteractionController>(); final handoff = PlayerPageHandoff(
logic: logic,
initialIndex: index,
prepareSource: _prepareSourceAfterFullPlayer,
);
handoff.activateDestination = () {
final destination = handoff.destinationController;
if (destination != null) logic.pages = destination;
};
_logFullPlayerRoute('present'); _logFullPlayerRoute('present');
setState(() => _fullPlayerPresented = true); setState(() => _presentationActive = true);
await Navigator.of(context).push<void>( final route = Navigator.of(context).push<void>(
PageRouteBuilder<void>( MaterialPageRoute<void>(
opaque: false, builder: (_) => PlayerPage(
barrierColor: null, drama: logic.queue.entryAt(index).drama,
barrierDismissible: false, initialIndex: index,
transitionDuration: const Duration(milliseconds: 180), handoff: handoff,
reverseTransitionDuration: const Duration(milliseconds: 160), wallet: logic.wallet,
pageBuilder: (routeContext, _, _) => Material( downloads: logic.downloads,
color: AppColors.transparent,
child: Scaffold(
backgroundColor: AppColors.transparent,
body: ListenableBuilder(
listenable: logic,
builder: (context, _) => AnimatedBuilder(
animation: logic.session,
builder: (context, _) {
final current = logic.session.currentIndex;
return _standardPlaybackOverlay(
routeContext,
current,
interactions,
presentation: true,
);
},
),
),
),
), ),
transitionsBuilder: (context, animation, _, child) =>
FadeTransition(opacity: animation, child: child),
), ),
); );
await WidgetsBinding.instance.endOfFrame;
if (mounted) {
setState(() => _presentationSuspended = true);
await WidgetsBinding.instance.endOfFrame;
}
sourcePageController.dispose();
handoff.activateDestination?.call();
await route;
if (!mounted) return; if (!mounted) return;
setState(() => _fullPlayerPresented = false); final currentPage = logic.visibleIndices.indexOf(
logic.session.currentIndex,
);
final restoredPage =
_returnPageController ??
PageController(initialPage: currentPage < 0 ? 0 : currentPage);
logic.pages = restoredPage;
_pageController = restoredPage;
logic.attach(widget, isMounted: () => mounted);
setState(() {
_presentationActive = false;
_presentationSuspended = false;
_presentationReturning = false;
});
logic.session.setRouteVisible(widget.active);
_logFullPlayerRoute('restored'); _logFullPlayerRoute('restored');
} }
Future<void> _prepareSourceAfterFullPlayer() async {
if (!mounted) return;
final page = logic.visibleIndices.indexOf(logic.session.currentIndex);
_returnPageController = PageController(
initialPage: page < 0 ? 0 : page,
);
logic.attach(widget, isMounted: () => mounted);
setState(() {
_presentationSuspended = false;
_presentationReturning = true;
});
await WidgetsBinding.instance.endOfFrame;
}
Future<void> _leaveHandoff(BuildContext context) async {
if (_leavingHandoff) return;
setState(() => _leavingHandoff = true);
await widget.handoff?.prepareSource();
if (context.mounted) Navigator.of(context).pop();
}
void _logFullPlayerRoute(String phase) { void _logFullPlayerRoute(String phase) {
if (!kDebugMode) return; if (!kDebugMode) return;
final active = logic.session.active; final active = logic.session.active;
@@ -1161,32 +1248,4 @@ class _PlayerPageState extends State<PlayerPage>
); );
} }
void _handleFullPlayerDragUpdate(DragUpdateDetails details) {
if (!logic.pages.hasClients) return;
final position = logic.pages.position;
final next = (position.pixels - details.delta.dy).clamp(
position.minScrollExtent,
position.maxScrollExtent,
);
if ((next - position.pixels).abs() < 0.5) return;
position.jumpTo(next);
}
void _handleFullPlayerDragEnd(DragEndDetails details) {
if (!logic.pages.hasClients || logic.visibleIndices.isEmpty) return;
final position = logic.pages.position;
final page = position.pixels / position.viewportDimension;
final velocity = details.primaryVelocity ?? 0;
var target = page.round();
if (velocity < -260) target = page.floor() + 1;
if (velocity > 260) target = page.ceil() - 1;
target = target.clamp(0, logic.visibleIndices.length - 1);
unawaited(
logic.pages.animateToPage(
target,
duration: const Duration(milliseconds: 220),
curve: Curves.easeOut,
),
);
}
} }
@@ -36,8 +36,6 @@ class PlayerPlaybackOverlay extends StatelessWidget {
required this.actions, required this.actions,
required this.controlsVisible, required this.controlsVisible,
required this.scrubbing, required this.scrubbing,
this.onVerticalDragUpdate,
this.onVerticalDragEnd,
}); });
/// 展示数据。 /// 展示数据。
@@ -51,8 +49,6 @@ class PlayerPlaybackOverlay extends StatelessWidget {
/// 正在拖动进度条。拖动期间清屏,只留进度条。 /// 正在拖动进度条。拖动期间清屏,只留进度条。
final bool scrubbing; final bool scrubbing;
final GestureDragUpdateCallback? onVerticalDragUpdate;
final GestureDragEndCallback? onVerticalDragEnd;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -69,8 +65,6 @@ class PlayerPlaybackOverlay extends StatelessWidget {
_handleLongPressStart(details, constraints.maxWidth), _handleLongPressStart(details, constraints.maxWidth),
onLongPressEnd: (_) => actions.onBoostEnd(), onLongPressEnd: (_) => actions.onBoostEnd(),
onLongPressCancel: actions.onBoostEnd, onLongPressCancel: actions.onBoostEnd,
onVerticalDragUpdate: onVerticalDragUpdate,
onVerticalDragEnd: onVerticalDragEnd,
), ),
), ),
), ),
+59 -4
View File
@@ -23,6 +23,7 @@ import 'package:fruitshorts/pages/auth/auth_view.dart';
import 'package:fruitshorts/pages/player/player_view.dart'; import 'package:fruitshorts/pages/player/player_view.dart';
import 'package:fruitshorts/pages/player/widgets/player_center_toggle.dart'; import 'package:fruitshorts/pages/player/widgets/player_center_toggle.dart';
import 'package:fruitshorts/pages/player/widgets/player_render_stack.dart'; import 'package:fruitshorts/pages/player/widgets/player_render_stack.dart';
import 'package:fruitshorts/pages/player/widgets/player_video_surface.dart';
import 'package:fruitshorts/pages/player/widgets/seek_progress_bar.dart'; import 'package:fruitshorts/pages/player/widgets/seek_progress_bar.dart';
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';
@@ -104,13 +105,15 @@ void main() {
expect(recommendPlayer, findsOneWidget); expect(recommendPlayer, findsOneWidget);
expect(renderStack, findsOneWidget); expect(renderStack, findsOneWidget);
final playerState = tester.state(recommendPlayer); final playerState = tester.state(recommendPlayer);
final renderState = tester.state(renderStack);
final currentSurface = find.descendant( final currentSurface = find.descendant(
of: recommendPlayer, of: recommendPlayer,
matching: find.byKey( matching: find.byKey(
const ValueKey('player-render-slot-current'), const ValueKey('player-render-slot-current'),
), ),
); );
final sourceRender = tester.widget<PlayerRenderStack>(renderStack);
final sourceIndex =
sourceRender.slots[PlayerRenderSlotRole.current]?.index;
final controller = factory.created.first.controller; final controller = factory.created.first.controller;
final createdBefore = factory.created.length; final createdBefore = factory.created.length;
final requestsBefore = playback.requests.length; final requestsBefore = playback.requests.length;
@@ -126,19 +129,37 @@ void main() {
await tester.tap(find.byKey(const Key('home-feed-full-series'))); await tester.tap(find.byKey(const Key('home-feed-full-series')));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.byType(PlayerPage), findsOneWidget); expect(find.byType(PlayerPage), findsOneWidget);
final fullPlayer = tester.widget<PlayerPage>(find.byType(PlayerPage));
expect(fullPlayer.homeMode, isFalse);
expect(fullPlayer.handoff, isNotNull);
expect(fullPlayer.handoff!.initialIndex, sourceIndex);
expect(fullPlayer.handoff!.logic.session.currentIndex, sourceIndex);
expect(find.byKey(const Key('home-tabs-bar')), findsNothing);
expect(find.byKey(const Key('recommend-top-nav')), findsNothing);
expect(find.byKey(const Key('home-feed-gesture')), findsNothing);
expect(find.byKey(const Key('player-back')), findsOneWidget); expect(find.byKey(const Key('player-back')), findsOneWidget);
expect(factory.created.length, createdBefore); expect(factory.created.length, createdBefore);
expect(playback.requests.length, requestsBefore); expect(playback.requests.length, requestsBefore);
expect(controller.value.position, const Duration(seconds: 5)); expect(controller.value.position, const Duration(seconds: 5));
final fullCurrentSurface = find.descendant(
of: find.byType(PlayerPage),
matching: find.byKey(
const ValueKey('player-render-slot-current'),
),
);
expect( expect(
find.descendant( find.descendant(
of: currentSurface, of: fullCurrentSurface,
matching: find.byKey(const Key('player-single-placeholder')), matching: find.byKey(const Key('player-single-placeholder')),
), ),
findsNothing, findsNothing,
); );
expect(
tester.widget<PlayerVideoSurface>(fullCurrentSurface).controller,
same(controller),
);
tester.state<NavigatorState>(find.byType(Navigator).first).pop(); await tester.tap(find.byKey(const Key('player-back')));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.byKey(const Key('recommend-page')), findsOneWidget); expect(find.byKey(const Key('recommend-page')), findsOneWidget);
@@ -147,8 +168,9 @@ void main() {
expect(factory.created.length, createdBefore); expect(factory.created.length, createdBefore);
expect(playback.requests.length, requestsBefore); expect(playback.requests.length, requestsBefore);
expect(controller.value.position, const Duration(seconds: 5)); expect(controller.value.position, const Duration(seconds: 5));
expect(find.byKey(const Key('home-tabs-bar')), findsOneWidget);
expect(find.byKey(const Key('recommend-top-nav')), findsOneWidget);
expect(tester.state(recommendPlayer), same(playerState)); expect(tester.state(recommendPlayer), same(playerState));
expect(tester.state(renderStack), same(renderState));
expect(find.byKey(const Key('recommend-feed-skeleton')), findsNothing); expect(find.byKey(const Key('recommend-feed-skeleton')), findsNothing);
expect( expect(
find.descendant( find.descendant(
@@ -157,6 +179,10 @@ void main() {
), ),
findsNothing, findsNothing,
); );
expect(
tester.widget<PlayerVideoSurface>(currentSurface).controller,
same(controller),
);
}); });
testWidgets('should_swipe_full_player_without_creating_another_session', ( testWidgets('should_swipe_full_player_without_creating_another_session', (
@@ -179,6 +205,35 @@ void main() {
expect(find.byType(PlayerPage), findsOneWidget); expect(find.byType(PlayerPage), findsOneWidget);
expect(factory.created.length, lessThanOrEqualTo(createdBefore + 1)); expect(factory.created.length, lessThanOrEqualTo(createdBefore + 1));
expect(find.byKey(const Key('player-render-stack')), findsOneWidget); expect(find.byKey(const Key('player-render-stack')), findsOneWidget);
await tester.tap(find.byKey(const Key('player-back')));
await tester.pumpAndSettle();
});
testWidgets('should_show_one_paused_overlay_when_entering_full_player', (
tester,
) async {
final factory = _RouteVideoFactory();
await _pumpApp(tester, playerFactory: factory.create);
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('home-feed-gesture')));
await tester.pump(const Duration(milliseconds: 400));
expect(find.byKey(const Key('home-feed-center-play')), findsOneWidget);
await tester.tap(find.byKey(const Key('home-feed-full-series')));
await tester.pumpAndSettle();
expect(find.byType(PlayerPage), findsOneWidget);
expect(find.byKey(const Key('player-play-toggle')), findsOneWidget);
expect(find.byKey(const Key('home-feed-center-play')), findsNothing);
expect(find.byKey(const Key('home-tabs-bar')), findsNothing);
expect(find.byKey(const Key('recommend-top-nav')), findsNothing);
await tester.tap(find.byKey(const Key('player-back')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('home-feed-center-play')), findsOneWidget);
expect(find.byKey(const Key('home-tabs-bar')), findsOneWidget);
}); });
testWidgets('should_render_player_loading_indicators_at_compact_size', ( testWidgets('should_render_player_loading_indicators_at_compact_size', (
+10 -2
View File
@@ -474,10 +474,11 @@ void main() {
expect(find.byType(PlayerPage), findsOneWidget); expect(find.byType(PlayerPage), findsOneWidget);
expect( expect(
tester.widget<PlayerPage>(find.byType(PlayerPage)).homeMode, tester.widget<PlayerPage>(find.byType(PlayerPage)).homeMode,
isTrue, isFalse,
); );
expect(find.byKey(const Key('player-back')), findsOneWidget); expect(find.byKey(const Key('player-back')), findsOneWidget);
tester.state<NavigatorState>(find.byType(Navigator).first).pop(); expect(find.byKey(const Key('home-tabs-bar')), findsNothing);
await tester.tap(find.byKey(const Key('player-back')));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.byKey(const Key('recommend-page')), findsOneWidget); expect(find.byKey(const Key('recommend-page')), findsOneWidget);
expect(find.byKey(const Key('recommend-tab-recommend')), findsOneWidget); expect(find.byKey(const Key('recommend-tab-recommend')), findsOneWidget);
@@ -485,7 +486,14 @@ void main() {
await tester.tap(find.byKey(const Key('home-feed-full-series'))); await tester.tap(find.byKey(const Key('home-feed-full-series')));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.byType(PlayerPage), findsOneWidget); expect(find.byType(PlayerPage), findsOneWidget);
expect(
tester.widget<PlayerPage>(find.byType(PlayerPage)).homeMode,
isFalse,
);
expect(find.byKey(const Key('player-back')), findsOneWidget); expect(find.byKey(const Key('player-back')), findsOneWidget);
expect(find.byKey(const Key('home-tabs-bar')), findsNothing);
await tester.tap(find.byKey(const Key('player-back')));
await tester.pumpAndSettle();
}); });
testWidgets('should_pull_refresh_recommend_feed_from_the_first_video', ( testWidgets('should_pull_refresh_recommend_feed_from_the_first_video', (