fix(player): 调整双击飘心动画

This commit is contained in:
Zhou Weidong committed 2026-10-26 18:11:01 +08:00
1 parent 63296d2a92
commit bea50ab32f
8 files changed
+52 -20

No files matched your search

+3
View File
@@ -639,6 +639,7 @@ class _PlayerPageState extends State<PlayerPage>
); );
return Stack( return Stack(
fit: StackFit.expand, fit: StackFit.expand,
clipBehavior: Clip.none,
children: [ children: [
PlayerRenderStack( PlayerRenderStack(
pageController: _pageController, pageController: _pageController,
@@ -662,6 +663,7 @@ class _PlayerPageState extends State<PlayerPage>
); );
final body = Stack( final body = Stack(
fit: StackFit.expand, fit: StackFit.expand,
clipBehavior: Clip.none,
children: [ children: [
content, content,
if (widget.homeMode && if (widget.homeMode &&
@@ -971,6 +973,7 @@ class _PlayerPageState extends State<PlayerPage>
return Stack( return Stack(
fit: StackFit.expand, fit: StackFit.expand,
clipBehavior: Clip.none,
children: [ children: [
if (selected && if (selected &&
!entryLoading && !entryLoading &&
@@ -126,6 +126,7 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
return SafeArea( return SafeArea(
bottom: false, bottom: false,
child: Stack( child: Stack(
clipBehavior: Clip.none,
children: [ children: [
Positioned.fill( Positioned.fill(
child: PlayerTapGestureRegion( child: PlayerTapGestureRegion(
@@ -4,31 +4,32 @@ import 'package:flutter/material.dart';
import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_colors.dart';
const int _heartDurationMs = 680; const int _heartDurationMs = 1300;
const int _lastHeartDelayMs = 200;
const double _heartPopPortion = 0.18; const double _heartPopPortion = 0.18;
const double _heartStartScale = 0.7; const double _heartStartScale = 0.7;
const double _heartHoldPortion = 0.45; const double _heartHoldPortion = 0.65;
const List<_HeartParticle> _hearts = [ const List<_HeartParticle> _hearts = [
_HeartParticle( _HeartParticle(
delay: 0, delay: 0,
travel: 108, travel: 175,
drift: 0, drift: 0,
size: 48, size: 44,
color: AppColors.doubleTapHeartRed, color: AppColors.doubleTapHeartRed,
), ),
_HeartParticle( _HeartParticle(
delay: 50, delay: 110,
travel: 96, travel: 150,
drift: -28, drift: -32,
size: 36, size: 34,
color: AppColors.doubleTapHeartPink, color: AppColors.doubleTapHeartPink,
), ),
_HeartParticle( _HeartParticle(
delay: 90, delay: _lastHeartDelayMs,
travel: 118, travel: 165,
drift: 26, drift: 30,
size: 40, size: 38,
color: AppColors.doubleTapHeartRed, color: AppColors.doubleTapHeartRed,
), ),
]; ];
@@ -38,6 +39,10 @@ class PlayerDoubleTapHeartBurst extends StatefulWidget {
const PlayerDoubleTapHeartBurst({super.key, required this.onCompleted}); const PlayerDoubleTapHeartBurst({super.key, required this.onCompleted});
static const int particleCount = 3; static const int particleCount = 3;
static const int singleHeartDurationMs = _heartDurationMs;
static const Duration animationDuration = Duration(
milliseconds: _lastHeartDelayMs + _heartDurationMs,
);
final VoidCallback onCompleted; final VoidCallback onCompleted;
@@ -55,6 +55,7 @@ class PlayerPlaybackOverlay extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea( return SafeArea(
child: Stack( child: Stack(
clipBehavior: Clip.none,
children: [ children: [
Positioned.fill( Positioned.fill(
child: PlayerTapGestureRegion( child: PlayerTapGestureRegion(
@@ -70,6 +70,7 @@ class _PlayerTapGestureRegionState extends State<PlayerTapGestureRegion> {
onPointerDown: _handlePointerDown, onPointerDown: _handlePointerDown,
child: Stack( child: Stack(
fit: StackFit.expand, fit: StackFit.expand,
clipBehavior: Clip.none,
children: [ children: [
GestureDetector( GestureDetector(
key: widget.gestureKey, key: widget.gestureKey,
+1 -1
View File
@@ -91,7 +91,7 @@ void main() {
await tester.tapAt(center); await tester.tapAt(center);
await tester.pump(); await tester.pump();
expect(repository.likeTargets, hasLength(1)); expect(repository.likeTargets, hasLength(1));
await tester.pump(const Duration(milliseconds: 800)); await tester.pump(const Duration(milliseconds: 1600));
clock.advance(const Duration(milliseconds: 500)); clock.advance(const Duration(milliseconds: 500));
await tester.pump(const Duration(milliseconds: 500)); await tester.pump(const Duration(milliseconds: 500));
+25 -4
View File
@@ -13,12 +13,33 @@ void main() {
home: Scaffold(body: PlayerDoubleTapHeartBurst(onCompleted: _noop)), home: Scaffold(body: PlayerDoubleTapHeartBurst(onCompleted: _noop)),
), ),
); );
await tester.pump(const Duration(milliseconds: 160)); await tester.pump(const Duration(milliseconds: 300));
expect(find.byIcon(Icons.favorite_rounded), findsNWidgets(3)); expect(find.byIcon(Icons.favorite_rounded), findsNWidgets(3));
expect(_size(tester, 0), 48); expect(_size(tester, 0), 44);
expect(_size(tester, 1), 36); expect(_size(tester, 1), 34);
expect(_size(tester, 2), 40); expect(_size(tester, 2), 38);
expect(
tester
.widgetList<Stack>(
find.descendant(
of: find.byType(PlayerDoubleTapHeartBurst),
matching: find.byType(Stack),
),
)
.every((stack) => stack.clipBehavior == Clip.none),
isTrue,
);
});
test('heart burst stays long and sparse', () {
expect(PlayerDoubleTapHeartBurst.singleHeartDurationMs, 1300);
expect(
PlayerDoubleTapHeartBurst.animationDuration,
greaterThanOrEqualTo(const Duration(milliseconds: 1200)),
);
expect(PlayerDoubleTapHeartBurst.particleCount, lessThanOrEqualTo(4));
expect(PlayerDoubleTapHeartBurst.particleCount, 3);
}); });
} }
+3 -3
View File
@@ -679,7 +679,7 @@ void main() {
await tester.tapAt(position); await tester.tapAt(position);
} }
await tester.pump(); await tester.pump();
await tester.pump(const Duration(milliseconds: 100)); await tester.pump(const Duration(milliseconds: 300));
expect(logic.session.userWantsPlay, isTrue); expect(logic.session.userWantsPlay, isTrue);
expect(find.byKey(const Key('player-play-toggle')), findsNothing); expect(find.byKey(const Key('player-play-toggle')), findsNothing);
@@ -699,7 +699,7 @@ void main() {
AppColors.doubleTapHeartRed, AppColors.doubleTapHeartRed,
); );
await tester.pump(const Duration(milliseconds: 800)); await tester.pump(const Duration(milliseconds: 1600));
expect(find.byKey(const Key('double-tap-heart-0')), findsNothing); expect(find.byKey(const Key('double-tap-heart-0')), findsNothing);
expect(logic.session.userWantsPlay, isTrue); expect(logic.session.userWantsPlay, isTrue);
}); });
@@ -722,7 +722,7 @@ void main() {
await tester.tapAt(const Offset(80, 300)); await tester.tapAt(const Offset(80, 300));
await tester.tapAt(const Offset(80, 300)); await tester.tapAt(const Offset(80, 300));
await tester.pump(); await tester.pump();
await tester.pump(const Duration(milliseconds: 100)); await tester.pump(const Duration(milliseconds: 300));
expect(logic.session.userWantsPlay, isTrue); expect(logic.session.userWantsPlay, isTrue);
expect(interactions.likeTargets, hasLength(1)); expect(interactions.likeTargets, hasLength(1));