feat: 3-dot menu open song options

This commit is contained in:
Fredrik Burmester
2026-01-06 16:03:25 +01:00
parent 055357de60
commit 99c9caf03c

View File

@@ -1,4 +1,5 @@
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
import type { import type {
BaseItemDto, BaseItemDto,
MediaSourceInfo, MediaSourceInfo,
@@ -24,6 +25,9 @@ import { useSharedValue } from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Badge } from "@/components/Badge"; import { Badge } from "@/components/Badge";
import { Text } from "@/components/common/Text"; import { Text } from "@/components/common/Text";
import { CreatePlaylistModal } from "@/components/music/CreatePlaylistModal";
import { PlaylistPickerSheet } from "@/components/music/PlaylistPickerSheet";
import { TrackOptionsSheet } from "@/components/music/TrackOptionsSheet";
import { apiAtom } from "@/providers/JellyfinProvider"; import { apiAtom } from "@/providers/JellyfinProvider";
import { import {
type RepeatMode, type RepeatMode,
@@ -55,6 +59,9 @@ export default function NowPlayingScreen() {
const router = useRouter(); const router = useRouter();
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const [viewMode, setViewMode] = useState<ViewMode>("player"); const [viewMode, setViewMode] = useState<ViewMode>("player");
const [trackOptionsOpen, setTrackOptionsOpen] = useState(false);
const [playlistPickerOpen, setPlaylistPickerOpen] = useState(false);
const [createPlaylistOpen, setCreatePlaylistOpen] = useState(false);
const { const {
currentTrack, currentTrack,
@@ -134,6 +141,18 @@ export default function NowPlayingScreen() {
setRepeatMode(nextMode); setRepeatMode(nextMode);
}, [repeatMode, setRepeatMode]); }, [repeatMode, setRepeatMode]);
const handleOptionsPress = useCallback(() => {
setTrackOptionsOpen(true);
}, []);
const handleAddToPlaylist = useCallback(() => {
setPlaylistPickerOpen(true);
}, []);
const handleCreateNewPlaylist = useCallback(() => {
setCreatePlaylistOpen(true);
}, []);
const getRepeatIcon = (): string => { const getRepeatIcon = (): string => {
switch (repeatMode) { switch (repeatMode) {
case "one": case "one":
@@ -150,108 +169,136 @@ export default function NowPlayingScreen() {
if (!currentTrack) { if (!currentTrack) {
return ( return (
<BottomSheetModalProvider>
<View
className='flex-1 bg-[#121212] items-center justify-center'
style={{
paddingTop: Platform.OS === "android" ? insets.top : 0,
paddingBottom: Platform.OS === "android" ? insets.bottom : 0,
}}
>
<Text className='text-neutral-500'>No track playing</Text>
</View>
</BottomSheetModalProvider>
);
}
return (
<BottomSheetModalProvider>
<View <View
className='flex-1 bg-[#121212] items-center justify-center' className='flex-1 bg-[#121212]'
style={{ style={{
paddingTop: Platform.OS === "android" ? insets.top : 0, paddingTop: Platform.OS === "android" ? insets.top : 0,
paddingBottom: Platform.OS === "android" ? insets.bottom : 0, paddingBottom: Platform.OS === "android" ? insets.bottom : 0,
}} }}
> >
<Text className='text-neutral-500'>No track playing</Text> {/* Header */}
</View> <View className='flex-row items-center justify-between px-4 pt-3 pb-2'>
);
}
return (
<View
className='flex-1 bg-[#121212]'
style={{
paddingTop: Platform.OS === "android" ? insets.top : 0,
paddingBottom: Platform.OS === "android" ? insets.bottom : 0,
}}
>
{/* Header */}
<View className='flex-row items-center justify-between px-4 pt-3 pb-2'>
<TouchableOpacity
onPress={handleClose}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
className='p-2'
>
<Ionicons name='chevron-down' size={28} color='white' />
</TouchableOpacity>
<View className='flex-row'>
<TouchableOpacity <TouchableOpacity
onPress={() => setViewMode("player")} onPress={handleClose}
className='px-3 py-1' hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
className='p-2'
> >
<Text <Ionicons name='chevron-down' size={28} color='white' />
className={
viewMode === "player"
? "text-white font-semibold"
: "text-neutral-500"
}
>
Now Playing
</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity
onPress={() => setViewMode("queue")} <View className='flex-row'>
className='px-3 py-1' <TouchableOpacity
> onPress={() => setViewMode("player")}
<Text className='px-3 py-1'
className={
viewMode === "queue"
? "text-white font-semibold"
: "text-neutral-500"
}
> >
Queue ({queue.length}) <Text
</Text> className={
viewMode === "player"
? "text-white font-semibold"
: "text-neutral-500"
}
>
Now Playing
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => setViewMode("queue")}
className='px-3 py-1'
>
<Text
className={
viewMode === "queue"
? "text-white font-semibold"
: "text-neutral-500"
}
>
Queue ({queue.length})
</Text>
</TouchableOpacity>
</View>
<TouchableOpacity
onPress={handleOptionsPress}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
className='p-2'
>
<Ionicons name='ellipsis-horizontal' size={24} color='white' />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View style={{ width: 16 }} />
</View>
{viewMode === "player" ? ( {viewMode === "player" ? (
<PlayerView <PlayerView
api={api} api={api}
currentTrack={currentTrack} currentTrack={currentTrack}
imageUrl={imageUrl} imageUrl={imageUrl}
sliderProgress={sliderProgress} sliderProgress={sliderProgress}
sliderMin={sliderMin} sliderMin={sliderMin}
sliderMax={sliderMax} sliderMax={sliderMax}
progressText={progressText} progressText={progressText}
durationText={durationText} durationText={durationText}
isPlaying={isPlaying} isPlaying={isPlaying}
isLoading={isLoading} isLoading={isLoading}
repeatMode={repeatMode} repeatMode={repeatMode}
shuffleEnabled={shuffleEnabled} shuffleEnabled={shuffleEnabled}
canGoNext={canGoNext} canGoNext={canGoNext}
canGoPrevious={canGoPrevious} canGoPrevious={canGoPrevious}
onSliderComplete={handleSliderComplete} onSliderComplete={handleSliderComplete}
onTogglePlayPause={togglePlayPause} onTogglePlayPause={togglePlayPause}
onNext={next} onNext={next}
onPrevious={previous} onPrevious={previous}
onCycleRepeat={cycleRepeatMode} onCycleRepeat={cycleRepeatMode}
onToggleShuffle={toggleShuffle} onToggleShuffle={toggleShuffle}
getRepeatIcon={getRepeatIcon} getRepeatIcon={getRepeatIcon}
queue={queue} queue={queue}
queueIndex={queueIndex} queueIndex={queueIndex}
mediaSource={mediaSource} mediaSource={mediaSource}
isTranscoding={isTranscoding} isTranscoding={isTranscoding}
/>
) : (
<QueueView
api={api}
queue={queue}
queueIndex={queueIndex}
onJumpToIndex={jumpToIndex}
onRemoveFromQueue={removeFromQueue}
onReorderQueue={reorderQueue}
/>
)}
<TrackOptionsSheet
open={trackOptionsOpen}
setOpen={setTrackOptionsOpen}
track={currentTrack}
onAddToPlaylist={handleAddToPlaylist}
/> />
) : ( <PlaylistPickerSheet
<QueueView open={playlistPickerOpen}
api={api} setOpen={setPlaylistPickerOpen}
queue={queue} trackToAdd={currentTrack}
queueIndex={queueIndex} onCreateNew={handleCreateNewPlaylist}
onJumpToIndex={jumpToIndex}
onRemoveFromQueue={removeFromQueue}
onReorderQueue={reorderQueue}
/> />
)} <CreatePlaylistModal
</View> open={createPlaylistOpen}
setOpen={setCreatePlaylistOpen}
initialTrackId={currentTrack?.Id}
/>
</View>
</BottomSheetModalProvider>
); );
} }