mirror of
https://github.com/Jellify-Music/App.git
synced 2025-12-30 15:29:49 -06:00
Rework toggleplayback, add haptic feedback
This commit is contained in:
@@ -9,20 +9,20 @@ import { ImageType } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { queryConfig } from "../../api/queries/query.config";
|
||||
import { Text } from "../Global/helpers/text";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import { playPauseButton } from "./helpers/buttons";
|
||||
import { BottomTabNavigationEventMap } from "@react-navigation/bottom-tabs";
|
||||
import { NavigationHelpers, ParamListBase } from "@react-navigation/native";
|
||||
import { HorizontalSlider } from "../Global/helpers/slider";
|
||||
import PlayPauseButton from "./helpers/buttons";
|
||||
|
||||
export default function Player({ navigation }: { navigation : NavigationHelpers<ParamListBase, BottomTabNavigationEventMap> }): React.JSX.Element {
|
||||
|
||||
const { apiClient } = useApiClientContext();
|
||||
const { queue, playbackState, nowPlaying, play, pause, progress } = usePlayerContext();
|
||||
const { queue, playbackState, nowPlaying, useTogglePlayback, progress } = usePlayerContext();
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
{ nowPlaying && (
|
||||
<YStack alignItems="center">
|
||||
<YStack>
|
||||
|
||||
<XStack alignItems="center">
|
||||
|
||||
@@ -45,7 +45,6 @@ export default function Player({ navigation }: { navigation : NavigationHelpers<
|
||||
|
||||
<XStack
|
||||
marginVertical={10}
|
||||
flex={1}
|
||||
>
|
||||
|
||||
<YStack justifyContent="flex-start" flex={4}>
|
||||
@@ -63,14 +62,14 @@ export default function Player({ navigation }: { navigation : NavigationHelpers<
|
||||
{nowPlaying.artist ?? "Unknown Artist"}</Text>
|
||||
</YStack>
|
||||
|
||||
<XStack alignItems="center">
|
||||
<XStack alignItems="center" flex={1}>
|
||||
{/* Buttons for favorites, song menu go here */}
|
||||
|
||||
</XStack>
|
||||
|
||||
</XStack>
|
||||
|
||||
<XStack>
|
||||
<XStack alignItems="center">
|
||||
{/* playback progress goes here */}
|
||||
<HorizontalSlider
|
||||
value={progress!.position}
|
||||
@@ -80,12 +79,12 @@ export default function Player({ navigation }: { navigation : NavigationHelpers<
|
||||
|
||||
</XStack>
|
||||
|
||||
<XStack>
|
||||
{playPauseButton(playbackState, play, pause)}
|
||||
<XStack alignItems="center">
|
||||
<PlayPauseButton />
|
||||
</XStack>
|
||||
|
||||
|
||||
</YStack>
|
||||
</YStack>
|
||||
)}
|
||||
</SafeAreaView>
|
||||
);
|
||||
|
||||
@@ -2,14 +2,17 @@ import { State } from "react-native-track-player";
|
||||
import { Colors } from "react-native/Libraries/NewAppScreen";
|
||||
import { Spinner } from "tamagui";
|
||||
import Icon from "../../Global/helpers/icon";
|
||||
import { usePlayerContext } from "@/player/provider";
|
||||
|
||||
export function playPauseButton(playbackState: State | undefined, play: Function, pause: Function) {
|
||||
export default function PlayPauseButton() : React.JSX.Element {
|
||||
|
||||
const { playbackState, useTogglePlayback } = usePlayerContext();
|
||||
|
||||
let button : React.JSX.Element;
|
||||
|
||||
switch (playbackState) {
|
||||
case (State.Playing) : {
|
||||
button = <Icon name="pause" large onPress={() => pause()} />;
|
||||
button = <Icon name="pause" large onPress={() => useTogglePlayback.mutate(undefined)} />;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -20,7 +23,7 @@ export function playPauseButton(playbackState: State | undefined, play: Function
|
||||
}
|
||||
|
||||
default : {
|
||||
button = <Icon name="play" large onPress={() => play()} />
|
||||
button = <Icon name="play" large onPress={() => useTogglePlayback.mutate(undefined)} />
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@ import { getImageApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { queryConfig } from "../../api/queries/query.config";
|
||||
import { useApiClientContext } from "../jellyfin-api-provider";
|
||||
import TextTicker from 'react-native-text-ticker';
|
||||
import { playPauseButton } from "./helpers/buttons";
|
||||
import PlayPauseButton from "./helpers/buttons";
|
||||
import { skipToNext } from "react-native-track-player/lib/src/trackPlayer";
|
||||
|
||||
export function Miniplayer({ navigation }: { navigation : NavigationHelpers<ParamListBase, BottomTabNavigationEventMap> }) : React.JSX.Element {
|
||||
|
||||
const { nowPlaying, playbackState, play, pause } = usePlayerContext();
|
||||
const { nowPlaying } = usePlayerContext();
|
||||
|
||||
const { apiClient } = useApiClientContext();
|
||||
|
||||
@@ -77,7 +77,7 @@ export function Miniplayer({ navigation }: { navigation : NavigationHelpers<Para
|
||||
</YStack>
|
||||
|
||||
<XStack flex={2}>
|
||||
{ playPauseButton(playbackState, play, pause) }
|
||||
<PlayPauseButton />
|
||||
|
||||
<Icon
|
||||
large
|
||||
|
||||
13
package-lock.json
generated
13
package-lock.json
generated
@@ -37,6 +37,7 @@
|
||||
"react-native-device-info": "^11.1.0",
|
||||
"react-native-file-access": "^3.1.1",
|
||||
"react-native-gesture-handler": "^2.20.0",
|
||||
"react-native-haptic-feedback": "^2.3.3",
|
||||
"react-native-mmkv": "2.12.2",
|
||||
"react-native-reanimated": "^3.16.3",
|
||||
"react-native-safe-area-context": "^4.11.1",
|
||||
@@ -20447,6 +20448,18 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-haptic-feedback": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-haptic-feedback/-/react-native-haptic-feedback-2.3.3.tgz",
|
||||
"integrity": "sha512-svS4D5PxfNv8o68m9ahWfwje5NqukM3qLS48+WTdhbDkNUkOhP9rDfDSRHzlhk4zq+ISjyw95EhLeh8NkKX5vQ==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"example"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"react-native": ">=0.60.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-mmkv": {
|
||||
"version": "2.12.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-mmkv/-/react-native-mmkv-2.12.2.tgz",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"react-native-device-info": "^11.1.0",
|
||||
"react-native-file-access": "^3.1.1",
|
||||
"react-native-gesture-handler": "^2.20.0",
|
||||
"react-native-haptic-feedback": "^2.3.3",
|
||||
"react-native-mmkv": "2.12.2",
|
||||
"react-native-reanimated": "^3.16.3",
|
||||
"react-native-safe-area-context": "^4.11.1",
|
||||
|
||||
@@ -16,6 +16,8 @@ import { useMutation, UseMutationResult } from "@tanstack/react-query";
|
||||
import { QueueMutation } from "./interfaces";
|
||||
import { mapDtoToTrack } from "@/helpers/mappings";
|
||||
import { QueuingType } from "@/enums/queuing-type";
|
||||
import { trigger } from "react-native-haptic-feedback";
|
||||
import { pause } from "react-native-track-player/lib/src/trackPlayer";
|
||||
|
||||
interface PlayerContext {
|
||||
showPlayer: boolean;
|
||||
@@ -24,10 +26,7 @@ interface PlayerContext {
|
||||
setShowMiniplayer: React.Dispatch<SetStateAction<boolean>>;
|
||||
nowPlaying: JellifyTrack | undefined;
|
||||
queue: JellifyTrack[];
|
||||
play: (index?: number | undefined) => Promise<void>,
|
||||
pause: () => Promise<void>,
|
||||
resetQueue: (hideMiniplayer : boolean | undefined) => Promise<void>;
|
||||
addToQueue: (tracks: JellifyTrack[]) => Promise<void>;
|
||||
useTogglePlayback: UseMutationResult<void, Error, number | undefined, unknown>;
|
||||
playNewQueue: UseMutationResult<void, Error, QueueMutation, unknown>;
|
||||
playbackState: State | undefined;
|
||||
progress: Progress | undefined;
|
||||
@@ -56,13 +55,6 @@ const PlayerContextInitializer = () => {
|
||||
TrackPlayer.skip(index)
|
||||
|
||||
TrackPlayer.play();
|
||||
|
||||
handlePlaybackStarted(sessionId, playStateApi, nowPlaying!)
|
||||
}
|
||||
|
||||
const pause = async () => {
|
||||
TrackPlayer.pause();
|
||||
handlePlaybackStopped(sessionId, playStateApi, nowPlaying!);
|
||||
}
|
||||
|
||||
const resetQueue = async (hideMiniplayer?: boolean | undefined) => {
|
||||
@@ -86,8 +78,19 @@ const PlayerContextInitializer = () => {
|
||||
//#endregion Functions
|
||||
|
||||
//#region Hooks
|
||||
const useTogglePlayback = useMutation({
|
||||
mutationFn: async (index?: number | undefined) => {
|
||||
trigger("impactLight");
|
||||
if (playbackState === State.Playing)
|
||||
await pause();
|
||||
else
|
||||
await play(index);
|
||||
}
|
||||
})
|
||||
|
||||
const playNewQueue = useMutation({
|
||||
mutationFn: async (mutation: QueueMutation) => {
|
||||
trigger("impactLight");
|
||||
await resetQueue(false)
|
||||
await addToQueue(mutation.tracklist.map((track) => {
|
||||
return mapDtoToTrack(apiClient!, sessionId, track, QueuingType.FromSelection)
|
||||
@@ -160,10 +163,7 @@ const PlayerContextInitializer = () => {
|
||||
setShowMiniplayer,
|
||||
nowPlaying,
|
||||
queue,
|
||||
play,
|
||||
pause,
|
||||
addToQueue,
|
||||
resetQueue,
|
||||
useTogglePlayback,
|
||||
playNewQueue,
|
||||
playbackState,
|
||||
progress,
|
||||
@@ -171,6 +171,7 @@ const PlayerContextInitializer = () => {
|
||||
//#endregion return
|
||||
}
|
||||
|
||||
//#region Create PlayerContext
|
||||
export const PlayerContext = createContext<PlayerContext>({
|
||||
showPlayer: false,
|
||||
setShowPlayer: () => {},
|
||||
@@ -178,10 +179,24 @@ export const PlayerContext = createContext<PlayerContext>({
|
||||
setShowMiniplayer: () => {},
|
||||
nowPlaying: undefined,
|
||||
queue: [],
|
||||
play: async (index?: number | undefined) => {},
|
||||
pause: async () => {},
|
||||
resetQueue: async () => {},
|
||||
addToQueue: async ([]) => {},
|
||||
useTogglePlayback: {
|
||||
mutate: () => {},
|
||||
mutateAsync: async () => {},
|
||||
data: undefined,
|
||||
error: null,
|
||||
variables: undefined,
|
||||
isError: false,
|
||||
isIdle: true,
|
||||
isPaused: false,
|
||||
isPending: false,
|
||||
isSuccess: false,
|
||||
status: "idle",
|
||||
reset: () => {},
|
||||
context: {},
|
||||
failureCount: 0,
|
||||
failureReason: null,
|
||||
submittedAt: 0
|
||||
},
|
||||
playNewQueue: {
|
||||
mutate: () => {},
|
||||
mutateAsync: async () => {},
|
||||
@@ -203,6 +218,7 @@ export const PlayerContext = createContext<PlayerContext>({
|
||||
playbackState: undefined,
|
||||
progress: undefined,
|
||||
});
|
||||
//#endregion Create PlayerContext
|
||||
|
||||
export const PlayerProvider: ({ children }: { children: ReactNode }) => React.JSX.Element = ({ children }: { children: ReactNode }) => {
|
||||
const {
|
||||
@@ -212,10 +228,7 @@ export const PlayerProvider: ({ children }: { children: ReactNode }) => React.JS
|
||||
setShowMiniplayer,
|
||||
nowPlaying,
|
||||
queue,
|
||||
play,
|
||||
pause,
|
||||
resetQueue,
|
||||
addToQueue,
|
||||
useTogglePlayback,
|
||||
playNewQueue,
|
||||
playbackState,
|
||||
progress
|
||||
@@ -228,10 +241,7 @@ export const PlayerProvider: ({ children }: { children: ReactNode }) => React.JS
|
||||
setShowMiniplayer,
|
||||
nowPlaying,
|
||||
queue,
|
||||
play,
|
||||
pause,
|
||||
resetQueue,
|
||||
addToQueue,
|
||||
useTogglePlayback,
|
||||
playNewQueue,
|
||||
playbackState,
|
||||
progress
|
||||
|
||||
Reference in New Issue
Block a user