From ca46bc2054f3c52f6096746431f6a0114ad9c2a0 Mon Sep 17 00:00:00 2001 From: Ritesh Shukla Date: Sun, 23 Nov 2025 20:10:07 +0530 Subject: [PATCH] Make violet suck less (#708) making the queue list items more easily dragged --- App.tsx | 4 +- package.json | 3 +- src/components/Global/components/track.tsx | 2 +- src/components/Player/queue.tsx | 140 ++++++++------------- src/stores/player/queue.ts | 3 +- yarn.lock | 9 +- 6 files changed, 68 insertions(+), 93 deletions(-) diff --git a/App.tsx b/App.tsx index 3789a94c..d90ca6e3 100644 --- a/App.tsx +++ b/App.tsx @@ -4,7 +4,7 @@ import 'react-native-url-polyfill/auto' import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client' import Jellify from './src/components/jellify' import { TamaguiProvider } from 'tamagui' -import { Platform, useColorScheme } from 'react-native' +import { LogBox, Platform, useColorScheme } from 'react-native' import jellifyConfig from './tamagui.config' import { queryClientPersister } from './src/constants/storage' import { ONE_DAY, queryClient } from './src/constants/query-client' @@ -26,6 +26,8 @@ import navigationRef from './navigation' import { PROGRESS_UPDATE_EVENT_INTERVAL } from './src/player/config' import { useThemeSetting } from './src/stores/settings/app' +LogBox.ignoreAllLogs() + export default function App(): React.JSX.Element { // Add performance monitoring to track app-level re-renders const performanceMetrics = usePerformanceMonitor('App', 3) diff --git a/package.json b/package.json index 526c6767..4db44016 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "react-native-reanimated": "4.1.5", "react-native-safe-area-context": "5.6.2", "react-native-screens": "4.18.0", + "react-native-sortables": "^1.9.3", "react-native-swipeable-item": "^2.0.9", "react-native-text-ticker": "^1.15.0", "react-native-toast-message": "^2.3.3", @@ -144,4 +145,4 @@ "node": ">=18" }, "packageManager": "yarn@1.22.22" -} \ No newline at end of file +} diff --git a/src/components/Global/components/track.tsx b/src/components/Global/components/track.tsx index 58150860..2ca88a46 100644 --- a/src/components/Global/components/track.tsx +++ b/src/components/Global/components/track.tsx @@ -37,7 +37,7 @@ export interface TrackProps { index: number queue: Queue showArtwork?: boolean | undefined - onPress?: () => void | undefined + onPress?: () => Promise | undefined onLongPress?: () => void | undefined isNested?: boolean | undefined invertedColors?: boolean | undefined diff --git a/src/components/Player/queue.tsx b/src/components/Player/queue.tsx index c5028b59..4f2261ec 100644 --- a/src/components/Player/queue.tsx +++ b/src/components/Player/queue.tsx @@ -2,10 +2,8 @@ import Icon from '../Global/components/icon' import Track from '../Global/components/track' import { RootStackParamList } from '../../screens/types' import { NativeStackNavigationProp } from '@react-navigation/native-stack' -import DraggableFlatList, { RenderItemParams } from 'react-native-draggable-flatlist' -import { Separator, XStack } from 'tamagui' -import { isUndefined } from 'lodash' -import { useLayoutEffect, useCallback, useMemo } from 'react' +import { ScrollView, XStack } from 'tamagui' +import { useLayoutEffect, useCallback, useState } from 'react' import JellifyTrack from '../../types/JellifyTrack' import { useRemoveFromQueue, @@ -13,24 +11,31 @@ import { useReorderQueue, useSkip, } from '../../providers/Player/hooks/mutations' -import useHapticFeedback from '../../hooks/use-haptic-feedback' -import { useCurrentTrack, usePlayQueue, useQueueRef } from '../../stores/player/queue' +import { + useCurrentTrack, + usePlayerQueueStore, + usePlayQueue, + useQueueRef, +} from '../../stores/player/queue' +import Sortable from 'react-native-sortables' +import { RenderItemInfo } from 'react-native-sortables/dist/typescript/types' +import { useReducedHapticsSetting } from '../../stores/settings/app' export default function Queue({ navigation, }: { navigation: NativeStackNavigationProp }): React.JSX.Element { - const nowPlaying = useCurrentTrack() + const playQueue = usePlayerQueueStore.getState().queue + const [queue, setQueue] = useState(playQueue) - const playQueue = usePlayQueue() const queueRef = useQueueRef() const { mutate: removeUpcomingTracks } = useRemoveUpcomingTracks() const { mutate: removeFromQueue } = useRemoveFromQueue() const { mutate: reorderQueue } = useReorderQueue() const skip = useSkip() - const trigger = useHapticFeedback() + const [reducedHaptics] = useReducedHapticsSetting() useLayoutEffect(() => { navigation.setOptions({ @@ -40,96 +45,55 @@ export default function Queue({ }) }, [navigation, removeUpcomingTracks]) - // Memoize scroll index calculation - const scrollIndex = useMemo( - () => playQueue?.findIndex((queueItem) => queueItem.item.Id! === nowPlaying!.item.Id!), - [playQueue, nowPlaying?.item.Id], - ) - - // Memoize key extractor for better performance - const keyExtractor = useCallback( - (item: JellifyTrack, index: number) => `${index}-${item.item.Id}`, - [], - ) - - // Memoize getItemLayout for better performance - const getItemLayout = useCallback( - (data: ArrayLike | null | undefined, index: number) => ({ - length: 20, - offset: (20 / 9) * index, - index, - }), - [], - ) - - // Memoize ItemSeparatorComponent to prevent recreation - const ItemSeparatorComponent = useCallback(() => , []) - // Memoize onDragEnd handler - const handleDragEnd = useCallback( - ({ from, to }: { from: number; to: number }) => { - reorderQueue({ from, to }) + const handleOrderChange = useCallback( + ({ fromIndex, toIndex }: { fromIndex: number; toIndex: number }) => { + reorderQueue({ from: fromIndex, to: toIndex }) }, [reorderQueue], ) + const keyExtractor = useCallback((item: JellifyTrack) => `${item.item.Id}`, []) + // Memoize renderItem function for better performance const renderItem = useCallback( - ({ item: queueItem, getIndex, drag, isActive }: RenderItemParams) => { - const index = getIndex() + ({ item: queueItem, index }: RenderItemInfo) => ( + + + + - const handleLongPress = () => { - trigger('impactLight') - drag() - } - - const handlePress = () => { - if (!isUndefined(index)) skip(index) - } - - const handleRemove = () => { - if (!isUndefined(index)) removeFromQueue(index) - } - - return ( - - - - ) - }, + skip(index)} + isNested + showRemove + onRemove={() => removeFromQueue(index)} + /> + + ), [queueRef, navigation, useSkip, useRemoveFromQueue], ) return ( - + + { + setQueue(data) + }} + overDrag='vertical' + customHandle + hapticsEnabled={!reducedHaptics} + /> + ) } diff --git a/src/stores/player/queue.ts b/src/stores/player/queue.ts index b51beb03..af7bad26 100644 --- a/src/stores/player/queue.ts +++ b/src/stores/player/queue.ts @@ -4,6 +4,7 @@ import { mmkvStateStorage } from '../../constants/storage' import { create } from 'zustand' import { createJSONStorage, devtools, persist } from 'zustand/middleware' import { RepeatMode } from 'react-native-track-player' +import { useShallow } from 'zustand/react/shallow' type PlayerQueueStore = { shuffled: boolean @@ -76,7 +77,7 @@ export const usePlayerQueueStore = create()( ), ) -export const usePlayQueue = () => usePlayerQueueStore((state) => state.queue) +export const usePlayQueue = () => usePlayerQueueStore(useShallow((state) => state.queue)) export const useShuffle = () => usePlayerQueueStore((state) => state.shuffled) diff --git a/yarn.lock b/yarn.lock index a6b978a8..65277a5b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8421,7 +8421,7 @@ react-native-google-cast@^4.9.1: resolved "https://registry.yarnpkg.com/react-native-google-cast/-/react-native-google-cast-4.9.1.tgz#f1c453c11460a1f787accff80072492a4cd3e86c" integrity sha512-/HvIKAaWHtG6aTNCxrNrqA2ftWGkfH0M/2iN+28pdGUXpKmueb33mgL1m8D4zzwEODQMcmpfoCsym1IwDvugBQ== -react-native-haptic-feedback@^2.3.3: +react-native-haptic-feedback@>=2.0.0, react-native-haptic-feedback@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/react-native-haptic-feedback/-/react-native-haptic-feedback-2.3.3.tgz#88b6876e91399a69bd1b551fe1681b2f3dc1214e" integrity sha512-svS4D5PxfNv8o68m9ahWfwje5NqukM3qLS48+WTdhbDkNUkOhP9rDfDSRHzlhk4zq+ISjyw95EhLeh8NkKX5vQ== @@ -8477,6 +8477,13 @@ react-native-screens@4.18.0: react-freeze "^1.0.0" warn-once "^0.1.0" +react-native-sortables@^1.9.3: + version "1.9.3" + resolved "https://registry.yarnpkg.com/react-native-sortables/-/react-native-sortables-1.9.3.tgz#cb7ef05aa121ccd54f42664a162c40779376605f" + integrity sha512-VLhW9+3AVEaJNwwQSgN+n/Qe+YRB0C0mNWTjHhyzcZ+YjY4BmJao4bZxl5lD6EsfqZ1Ij6B2ZdxjNlSkUXrvow== + optionalDependencies: + react-native-haptic-feedback ">=2.0.0" + react-native-swipeable-item@^2.0.9: version "2.0.9" resolved "https://registry.yarnpkg.com/react-native-swipeable-item/-/react-native-swipeable-item-2.0.9.tgz#694a3f10333b47ba7f0e57d916fa39407b76a6d6"