diff --git a/lib/pages/player/player_view_actions.dart b/lib/pages/player/player_view_actions.dart index 748cc50..ca874c1 100644 --- a/lib/pages/player/player_view_actions.dart +++ b/lib/pages/player/player_view_actions.dart @@ -35,6 +35,9 @@ extension _PlayerViewActions on _PlayerPageState { }, onClear: _hideChrome, onDownload: () => requestEpisodeDownload(context, episode.id), + onPip: logic.pictureInPicture.isPlatformSupported + ? () => unawaited(logic.enterPip(context)) + : null, ), ), ); diff --git a/lib/pages/player/widgets/player_more_sheet.dart b/lib/pages/player/widgets/player_more_sheet.dart index 53cf4df..49eb80f 100644 --- a/lib/pages/player/widgets/player_more_sheet.dart +++ b/lib/pages/player/widgets/player_more_sheet.dart @@ -35,6 +35,7 @@ class PlayerMoreActions { required this.onSpeedChanged, required this.onClear, required this.onDownload, + this.onPip, }); /// 倍速变化。 @@ -45,6 +46,9 @@ class PlayerMoreActions { /// 离线缓存当前集。 final VoidCallback onDownload; + + /// 进入小窗播放;平台不支持时为 null。 + final VoidCallback? onPip; } /// K29.6 精简版播放页更多面板。 @@ -113,6 +117,13 @@ class _PlayerMoreSheetState extends State { title: l10n.clearPlayback, onTap: _clear, ), + if (widget.actions.onPip != null) + _actionRow( + key: const Key('player-more-pip'), + icon: Icons.picture_in_picture_alt_rounded, + title: l10n.pictureInPicture, + onTap: () => _closeThen(widget.actions.onPip!), + ), _actionRow( key: const Key('player-more-offline'), icon: Icons.download_rounded, diff --git a/test/player/player_fullscreen_test.dart b/test/player/player_fullscreen_test.dart index 6db27eb..9b9d1ee 100644 --- a/test/player/player_fullscreen_test.dart +++ b/test/player/player_fullscreen_test.dart @@ -389,7 +389,6 @@ void main() { Key('player-more-text-size'), Key('player-more-cast'), Key('player-more-timer'), - Key('player-more-pip'), Key('player-more-report'), ]) { expect(find.byKey(removed), findsNothing); @@ -506,6 +505,30 @@ void main() { expect(find.text('720P · Unavailable'), findsOneWidget); }); + testWidgets('should_show_pip_action_when_platform_supported', (tester) async { + await _pumpApp(tester); + await _openSeriesPlayer(tester); + await tester.tap(find.byKey(const Key('player-more'))); + await tester.pumpAndSettle(); + + expect(find.byKey(const Key('player-more-pip')), findsOneWidget); + expect(find.byKey(const Key('player-more-clear')), findsOneWidget); + expect(find.byKey(const Key('player-more-offline')), findsOneWidget); + }, variant: TargetPlatformVariant.only(TargetPlatform.android)); + + testWidgets('should_hide_pip_action_when_platform_unsupported', ( + tester, + ) async { + await _pumpApp(tester); + await _openSeriesPlayer(tester); + await tester.tap(find.byKey(const Key('player-more'))); + await tester.pumpAndSettle(); + + expect(find.byKey(const Key('player-more-pip')), findsNothing); + expect(find.byKey(const Key('player-more-clear')), findsOneWidget); + expect(find.byKey(const Key('player-more-offline')), findsOneWidget); + }, variant: TargetPlatformVariant.only(TargetPlatform.macOS)); + testWidgets('should_enter_clear_screen_from_more_panel', (tester) async { await _pumpApp(tester); await _openSeriesPlayer(tester); diff --git a/test/player/player_more_sheet_test.dart b/test/player/player_more_sheet_test.dart new file mode 100644 index 0000000..3929b53 --- /dev/null +++ b/test/player/player_more_sheet_test.dart @@ -0,0 +1,77 @@ +// Author: fruit.com.cn + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:fruitshorts/l10n/app_localizations.dart'; +import 'package:fruitshorts/pages/player/widgets/player_more_sheet.dart'; + +void main() { + testWidgets('should_hide_pip_row_when_platform_unsupported', (tester) async { + await _openSheet(tester); + + expect(find.byKey(const Key('player-more-sheet')), findsOneWidget); + expect(find.byKey(const Key('player-more-clear')), findsOneWidget); + expect(find.byKey(const Key('player-more-offline')), findsOneWidget); + expect(find.byKey(const Key('player-more-pip')), findsNothing); + }); + + testWidgets('should_trigger_pip_and_close_sheet_when_tapped', (tester) async { + var pipCalls = 0; + await _openSheet(tester, onPip: () => pipCalls++); + + expect(find.byKey(const Key('player-more-pip')), findsOneWidget); + expect( + tester.getTopLeft(find.byKey(const Key('player-more-clear'))).dy < + tester.getTopLeft(find.byKey(const Key('player-more-pip'))).dy, + isTrue, + ); + expect( + tester.getTopLeft(find.byKey(const Key('player-more-pip'))).dy < + tester.getTopLeft(find.byKey(const Key('player-more-offline'))).dy, + isTrue, + ); + + await tester.tap(find.byKey(const Key('player-more-pip'))); + await tester.pumpAndSettle(); + + expect(pipCalls, 1); + expect(find.byKey(const Key('player-more-sheet')), findsNothing); + }); +} + +Future _openSheet(WidgetTester tester, {VoidCallback? onPip}) async { + await tester.pumpWidget( + MaterialApp( + supportedLocales: AppLocalizations.supportedLocales, + localizationsDelegates: AppLocalizations.localizationsDelegates, + home: Scaffold( + body: Builder( + builder: (context) => Center( + child: TextButton( + key: const Key('open-more-sheet'), + onPressed: () => showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + builder: (_) => PlayerMoreSheet( + l10n: AppLocalizations.of(context)!, + state: const PlayerMoreState(speed: 1.0, qualities: ['AUTO']), + actions: PlayerMoreActions( + onSpeedChanged: (_) {}, + onClear: () {}, + onDownload: () {}, + onPip: onPip, + ), + ), + ), + child: const Text('open'), + ), + ), + ), + ), + ), + ); + await tester.tap(find.byKey(const Key('open-more-sheet'))); + await tester.pumpAndSettle(); +}