merge: 改为显式清屏交互
This commit is contained in:
commit
60ec19f08d
12 files changed
+158
-166
No files matched your search
@@ -46,6 +46,7 @@
|
||||
"total": {"type": "int"}
|
||||
}
|
||||
},
|
||||
"clearScreen": "Clear screen",
|
||||
"minutesAgo": "{count} min ago",
|
||||
"@minutesAgo": {
|
||||
"placeholders": {
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"total": {"type": "int"}
|
||||
}
|
||||
},
|
||||
"clearScreen": "Bersihkan layar",
|
||||
"minutesAgo": "{count} mnt lalu",
|
||||
"@minutesAgo": {
|
||||
"placeholders": {
|
||||
|
||||
@@ -340,6 +340,12 @@ abstract class AppLocalizations {
|
||||
/// **'Episode {current} / {total}'**
|
||||
String episodeOfTotal(int current, int total);
|
||||
|
||||
/// No description provided for @clearScreen.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Clear screen'**
|
||||
String get clearScreen;
|
||||
|
||||
/// No description provided for @minutesAgo.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -133,6 +133,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
return 'Episode $current / $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String get clearScreen => 'Clear screen';
|
||||
|
||||
@override
|
||||
String minutesAgo(int count) {
|
||||
return '$count min ago';
|
||||
|
||||
@@ -133,6 +133,9 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
return 'Episode $current / $total';
|
||||
}
|
||||
|
||||
@override
|
||||
String get clearScreen => 'Bersihkan layar';
|
||||
|
||||
@override
|
||||
String minutesAgo(int count) {
|
||||
return '$count mnt lalu';
|
||||
|
||||
@@ -130,6 +130,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
return '第 $current 集 / 共 $total 集';
|
||||
}
|
||||
|
||||
@override
|
||||
String get clearScreen => '清屏';
|
||||
|
||||
@override
|
||||
String minutesAgo(int count) {
|
||||
return '$count 分钟前';
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"total": {"type": "int"}
|
||||
}
|
||||
},
|
||||
"clearScreen": "清屏",
|
||||
"minutesAgo": "{count} 分钟前",
|
||||
"@minutesAgo": {
|
||||
"placeholders": {
|
||||
|
||||
@@ -30,16 +30,6 @@ import 'widgets/seek_progress_bar.dart';
|
||||
import '../../core/data/repositories/watch_history_repository.dart';
|
||||
import '../../core/data/repositories/playback_repository.dart';
|
||||
|
||||
/// The home feed has no overlay play button, so it keeps its own center hint.
|
||||
/// Detail/series playback uses `_PlaybackOverlay.player-play-toggle` instead.
|
||||
bool shouldShowFeedPauseIndicator({
|
||||
required bool homeMode,
|
||||
required bool selected,
|
||||
required bool ready,
|
||||
required bool playing,
|
||||
required bool pip,
|
||||
}) => homeMode && selected && ready && !playing && !pip;
|
||||
|
||||
class PlayerPage extends StatefulWidget {
|
||||
const PlayerPage({
|
||||
super.key,
|
||||
@@ -80,6 +70,7 @@ class PlayerPage extends StatefulWidget {
|
||||
|
||||
class _PlayerPageState extends State<PlayerPage> with WidgetsBindingObserver {
|
||||
late final PlayerLogic logic;
|
||||
bool _feedChromeVisible = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -198,9 +189,10 @@ class _PlayerPageState extends State<PlayerPage> with WidgetsBindingObserver {
|
||||
children: [
|
||||
const ColoredBox(color: AppColors.ink),
|
||||
?video,
|
||||
if (!logic.chromeHidden)
|
||||
const IgnorePointer(
|
||||
child: DecoratedBox(
|
||||
if (!logic.chromeHidden && (!widget.homeMode || _feedChromeVisible))
|
||||
IgnorePointer(
|
||||
key: widget.homeMode ? const Key('home-feed-scrim') : null,
|
||||
child: const DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
@@ -312,67 +304,59 @@ class _PlayerPageState extends State<PlayerPage> with WidgetsBindingObserver {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (shouldShowFeedPauseIndicator(
|
||||
homeMode: widget.homeMode,
|
||||
selected: selected,
|
||||
ready: ready,
|
||||
playing: logic.session.userWantsPlay,
|
||||
pip: logic.pip,
|
||||
))
|
||||
Center(
|
||||
child: PlayerCenterToggle(
|
||||
key: const Key('home-feed-center-play'),
|
||||
playing: false,
|
||||
onTap: () => logic.session.setUserWantsPlay(true),
|
||||
),
|
||||
),
|
||||
if (logic.pip || logic.pipPreparing)
|
||||
const SizedBox.expand()
|
||||
else if (widget.homeMode)
|
||||
HomeFeedOverlay(
|
||||
l10n: l10n,
|
||||
drama: drama,
|
||||
episode: episode,
|
||||
liked: interactions.isLiked(drama.id),
|
||||
saved: context.watch<FavoriteController>().isSaved(drama.id),
|
||||
onLike: () async {
|
||||
if (await ensureSignedIn(context)) {
|
||||
if (!context.mounted) return;
|
||||
unawaited(context.read<Haptics>().lightImpact());
|
||||
final result = await interactions.toggleDramaLike(drama.id);
|
||||
if (!result.isSuccess && context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
result.errorMessage ??
|
||||
AppLocalizations.of(context)!.likeFailed,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
onSave: () async {
|
||||
final favorites = context.read<FavoriteController>();
|
||||
if (await ensureSignedIn(context)) {
|
||||
if (!context.mounted) return;
|
||||
unawaited(context.read<Haptics>().lightImpact());
|
||||
final ok = await favorites.toggle(drama.id, card: drama);
|
||||
if (ok || !context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.favoriteToggleFailed)),
|
||||
);
|
||||
}
|
||||
},
|
||||
onComments: () => logic.showComments(context, drama),
|
||||
onShare: () => logic.shareDrama(context, drama),
|
||||
onEpisodes: () => logic.showEpisodes(context),
|
||||
onDownload: () => requestEpisodeDownload(context, episode.id),
|
||||
onSearch: widget.onSearch ?? () => logic.showDiscover(context),
|
||||
onPip: logic.pictureInPicture.isPlatformSupported
|
||||
? () => unawaited(logic.enterPip(context))
|
||||
: null,
|
||||
)
|
||||
_feedChromeVisible
|
||||
? HomeFeedOverlay(
|
||||
l10n: l10n,
|
||||
drama: drama,
|
||||
episode: episode,
|
||||
liked: interactions.isLiked(drama.id),
|
||||
saved: context.watch<FavoriteController>().isSaved(drama.id),
|
||||
onLike: () async {
|
||||
if (await ensureSignedIn(context)) {
|
||||
if (!context.mounted) return;
|
||||
unawaited(context.read<Haptics>().lightImpact());
|
||||
final result = await interactions.toggleDramaLike(
|
||||
drama.id,
|
||||
);
|
||||
if (!result.isSuccess && context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
result.errorMessage ??
|
||||
AppLocalizations.of(context)!.likeFailed,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
onSave: () async {
|
||||
final favorites = context.read<FavoriteController>();
|
||||
if (await ensureSignedIn(context)) {
|
||||
if (!context.mounted) return;
|
||||
unawaited(context.read<Haptics>().lightImpact());
|
||||
final ok = await favorites.toggle(drama.id, card: drama);
|
||||
if (ok || !context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.favoriteToggleFailed)),
|
||||
);
|
||||
}
|
||||
},
|
||||
onComments: () => logic.showComments(context, drama),
|
||||
onShare: () => logic.shareDrama(context, drama),
|
||||
onEpisodes: () => logic.showEpisodes(context),
|
||||
onDownload: () => requestEpisodeDownload(context, episode.id),
|
||||
onClear: () => setState(() => _feedChromeVisible = false),
|
||||
onSearch:
|
||||
widget.onSearch ?? () => logic.showDiscover(context),
|
||||
onPip: logic.pictureInPicture.isPlatformSupported
|
||||
? () => unawaited(logic.enterPip(context))
|
||||
: null,
|
||||
)
|
||||
: const SizedBox.shrink()
|
||||
else
|
||||
_PlaybackOverlay(
|
||||
l10n: l10n,
|
||||
@@ -433,6 +417,14 @@ class _PlayerPageState extends State<PlayerPage> with WidgetsBindingObserver {
|
||||
? (position) => logic.seekTo(context, videoPort!, position)
|
||||
: null,
|
||||
),
|
||||
if (widget.homeMode && !_feedChromeVisible)
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
key: const Key('home-feed-video-tap'),
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => setState(() => _feedChromeVisible = true),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -608,50 +600,17 @@ class _PlaybackOverlay extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PlaybackOverlayState extends State<_PlaybackOverlay> {
|
||||
Timer? _hideTimer;
|
||||
bool _controlsVisible = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scheduleHide();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_hideTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _scheduleHide() {
|
||||
_hideTimer?.cancel();
|
||||
_hideTimer = Timer(const Duration(milliseconds: 2500), () {
|
||||
if (mounted) setState(() => _controlsVisible = false);
|
||||
});
|
||||
}
|
||||
|
||||
void _reveal() {
|
||||
setState(() => _controlsVisible = true);
|
||||
_scheduleHide();
|
||||
}
|
||||
|
||||
void _togglePlay() {
|
||||
widget.onTogglePlay();
|
||||
_reveal();
|
||||
}
|
||||
|
||||
void _onVideoTap() {
|
||||
if (!_controlsVisible) {
|
||||
_reveal();
|
||||
} else {
|
||||
_togglePlay();
|
||||
setState(() => _controlsVisible = true);
|
||||
} else if (!widget.playing) {
|
||||
widget.onTogglePlay();
|
||||
}
|
||||
}
|
||||
|
||||
void _run(VoidCallback action) {
|
||||
action();
|
||||
_reveal();
|
||||
}
|
||||
void _clearScreen() => setState(() => _controlsVisible = false);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -673,13 +632,14 @@ class _PlaybackOverlayState extends State<_PlaybackOverlay> {
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
Center(
|
||||
child: PlayerCenterToggle(
|
||||
key: const Key('player-play-toggle'),
|
||||
playing: widget.playing,
|
||||
onTap: _togglePlay,
|
||||
if (widget.playing)
|
||||
Center(
|
||||
child: PlayerCenterToggle(
|
||||
key: const Key('player-play-toggle'),
|
||||
playing: true,
|
||||
onTap: widget.onTogglePlay,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Positioned(
|
||||
right: 10,
|
||||
bottom: 14,
|
||||
@@ -715,7 +675,7 @@ class _PlaybackOverlayState extends State<_PlaybackOverlay> {
|
||||
key: const Key('player-episodes'),
|
||||
icon: Icons.view_list_rounded,
|
||||
label: widget.l10n.episodes,
|
||||
onTap: () => _run(widget.onEpisodes),
|
||||
onTap: widget.onEpisodes,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -724,7 +684,7 @@ class _PlaybackOverlayState extends State<_PlaybackOverlay> {
|
||||
key: const Key('player-comments'),
|
||||
icon: Icons.chat_bubble_outline_rounded,
|
||||
label: widget.l10n.comments,
|
||||
onTap: () => _run(widget.onComments),
|
||||
onTap: widget.onComments,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -733,9 +693,7 @@ class _PlaybackOverlayState extends State<_PlaybackOverlay> {
|
||||
height: 32,
|
||||
child: FilledButton(
|
||||
key: const Key('player-next'),
|
||||
onPressed: widget.onNext == null
|
||||
? null
|
||||
: () => _run(widget.onNext!),
|
||||
onPressed: widget.onNext,
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: AppColors.blue,
|
||||
disabledBackgroundColor: AppColors.raised,
|
||||
@@ -775,7 +733,7 @@ class _PlaybackOverlayState extends State<_PlaybackOverlay> {
|
||||
const SizedBox(height: 12),
|
||||
PlayerFeaturedComment(
|
||||
drama: widget.drama,
|
||||
onViewAll: () => _run(widget.onComments),
|
||||
onViewAll: widget.onComments,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -800,7 +758,7 @@ class _PlaybackOverlayState extends State<_PlaybackOverlay> {
|
||||
key: const Key('player-back'),
|
||||
tooltip: widget.l10n.backToSeries,
|
||||
icon: Icons.chevron_left_rounded,
|
||||
onPressed: () => _run(widget.onBack!),
|
||||
onPressed: widget.onBack!,
|
||||
)
|
||||
else
|
||||
const SizedBox(width: 36),
|
||||
@@ -822,6 +780,13 @@ class _PlaybackOverlayState extends State<_PlaybackOverlay> {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_TopAction(
|
||||
key: const Key('player-clear-screen'),
|
||||
tooltip: widget.l10n.clearScreen,
|
||||
icon: Icons.fullscreen_rounded,
|
||||
onPressed: _clearScreen,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_TopAction(
|
||||
key: const Key('player-more'),
|
||||
tooltip: widget.l10n.more,
|
||||
@@ -907,7 +872,6 @@ class _PlaybackOverlayState extends State<_PlaybackOverlay> {
|
||||
),
|
||||
),
|
||||
);
|
||||
if (mounted) _reveal();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ class HomeFeedOverlay extends StatelessWidget {
|
||||
required this.onShare,
|
||||
required this.onEpisodes,
|
||||
required this.onDownload,
|
||||
required this.onClear,
|
||||
required this.onSearch,
|
||||
this.onPip,
|
||||
});
|
||||
@@ -41,6 +42,7 @@ class HomeFeedOverlay extends StatelessWidget {
|
||||
final VoidCallback onShare;
|
||||
final VoidCallback onEpisodes;
|
||||
final VoidCallback onDownload;
|
||||
final VoidCallback onClear;
|
||||
final VoidCallback onSearch;
|
||||
final VoidCallback? onPip;
|
||||
|
||||
@@ -96,6 +98,13 @@ class HomeFeedOverlay extends StatelessWidget {
|
||||
onPressed: onSearch,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_RoundIcon(
|
||||
key: const Key('home-feed-clear-screen'),
|
||||
tooltip: l10n.clearScreen,
|
||||
icon: Icons.fullscreen_rounded,
|
||||
onPressed: onClear,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_RoundIcon(
|
||||
key: const Key('home-feed-more'),
|
||||
tooltip: l10n.more,
|
||||
|
||||
@@ -101,6 +101,7 @@ void main() {
|
||||
onShare: () {},
|
||||
onEpisodes: () {},
|
||||
onDownload: () {},
|
||||
onClear: () {},
|
||||
onSearch: () {},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -5,16 +5,14 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:fruitshorts/pages/player/widgets/player_center_toggle.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('should_use_one_shared_52px_style_for_home_and_player', (
|
||||
tester,
|
||||
) async {
|
||||
testWidgets('should_use_one_shared_52px_center_toggle_style', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Row(
|
||||
children: [
|
||||
PlayerCenterToggle(
|
||||
key: const Key('home-feed-center-play'),
|
||||
key: const Key('test-center-toggle-a'),
|
||||
playing: false,
|
||||
onTap: () {},
|
||||
),
|
||||
@@ -30,7 +28,7 @@ void main() {
|
||||
);
|
||||
|
||||
for (final key in const [
|
||||
Key('home-feed-center-play'),
|
||||
Key('test-center-toggle-a'),
|
||||
Key('player-play-toggle'),
|
||||
]) {
|
||||
expect(tester.getSize(find.byKey(key)), const Size(52, 52));
|
||||
|
||||
@@ -9,7 +9,6 @@ import 'package:fruitshorts/app.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/favorite_repository.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/playback_repository.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
|
||||
import 'package:fruitshorts/pages/player/player_view.dart';
|
||||
import 'package:fruitshorts/pages/player/widgets/player_center_toggle.dart';
|
||||
|
||||
import '../support/fake_catalog.dart';
|
||||
@@ -70,7 +69,7 @@ void main() {
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('should_keep_single_pause_toggle_when_detail_player_is_paused', (
|
||||
testWidgets('should_hide_pause_toggle_when_playback_is_paused', (
|
||||
tester,
|
||||
) async {
|
||||
await _pumpApp(tester);
|
||||
@@ -86,12 +85,16 @@ void main() {
|
||||
await tester.tap(find.byKey(const Key('player-play-toggle')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('player-play-toggle')), findsNothing);
|
||||
expect(find.byKey(const Key('player-controls-top')), findsOneWidget);
|
||||
expect(find.byKey(const Key('player-controls-bottom')), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byKey(const Key('player-video-tap')));
|
||||
await tester.pump();
|
||||
expect(find.byKey(const Key('player-play-toggle')), findsOneWidget);
|
||||
expect(find.byKey(const Key('home-feed-center-play')), findsNothing);
|
||||
expect(find.byIcon(Icons.play_arrow_rounded), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should_hide_controls_after_2_5s_and_reveal_on_video_tap', (
|
||||
testWidgets('should_keep_controls_visible_until_clear_screen_is_tapped', (
|
||||
tester,
|
||||
) async {
|
||||
await _pumpApp(tester);
|
||||
@@ -108,7 +111,14 @@ void main() {
|
||||
expect(find.byKey(const Key('player-controls-bottom')), findsOneWidget);
|
||||
expect(find.byType(PlayerCenterToggle), findsOneWidget);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 2600));
|
||||
await tester.pump(const Duration(seconds: 5));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byKey(const Key('player-controls-top')), findsOneWidget);
|
||||
expect(find.byKey(const Key('player-controls-bottom')), findsOneWidget);
|
||||
expect(find.byType(PlayerCenterToggle), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byKey(const Key('player-clear-screen')));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byKey(const Key('player-controls-top')), findsNothing);
|
||||
@@ -124,39 +134,6 @@ void main() {
|
||||
expect(find.byType(PlayerCenterToggle), findsOneWidget);
|
||||
});
|
||||
|
||||
test('should_only_show_feed_pause_indicator_in_home_mode', () {
|
||||
expect(
|
||||
shouldShowFeedPauseIndicator(
|
||||
homeMode: true,
|
||||
selected: true,
|
||||
ready: true,
|
||||
playing: false,
|
||||
pip: false,
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
shouldShowFeedPauseIndicator(
|
||||
homeMode: false,
|
||||
selected: true,
|
||||
ready: true,
|
||||
playing: false,
|
||||
pip: false,
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
expect(
|
||||
shouldShowFeedPauseIndicator(
|
||||
homeMode: true,
|
||||
selected: true,
|
||||
ready: true,
|
||||
playing: true,
|
||||
pip: false,
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('should_fit_player_header_on_320_width', (tester) async {
|
||||
await _pumpApp(tester, size: const Size(320, 568));
|
||||
await tester.binding.handlePopRoute();
|
||||
@@ -174,6 +151,31 @@ void main() {
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('should_clear_and_restore_home_feed_chrome', (tester) async {
|
||||
await _pumpApp(tester);
|
||||
await tester.tap(find.byKey(const Key('tab-home')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
|
||||
expect(find.byKey(const Key('home-feed-info-card')), findsOneWidget);
|
||||
expect(find.byKey(const Key('home-feed-scrim')), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byKey(const Key('home-feed-clear-screen')));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byKey(const Key('home-feed-title')), findsNothing);
|
||||
expect(find.byKey(const Key('home-feed-info-card')), findsNothing);
|
||||
expect(find.byKey(const Key('home-feed-scrim')), findsNothing);
|
||||
expect(find.byKey(const Key('home-feed-video-tap')), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byKey(const Key('home-feed-video-tap')));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
|
||||
expect(find.byKey(const Key('home-feed-info-card')), findsOneWidget);
|
||||
expect(find.byKey(const Key('home-feed-scrim')), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
void _expectFullscreenOverlay(WidgetTester tester) {
|
||||
|
||||
Reference in new issue
Block a user