mirror of
https://github.com/anultravioletaurora/Jellify.git
synced 2025-12-21 13:30:11 -06:00
Make violet suck less (#708)
making the queue list items more easily dragged
This commit is contained in:
4
App.tsx
4
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)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export interface TrackProps {
|
||||
index: number
|
||||
queue: Queue
|
||||
showArtwork?: boolean | undefined
|
||||
onPress?: () => void | undefined
|
||||
onPress?: () => Promise<void> | undefined
|
||||
onLongPress?: () => void | undefined
|
||||
isNested?: boolean | undefined
|
||||
invertedColors?: boolean | undefined
|
||||
|
||||
@@ -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<RootStackParamList>
|
||||
}): React.JSX.Element {
|
||||
const nowPlaying = useCurrentTrack()
|
||||
const playQueue = usePlayerQueueStore.getState().queue
|
||||
const [queue, setQueue] = useState<JellifyTrack[]>(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<JellifyTrack> | null | undefined, index: number) => ({
|
||||
length: 20,
|
||||
offset: (20 / 9) * index,
|
||||
index,
|
||||
}),
|
||||
[],
|
||||
)
|
||||
|
||||
// Memoize ItemSeparatorComponent to prevent recreation
|
||||
const ItemSeparatorComponent = useCallback(() => <Separator />, [])
|
||||
|
||||
// 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<JellifyTrack>) => {
|
||||
const index = getIndex()
|
||||
({ item: queueItem, index }: RenderItemInfo<JellifyTrack>) => (
|
||||
<XStack alignItems='center' key={`${index}-${queueItem.item.Id}`}>
|
||||
<Sortable.Handle style={{ display: 'flex', flexShrink: 1 }}>
|
||||
<Icon name='drag' />
|
||||
</Sortable.Handle>
|
||||
|
||||
const handleLongPress = () => {
|
||||
trigger('impactLight')
|
||||
drag()
|
||||
}
|
||||
|
||||
const handlePress = () => {
|
||||
if (!isUndefined(index)) skip(index)
|
||||
}
|
||||
|
||||
const handleRemove = () => {
|
||||
if (!isUndefined(index)) removeFromQueue(index)
|
||||
}
|
||||
|
||||
return (
|
||||
<XStack alignItems='center' onLongPress={handleLongPress}>
|
||||
<Track
|
||||
queue={queueRef ?? 'Recently Played'}
|
||||
track={queueItem.item}
|
||||
index={index ?? 0}
|
||||
showArtwork
|
||||
testID={`queue-item-${index}`}
|
||||
onPress={handlePress}
|
||||
onLongPress={handleLongPress}
|
||||
isNested
|
||||
showRemove
|
||||
onRemove={handleRemove}
|
||||
/>
|
||||
</XStack>
|
||||
)
|
||||
},
|
||||
<Track
|
||||
queue={queueRef ?? 'Recently Played'}
|
||||
track={queueItem.item}
|
||||
index={index ?? 0}
|
||||
showArtwork
|
||||
testID={`queue-item-${index}`}
|
||||
onPress={() => skip(index)}
|
||||
isNested
|
||||
showRemove
|
||||
onRemove={() => removeFromQueue(index)}
|
||||
/>
|
||||
</XStack>
|
||||
),
|
||||
[queueRef, navigation, useSkip, useRemoveFromQueue],
|
||||
)
|
||||
|
||||
return (
|
||||
<DraggableFlatList
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
data={playQueue ?? []}
|
||||
dragHitSlop={{
|
||||
left: -50, // https://github.com/computerjazz/react-native-draggable-flatlist/issues/336
|
||||
}}
|
||||
extraData={nowPlaying?.item.Id} // Only track the playing track ID, not the entire object
|
||||
getItemLayout={getItemLayout}
|
||||
initialScrollIndex={scrollIndex !== -1 ? scrollIndex : 0}
|
||||
ItemSeparatorComponent={ItemSeparatorComponent}
|
||||
keyExtractor={keyExtractor}
|
||||
numColumns={1}
|
||||
onDragEnd={handleDragEnd}
|
||||
renderItem={renderItem}
|
||||
removeClippedSubviews={true}
|
||||
maxToRenderPerBatch={10}
|
||||
windowSize={10}
|
||||
updateCellsBatchingPeriod={50}
|
||||
/>
|
||||
<ScrollView flex={1}>
|
||||
<Sortable.Grid
|
||||
data={queue}
|
||||
columns={1}
|
||||
keyExtractor={keyExtractor}
|
||||
renderItem={renderItem}
|
||||
onOrderChange={handleOrderChange}
|
||||
onDragEnd={({ data }) => {
|
||||
setQueue(data)
|
||||
}}
|
||||
overDrag='vertical'
|
||||
customHandle
|
||||
hapticsEnabled={!reducedHaptics}
|
||||
/>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<PlayerQueueStore>()(
|
||||
),
|
||||
)
|
||||
|
||||
export const usePlayQueue = () => usePlayerQueueStore((state) => state.queue)
|
||||
export const usePlayQueue = () => usePlayerQueueStore(useShallow((state) => state.queue))
|
||||
|
||||
export const useShuffle = () => usePlayerQueueStore((state) => state.shuffled)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user