Skip to content

Commit 8f8d82a

Browse files
author
Dhemas Nurjaya
committed
fix: address CodeRabbit PR #103 review feedback for swipe gesture
- Guard haptic feedback with _previewSong != null to prevent false vibration at queue boundaries (first/last song) - Use skipToIndex(targetIndex) instead of skipPrevious() to avoid rewind-instead-of-skip when playback position exceeds 3 seconds - Drive carousel visibility from hasPreviewSong instead of artwork URL so songs without cover art show a placeholder instead of disabling the swipe animation entirely
1 parent 7e4c9e5 commit 8f8d82a

1 file changed

Lines changed: 42 additions & 36 deletions

File tree

lib/screens/now_playing_screen.dart

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ class _NowPlayingScreenState extends State<NowPlayingScreen>
125125
_horizontalDragOffset += details.delta.dx;
126126
_updatePreviewSong();
127127
});
128-
if (_swipeProgress >= 1.0 && !_hasTriggeredHaptic) {
128+
if (_previewSong != null && _swipeProgress >= 1.0 && !_hasTriggeredHaptic) {
129129
_hasTriggeredHaptic = true;
130130
HapticFeedback.lightImpact();
131131
}
@@ -138,16 +138,22 @@ class _NowPlayingScreenState extends State<NowPlayingScreen>
138138
final velocity = details.primaryVelocity ?? 0;
139139
final provider = context.read<PlayerProvider>();
140140

141-
final shouldSkipNext = (_horizontalDragOffset < -_swipeThreshold ||
141+
final shouldSkipNext =
142+
(_horizontalDragOffset < -_swipeThreshold ||
142143
velocity < -_swipeVelocityThreshold) &&
143144
provider.hasNext;
144-
final shouldSkipPrevious = (_horizontalDragOffset > _swipeThreshold ||
145+
final shouldSkipPrevious =
146+
(_horizontalDragOffset > _swipeThreshold ||
145147
velocity > _swipeVelocityThreshold) &&
146148
provider.hasPrevious;
147149

148150
if (shouldSkipNext || shouldSkipPrevious) {
151+
final targetIndex = shouldSkipNext
152+
? provider.currentIndex + 1
153+
: provider.currentIndex - 1;
149154
_animateSwipeCompletion(
150155
goNext: shouldSkipNext,
156+
targetIndex: targetIndex,
151157
provider: provider,
152158
flingVelocity: velocity.abs(),
153159
);
@@ -158,6 +164,7 @@ class _NowPlayingScreenState extends State<NowPlayingScreen>
158164

159165
void _animateSwipeCompletion({
160166
required bool goNext,
167+
required int targetIndex,
161168
required PlayerProvider provider,
162169
required double flingVelocity,
163170
}) {
@@ -174,13 +181,9 @@ class _NowPlayingScreenState extends State<NowPlayingScreen>
174181

175182
_swipeAnimationController.duration = Duration(milliseconds: durationMs);
176183
_swipeAnimationController.reset();
177-
final animation = Tween<double>(
178-
begin: startOffset,
179-
end: endOffset,
180-
).animate(CurvedAnimation(
181-
parent: _swipeAnimationController,
182-
curve: Curves.easeOut,
183-
));
184+
final animation = Tween<double>(begin: startOffset, end: endOffset).animate(
185+
CurvedAnimation(parent: _swipeAnimationController, curve: Curves.easeOut),
186+
);
184187

185188
void listener() {
186189
if (!mounted) return;
@@ -199,11 +202,7 @@ class _NowPlayingScreenState extends State<NowPlayingScreen>
199202
_previewSong = null;
200203
_isSwipeAnimating = false;
201204
});
202-
if (goNext) {
203-
provider.skipNext();
204-
} else {
205-
provider.skipPrevious();
206-
}
205+
provider.skipToIndex(targetIndex);
207206
});
208207
}
209208

@@ -217,13 +216,12 @@ class _NowPlayingScreenState extends State<NowPlayingScreen>
217216

218217
_swipeAnimationController.duration = Duration(milliseconds: durationMs);
219218
_swipeAnimationController.reset();
220-
final animation = Tween<double>(
221-
begin: startOffset,
222-
end: 0.0,
223-
).animate(CurvedAnimation(
224-
parent: _swipeAnimationController,
225-
curve: Curves.easeOutQuad,
226-
));
219+
final animation = Tween<double>(begin: startOffset, end: 0.0).animate(
220+
CurvedAnimation(
221+
parent: _swipeAnimationController,
222+
curve: Curves.easeOutQuad,
223+
),
224+
);
227225

228226
void listener() {
229227
if (!mounted) return;
@@ -304,11 +302,7 @@ class _NowPlayingScreenState extends State<NowPlayingScreen>
304302
duration: animDuration,
305303
curve: animCurve,
306304
transform: Matrix4.identity()
307-
..setTranslationRaw(
308-
0.0,
309-
-_morphProgress * 10,
310-
0.0,
311-
)
305+
..setTranslationRaw(0.0, -_morphProgress * 10, 0.0)
312306
..scaleByDouble(
313307
1.0 + _morphProgress * 0.03,
314308
1.0 + _morphProgress * 0.03,
@@ -320,6 +314,7 @@ class _NowPlayingScreenState extends State<NowPlayingScreen>
320314
currentImageUrl: _cachedImageUrl ?? '',
321315
currentThumbnailUrl: _cachedThumbnailUrl,
322316
previewImageUrl: _getPreviewArtworkUrl(_previewSong),
317+
hasPreviewSong: _previewSong != null,
323318
size: artworkSize,
324319
swipeProgress: _swipeProgress,
325320
horizontalDragOffset: _horizontalDragOffset,
@@ -607,9 +602,12 @@ class _NowPlayingScreenState extends State<NowPlayingScreen>
607602
_getPreviewArtworkUrl(
608603
_previewSong,
609604
),
605+
hasPreviewSong:
606+
_previewSong != null,
610607
size: artworkSize,
611608
swipeProgress: _swipeProgress,
612-
horizontalDragOffset: _horizontalDragOffset,
609+
horizontalDragOffset:
610+
_horizontalDragOffset,
613611
),
614612
),
615613

@@ -680,7 +678,8 @@ class _NowPlayingScreenState extends State<NowPlayingScreen>
680678
}
681679

682680
Widget _buildRadioPlayer(BuildContext context, RadioStation station) {
683-
final animDuration = (_isDragging || _isHorizontalDragging || _isSwipeAnimating)
681+
final animDuration =
682+
(_isDragging || _isHorizontalDragging || _isSwipeAnimating)
684683
? Duration.zero
685684
: const Duration(milliseconds: 300);
686685
final animCurve = Curves.easeOutCubic;
@@ -1371,6 +1370,7 @@ class _SwipeableAlbumArtwork extends StatelessWidget {
13711370
final String currentImageUrl;
13721371
final String? currentThumbnailUrl;
13731372
final String? previewImageUrl;
1373+
final bool hasPreviewSong;
13741374
final double size;
13751375
final double swipeProgress;
13761376
final double horizontalDragOffset;
@@ -1379,16 +1379,15 @@ class _SwipeableAlbumArtwork extends StatelessWidget {
13791379
required this.currentImageUrl,
13801380
this.currentThumbnailUrl,
13811381
this.previewImageUrl,
1382+
this.hasPreviewSong = false,
13821383
required this.size,
13831384
required this.swipeProgress,
13841385
required this.horizontalDragOffset,
13851386
});
13861387

13871388
@override
13881389
Widget build(BuildContext context) {
1389-
final hasPreview = previewImageUrl != null &&
1390-
previewImageUrl!.isNotEmpty &&
1391-
horizontalDragOffset != 0;
1390+
final hasPreview = hasPreviewSong && horizontalDragOffset != 0;
13921391

13931392
final isSwipingRight = horizontalDragOffset > 0;
13941393
final previewStart = isSwipingRight
@@ -1406,7 +1405,10 @@ class _SwipeableAlbumArtwork extends StatelessWidget {
14061405

14071406
// Fade based on distance from center
14081407
final totalDistance = size + _kCarouselGap;
1409-
final progress = (horizontalDragOffset.abs() / totalDistance).clamp(0.0, 1.0);
1408+
final progress = (horizontalDragOffset.abs() / totalDistance).clamp(
1409+
0.0,
1410+
1.0,
1411+
);
14101412
final currentOpacity = (1.0 - progress * 0.5).clamp(0.5, 1.0);
14111413
final previewOpacity = (progress * 0.5 + 0.5).clamp(0.5, 1.0);
14121414

@@ -1437,6 +1439,8 @@ class _SwipeableAlbumArtwork extends StatelessWidget {
14371439
}
14381440

14391441
Widget _buildPreviewArtwork(BuildContext context) {
1442+
final hasArtwork = previewImageUrl != null && previewImageUrl!.isNotEmpty;
1443+
14401444
return Container(
14411445
width: size,
14421446
height: size,
@@ -1452,7 +1456,9 @@ class _SwipeableAlbumArtwork extends StatelessWidget {
14521456
),
14531457
child: ClipRRect(
14541458
borderRadius: BorderRadius.circular(12),
1455-
child: isLocalFilePath(previewImageUrl)
1459+
child: !hasArtwork
1460+
? _buildNoArtPlaceholder(context)
1461+
: isLocalFilePath(previewImageUrl)
14561462
? Image.file(
14571463
File(previewImageUrl!),
14581464
key: ValueKey(previewImageUrl),
@@ -1470,8 +1476,8 @@ class _SwipeableAlbumArtwork extends StatelessWidget {
14701476
useOldImageOnUrlChange: true,
14711477
fadeInDuration: Duration.zero,
14721478
fadeOutDuration: Duration.zero,
1473-
placeholder: (_, __) => _buildPlaceholder(),
1474-
errorWidget: (_, __, ___) => _buildNoArtPlaceholder(context),
1479+
placeholder: (_, _) => _buildPlaceholder(),
1480+
errorWidget: (_, _, _) => _buildNoArtPlaceholder(context),
14751481
),
14761482
),
14771483
);

0 commit comments

Comments
 (0)