feat: implement swipe gesture controls for mobile audio player and enhance theme color handling
This commit is contained in:
@@ -16,6 +16,13 @@ export const AudioPlayer: React.FC = () => {
|
||||
const { currentTrack, playPreviousTrack, addToQueue, playNextTrack, clearQueue, queue, toggleShuffle, shuffle, toggleCurrentTrackStar } = useAudioPlayer();
|
||||
const router = useRouter();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
// Swipe gesture state for mobile
|
||||
const [touchStart, setTouchStart] = useState<number | null>(null);
|
||||
const [touchEnd, setTouchEnd] = useState<number | null>(null);
|
||||
|
||||
// Minimum swipe distance (in px)
|
||||
const minSwipeDistance = 50;
|
||||
const audioRef = useRef<HTMLAudioElement>(null);
|
||||
const preloadAudioRef = useRef<HTMLAudioElement>(null);
|
||||
const [progress, setProgress] = useState(0);
|
||||
@@ -29,6 +36,32 @@ export const AudioPlayer: React.FC = () => {
|
||||
const audioCurrent = audioRef.current;
|
||||
const { toast } = useToast();
|
||||
|
||||
// Swipe gesture handlers for mobile
|
||||
const handleTouchStart = (e: React.TouchEvent) => {
|
||||
setTouchEnd(null);
|
||||
setTouchStart(e.targetTouches[0].clientX);
|
||||
};
|
||||
|
||||
const handleTouchMove = (e: React.TouchEvent) => {
|
||||
setTouchEnd(e.targetTouches[0].clientX);
|
||||
};
|
||||
|
||||
const handleTouchEnd = () => {
|
||||
if (!touchStart || !touchEnd) return;
|
||||
|
||||
const distance = touchStart - touchEnd;
|
||||
const isLeftSwipe = distance > minSwipeDistance;
|
||||
const isRightSwipe = distance < -minSwipeDistance;
|
||||
|
||||
if (isLeftSwipe) {
|
||||
// Swipe left -> next track
|
||||
playNextTrack();
|
||||
} else if (isRightSwipe) {
|
||||
// Swipe right -> previous track
|
||||
playPreviousTrack();
|
||||
}
|
||||
};
|
||||
|
||||
// Last.fm scrobbler integration (Navidrome)
|
||||
const {
|
||||
onTrackStart: navidromeOnTrackStart,
|
||||
@@ -531,10 +564,13 @@ export const AudioPlayer: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Track info */}
|
||||
{/* Track info with swipe gestures */}
|
||||
<div
|
||||
className="flex items-center flex-1 min-w-0 cursor-pointer"
|
||||
onClick={() => setIsFullScreen(true)}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchMove={handleTouchMove}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
>
|
||||
<Image
|
||||
src={currentTrack.coverArt || '/default-user.jpg'}
|
||||
@@ -549,7 +585,7 @@ export const AudioPlayer: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile controls */}
|
||||
{/* Mobile controls - Only heart and play/pause */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
className="p-3 hover:bg-muted/50 rounded-full transition-all duration-200 active:scale-95 touch-manipulation"
|
||||
@@ -565,14 +601,6 @@ export const AudioPlayer: React.FC = () => {
|
||||
className={`w-4 h-4 ${currentTrack.starred ? 'text-primary fill-primary' : ''}`}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="p-3 hover:bg-muted/50 rounded-full transition-all duration-200 active:scale-95 touch-manipulation"
|
||||
onClick={playPreviousTrack}
|
||||
type="button"
|
||||
aria-label="Previous track"
|
||||
>
|
||||
<FaBackward className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
className="p-4 hover:bg-muted/50 rounded-full transition-all duration-200 active:scale-95 bg-primary/10 touch-manipulation"
|
||||
onClick={togglePlayPause}
|
||||
@@ -585,14 +613,6 @@ export const AudioPlayer: React.FC = () => {
|
||||
>
|
||||
{isPlaying ? <FaPause className="w-5 h-5" /> : <FaPlay className="w-5 h-5" />}
|
||||
</button>
|
||||
<button
|
||||
className="p-3 hover:bg-muted/50 rounded-full transition-all duration-200 active:scale-95 touch-manipulation"
|
||||
onClick={playNextTrack}
|
||||
type="button"
|
||||
aria-label="Next track"
|
||||
>
|
||||
<FaForward className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,8 @@ import { PostHogProvider } from "../components/PostHogProvider";
|
||||
import { WhatsNewPopup } from "../components/WhatsNewPopup";
|
||||
import Ihateserverside from "./ihateserverside";
|
||||
import DynamicViewportTheme from "./DynamicViewportTheme";
|
||||
import ThemeColorHandler from "./ThemeColorHandler";
|
||||
import { useViewportThemeColor } from "@/hooks/use-viewport-theme-color";
|
||||
import { LoginForm } from "./start-screen";
|
||||
import Image from "next/image";
|
||||
|
||||
@@ -83,6 +85,7 @@ export default function RootLayoutClient({ children }: { children: React.ReactNo
|
||||
<PostHogProvider>
|
||||
<ThemeProvider>
|
||||
<DynamicViewportTheme />
|
||||
<ThemeColorHandler />
|
||||
<NavidromeConfigProvider>
|
||||
<NavidromeProvider>
|
||||
<NavidromeErrorBoundary>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { Song } from '@/lib/navidrome';
|
||||
import { Song, Album } from '@/lib/navidrome';
|
||||
import { useNavidrome } from '@/app/components/NavidromeContext';
|
||||
import { useAudioPlayer } from '@/app/components/AudioPlayerContext';
|
||||
import { useIsMobile } from '@/hooks/use-mobile';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Play, Heart, Music, Shuffle } from 'lucide-react';
|
||||
|
||||
8
app/components/ThemeColorHandler.tsx
Normal file
8
app/components/ThemeColorHandler.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { useViewportThemeColor } from '@/hooks/use-viewport-theme-color';
|
||||
|
||||
export default function ThemeColorHandler() {
|
||||
useViewportThemeColor();
|
||||
return null;
|
||||
}
|
||||
@@ -88,6 +88,18 @@
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
/* Hide scrollbars on mobile */
|
||||
@media (max-width: 768px) {
|
||||
* {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* Internet Explorer 10+ */
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
display: none; /* Safari and Chrome */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
|
||||
Reference in New Issue
Block a user