feat: add long press to shuffle with a wave animation (#888)

* feat: add long press to shuffle with a wave animation to singnify how long to press

* refactor: long-press shuffle cleanup

Follow-up on the long-press shuffle PR: shared hook/overlay, playback parity,
pointer events, broader surface coverage, locales, and tests.

* docs: credit ImAsra for long-press album shuffle (PR #888)

Add CHANGELOG entry and Settings credits for the hold-to-shuffle play
interaction shipped in Psychotoxical/psysonic#888.

* fix: restore playAlbumShuffled and long-press hook wiring

The follow-up merge dropped playAlbumShuffled and reverted the shared
long-press hook in album play buttons, breaking tsc and vitest on PR #888.

---------

Co-authored-by: cucadmuh <49571317+cucadmuh@users.noreply.github.com>
This commit is contained in:
ImAsra
2026-05-28 22:59:49 +02:00
committed by GitHub
parent 8443b3d4be
commit ae2e123a14
21 changed files with 585 additions and 44 deletions
+19 -10
View File
@@ -16,7 +16,9 @@ import { COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '../cover/layoutSizes';
import { resolveCoverDisplayTier } from '../cover/tiers';
import { acquireUrl } from '../utils/imageCache/urlPool';
import { OpenArtistRefInline } from './OpenArtistRefInline';
import { playAlbum } from '../utils/playback/playAlbum';
import { playAlbum, playAlbumShuffled } from '../utils/playback/playAlbum';
import { useLongPressAction } from '../hooks/useLongPressAction';
import { LongPressWaveOverlay } from './LongPressWaveOverlay';
import { useDragDrop } from '../contexts/DragDropContext';
import { isAlbumRecentlyAdded } from '../utils/albumRecency';
import { deriveAlbumArtistRefs } from '../utils/album/deriveAlbumHeaderArtistRefs';
@@ -59,6 +61,10 @@ function AlbumCard({
libraryResolve = false,
}: AlbumCardProps) {
const { t } = useTranslation();
const { isHolding, pressBind } = useLongPressAction({
onShortPress: () => playAlbum(album.id),
onLongPress: () => playAlbumShuffled(album.id),
});
const navigate = useNavigate();
const openContextMenu = usePlayerStore(s => s.openContextMenu);
const enqueue = usePlayerStore(s => s.enqueue);
@@ -157,15 +163,18 @@ function AlbumCard({
)}
{!selectionMode && (
<div className="album-card-play-overlay">
<button
className="album-card-details-btn"
onClick={e => { e.stopPropagation(); playAlbum(album.id); }}
aria-label={`${album.name} abspielen`}
data-tooltip={t('hero.playAlbum')}
data-tooltip-pos="top"
>
<Play size={15} fill="currentColor" />
</button>
<button
className="album-card-details-btn long-press-play-btn"
{...pressBind}
aria-label={`${t('hero.playAlbumTooltip')}${album.name}`}
data-tooltip={t('hero.playAlbumTooltip')}
data-tooltip-pos="top"
>
<LongPressWaveOverlay active={isHolding} />
<span className="long-press-play-btn__icon">
<Play size={15} fill="currentColor" />
</span>
</button>
<button
className="album-card-details-btn"
onClick={async e => {