merge: 恢复进度拖动前浮层状态

This commit is contained in:
Zhou Weidong committed 2026-10-22 20:08:53 +08:00
commit afe7a49ef0
7 files changed
+47 -43

No files matched your search

+1 -1
View File
@@ -1184,5 +1184,5 @@ $ flutter test
- 剧名 19/w600,箭头 20 白色并紧邻剧名;标签行改为 13/w600 亮灰内联布局。 - 剧名 19/w600,箭头 20 白色并紧邻剧名;标签行改为 13/w600 亮灰内联布局。
- 简介「展开/收起」沿用现有三语文案;进度条热区恢复 24 并接入拖动自动清屏与预览卡片。 - 简介「展开/收起」沿用现有三语文案;进度条热区恢复 24 并接入拖动自动清屏与预览卡片。
- 拖动期间隐藏操作栏、文字及根底栏,顶部导航常驻;松手后持续清屏直到点击画面。 - 拖动期间隐藏操作栏、文字及根底栏,顶部导航常驻;松手后持续清屏直到点击画面。
- 进度条固定中心并动画到 6 高,时间气泡限制在左右 12;按当前流程未执行 analyze/test。 - 进度条固定中心并动画到 6 高,时间气泡限制在左右 12;松手恢复拖动前浮层状态。
- 修复 `scrubCancelRequest` 误置于 `initState` 的编译错误,并清理推荐页未使用变量;`flutter analyze` 无问题。 - 修复 `scrubCancelRequest` 误置于 `initState` 的编译错误,并清理推荐页未使用变量;`flutter analyze` 无问题。
+1
View File
@@ -18,6 +18,7 @@ abstract final class AppColors {
static const scrim = Color(0xD1000000); static const scrim = Color(0xD1000000);
static const controlScrim = Color(0x80000000); static const controlScrim = Color(0x80000000);
static const controlSoftScrim = Color(0x42000000); static const controlSoftScrim = Color(0x42000000);
static const timeBubbleScrim = Color(0xB3000000);
static const scrubPreviewScrim = Color(0xDE000000); static const scrubPreviewScrim = Color(0xDE000000);
static const overlaySoft = Color(0x66000000); static const overlaySoft = Color(0x66000000);
static const overlayBar = Color(0x4D000000); static const overlayBar = Color(0x4D000000);
+4 -11
View File
@@ -119,7 +119,6 @@ class _PlayerPageState extends State<PlayerPage>
double _playbackSpeed = 1.0; double _playbackSpeed = 1.0;
bool _boosting = false; bool _boosting = false;
bool _scrubbing = false; bool _scrubbing = false;
bool _clearScreenFromScrub = false;
double? _speedBeforeBoost; double? _speedBeforeBoost;
bool? _chromeBeforeBoost; bool? _chromeBeforeBoost;
@@ -315,26 +314,24 @@ class _PlayerPageState extends State<PlayerPage>
widget.onScrubVisibilityChanged?.call(value); widget.onScrubVisibilityChanged?.call(value);
} }
void _finishFeedScrub() { void _finishFeedScrub(bool wasVisible) {
setState(() { setState(() {
_scrubbing = false; _scrubbing = false;
_chromeVisible = false; _chromeVisible = wasVisible;
_clearScreenFromScrub = true;
}); });
widget.onScrubVisibilityChanged?.call(!wasVisible);
} }
void _cancelFeedScrub() { void _cancelFeedScrub() {
logic.cancelScrub(); logic.cancelScrub();
_scrubbing = false; _scrubbing = false;
_chromeVisible = true; _chromeVisible = true;
_clearScreenFromScrub = false;
} }
void _restoreFeedChrome() { void _restoreFeedChrome() {
setState(() { setState(() {
_scrubbing = false; _scrubbing = false;
_chromeVisible = true; _chromeVisible = true;
_clearScreenFromScrub = false;
}); });
widget.onScrubVisibilityChanged?.call(false); widget.onScrubVisibilityChanged?.call(false);
} }
@@ -749,11 +746,7 @@ class _PlayerPageState extends State<PlayerPage>
? null ? null
: () => _openUnlockSheet(context, index, episode), : () => _openUnlockSheet(context, index, episode),
), ),
if (widget.homeMode && if (widget.homeMode && !_chromeVisible && !_boosting && !_scrubbing)
!_chromeVisible &&
!_boosting &&
!_scrubbing &&
!_clearScreenFromScrub)
Positioned( Positioned(
right: 12, right: 12,
bottom: 56, bottom: 56,
@@ -89,7 +89,7 @@ class HomeFeedOverlay extends StatefulWidget {
final VoidCallback onBoostEnd; final VoidCallback onBoostEnd;
final VoidCallback onMore; final VoidCallback onMore;
final VoidCallback? onScrubStart; final VoidCallback? onScrubStart;
final VoidCallback? onScrubEnd; final ValueChanged<bool>? onScrubEnd;
final ValueChanged<bool>? onScrubVisibilityChanged; final ValueChanged<bool>? onScrubVisibilityChanged;
final ValueChanged<Duration>? onSeek; final ValueChanged<Duration>? onSeek;
final int cancelScrubRequest; final int cancelScrubRequest;
@@ -102,14 +102,13 @@ class HomeFeedOverlay extends StatefulWidget {
class _HomeFeedOverlayState extends State<HomeFeedOverlay> { class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
bool _scrubbing = false; bool _scrubbing = false;
bool _clearAfterScrub = false; bool _wasVisibleBeforeScrub = true;
@override @override
void didUpdateWidget(covariant HomeFeedOverlay oldWidget) { void didUpdateWidget(covariant HomeFeedOverlay oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (oldWidget.cancelScrubRequest != widget.cancelScrubRequest) { if (oldWidget.cancelScrubRequest != widget.cancelScrubRequest) {
_scrubbing = false; _scrubbing = false;
_clearAfterScrub = false;
} }
} }
@@ -138,7 +137,7 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
), ),
if (widget.boosting) if (widget.boosting)
Positioned(left: 0, right: 0, bottom: 20, child: _boostHint()) Positioned(left: 0, right: 0, bottom: 20, child: _boostHint())
else if (widget.visible && !_scrubbing && !_clearAfterScrub) else if (widget.visible && !_scrubbing)
Positioned( Positioned(
left: _pageHorizontal, left: _pageHorizontal,
right: _railRight, right: _railRight,
@@ -176,31 +175,19 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
} }
void _startScrub() { void _startScrub() {
if (!_scrubbing || _clearAfterScrub) { _wasVisibleBeforeScrub = widget.visible;
setState(() { if (!_scrubbing) setState(() => _scrubbing = true);
_scrubbing = true;
_clearAfterScrub = false;
});
}
widget.onScrubVisibilityChanged?.call(true); widget.onScrubVisibilityChanged?.call(true);
widget.onScrubStart?.call(); widget.onScrubStart?.call();
} }
void _endScrub() { void _endScrub() {
if (_scrubbing || !_clearAfterScrub) { final wasVisible = _wasVisibleBeforeScrub;
setState(() { if (_scrubbing) setState(() => _scrubbing = false);
_scrubbing = false; widget.onScrubEnd?.call(wasVisible);
_clearAfterScrub = true;
});
}
widget.onScrubEnd?.call();
} }
void _handleSurfaceTap() { void _handleSurfaceTap() {
if (_clearAfterScrub) {
setState(() => _clearAfterScrub = false);
widget.onScrubVisibilityChanged?.call(false);
}
widget.onTogglePlay(); widget.onTogglePlay();
} }
@@ -150,7 +150,7 @@ class _FeedProgressBarState extends State<_FeedProgressBar> {
if (showPreview) if (showPreview)
Positioned( Positioned(
left: _timeBubbleLeft(width, _preview!), left: _timeBubbleLeft(width, _preview!),
bottom: widget.hitHeight + 8, bottom: _lineCenterFromBottom + 16,
child: _ScrubTimeBubble( child: _ScrubTimeBubble(
key: const Key('scrub-preview-time-bubble'), key: const Key('scrub-preview-time-bubble'),
position: previewPosition, position: previewPosition,
@@ -377,9 +377,9 @@ class _ScrubTimeBubble extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
width: _timeBubbleWidth, width: _timeBubbleWidth,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.overlayMedium, color: AppColors.timeBubbleScrim,
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
child: Text( child: Text(
+24 -7
View File
@@ -719,6 +719,28 @@ void main() {
tester.getRect(find.byKey(const Key('home-feed-gesture'))).right - 12, tester.getRect(find.byKey(const Key('home-feed-gesture'))).right - 12,
), ),
); );
expect(
tester
.getRect(find.byKey(const Key('home-feed-progress-track')))
.center
.dy -
bubbleRect.bottom,
closeTo(16, 0.1),
);
final bubbleContainer = tester.widget<Container>(
find.descendant(
of: find.byKey(const Key('scrub-preview-time-bubble')),
matching: find.byType(Container),
),
);
expect(
(bubbleContainer.decoration! as BoxDecoration).color,
AppColors.timeBubbleScrim,
);
expect(
bubbleContainer.padding,
const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
);
await gesture.moveBy(const Offset(-600, 0)); await gesture.moveBy(const Offset(-600, 0));
await tester.pump(); await tester.pump();
expect( expect(
@@ -765,8 +787,8 @@ void main() {
); );
await gesture.up(); await gesture.up();
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(scrubVisibility.last, isTrue); expect(scrubVisibility.last, isFalse);
expect(find.byKey(const Key('home-feed-title')), findsNothing); expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
expect(seeked, isNotNull); expect(seeked, isNotNull);
expect(seeked!.inSeconds, greaterThan(0)); expect(seeked!.inSeconds, greaterThan(0));
expect( expect(
@@ -778,11 +800,6 @@ void main() {
2, 2,
); );
await tester.tap(find.byKey(const Key('home-feed-gesture')));
await tester.pump();
expect(scrubVisibility.last, isFalse);
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
await tester.tap(find.byKey(const Key('home-feed-full-series'))); await tester.tap(find.byKey(const Key('home-feed-full-series')));
await tester.pump(); await tester.pump();
expect(opened, 1); expect(opened, 1);
@@ -102,9 +102,15 @@ void main() {
await tester.pump(const Duration(milliseconds: 130)); await tester.pump(const Duration(milliseconds: 130));
expect(find.byKey(const Key('recommend-top-nav')), findsOneWidget); expect(find.byKey(const Key('recommend-top-nav')), findsOneWidget);
expect(tester.getCenter(progress).dy, closeTo(progressCenterY, 0.1)); expect(tester.getCenter(progress).dy, closeTo(progressCenterY, 0.1));
final navVisibility = find.ancestor(
of: find.byKey(const Key('home-tabs-bar')),
matching: find.byType(Visibility),
);
expect(tester.widget<Visibility>(navVisibility.first).visible, isFalse);
await scrub.up(); await scrub.up();
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.byKey(const Key('recommend-top-nav')), findsOneWidget); expect(find.byKey(const Key('recommend-top-nav')), findsOneWidget);
expect(tester.widget<Visibility>(navVisibility.first).visible, isTrue);
await tester.tap(find.byKey(const Key('recommend-tab-playlist'))); await tester.tap(find.byKey(const Key('recommend-tab-playlist')));
await tester.pumpAndSettle(); await tester.pumpAndSettle();