mirror of
https://github.com/anultravioletaurora/Jellify.git
synced 2026-05-14 01:28:43 -05:00
Multiple Artist Support in Player (#441)
* Add support for navigating to multiple artists in the player fix some display issues in the artists screen, namely displaying an indicator when loading and hiding sections when we don't have artists that start with a given letter
This commit is contained in:
@@ -34,10 +34,6 @@ appId: com.jellify
|
||||
- tapOn:
|
||||
id: "play-button-test-id"
|
||||
|
||||
# check if the pause button is visible
|
||||
- assertVisible:
|
||||
id: "pause-button-test-id"
|
||||
|
||||
# check if the queue button is visible
|
||||
- assertVisible:
|
||||
id: 'queue-button-test-id'
|
||||
|
||||
+2
-2
@@ -44,7 +44,7 @@
|
||||
"@react-navigation/native-stack": "^7.3.21",
|
||||
"@sentry/react-native": "^6.17.0",
|
||||
"@shopify/flash-list": "^2.0.0-rc.11",
|
||||
"@tamagui/config": "^1.132.1",
|
||||
"@tamagui/config": "^1.132.6",
|
||||
"@tanstack/query-sync-storage-persister": "^5.83.0",
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"@tanstack/react-query-persist-client": "^5.83.0",
|
||||
@@ -87,7 +87,7 @@
|
||||
"react-native-uuid": "^2.0.3",
|
||||
"react-native-vector-icons": "^10.2.0",
|
||||
"ruby": "^0.6.1",
|
||||
"tamagui": "^1.132.1"
|
||||
"tamagui": "^1.132.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.28.0",
|
||||
|
||||
@@ -11,6 +11,7 @@ import { FlashList } from '@shopify/flash-list'
|
||||
import { useLibraryContext } from '../../providers/Library'
|
||||
import { sleepify } from '../../utils/sleep'
|
||||
import { AZScroller } from '../Global/components/alphabetical-selector'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
|
||||
export default function Artists({
|
||||
artists,
|
||||
@@ -50,18 +51,19 @@ export default function Artists({
|
||||
!artistPageParams.current.includes(letter)) &&
|
||||
hasNextPage
|
||||
)
|
||||
|
||||
await sleepify(250)
|
||||
sectionListRef.current?.scrollToIndex({
|
||||
index:
|
||||
(artistsRef.current?.indexOf(letter) ?? 0) > -1
|
||||
? artistsRef.current!.indexOf(letter)
|
||||
: 0,
|
||||
viewPosition: 0.1,
|
||||
animated: true,
|
||||
})
|
||||
}
|
||||
|
||||
const { mutate: alphabetSelectorMutate, isPending: isAlphabetSelectorPending } = useMutation({
|
||||
mutationFn: (letter: string) => alphabeticalSelectorCallback(letter),
|
||||
onSuccess: (data, letter) => {
|
||||
sectionListRef.current?.scrollToIndex({
|
||||
index: artistsRef.current!.indexOf(letter),
|
||||
viewPosition: 0.1,
|
||||
animated: true,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
artistsRef.current = artists ?? []
|
||||
}, [artists])
|
||||
@@ -89,21 +91,28 @@ export default function Artists({
|
||||
ItemSeparatorComponent={() => <Separator />}
|
||||
estimatedItemSize={itemHeight}
|
||||
data={artists}
|
||||
refreshControl={<RefreshControl refreshing={isPending} />}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={isPending || isAlphabetSelectorPending} />
|
||||
}
|
||||
renderItem={({ index, item: artist }) =>
|
||||
typeof artist === 'string' ? (
|
||||
<XStack
|
||||
padding={'$2'}
|
||||
backgroundColor={'$background'}
|
||||
borderRadius={'$5'}
|
||||
borderWidth={'$1'}
|
||||
borderColor={'$primary'}
|
||||
margin={'$2'}
|
||||
>
|
||||
<Text bold color={'$primary'}>
|
||||
{artist.toUpperCase()}
|
||||
</Text>
|
||||
</XStack>
|
||||
// Don't render the letter if we don't have any artists that start with it
|
||||
// If the index is the last index, or the next index is not an object, then don't render the letter
|
||||
index - 1 === artists!.length ||
|
||||
typeof artists![index + 1] !== 'object' ? null : (
|
||||
<XStack
|
||||
padding={'$2'}
|
||||
backgroundColor={'$background'}
|
||||
borderRadius={'$5'}
|
||||
borderWidth={'$1'}
|
||||
borderColor={'$primary'}
|
||||
margin={'$2'}
|
||||
>
|
||||
<Text bold color={'$primary'}>
|
||||
{artist.toUpperCase()}
|
||||
</Text>
|
||||
</XStack>
|
||||
)
|
||||
) : typeof artist === 'number' ? null : typeof artist === 'object' ? (
|
||||
<MemoizedItem
|
||||
item={artist}
|
||||
@@ -137,9 +146,7 @@ export default function Artists({
|
||||
removeClippedSubviews={false}
|
||||
/>
|
||||
|
||||
{showAlphabeticalSelector && (
|
||||
<AZScroller onLetterSelect={alphabeticalSelectorCallback} />
|
||||
)}
|
||||
{showAlphabeticalSelector && <AZScroller onLetterSelect={alphabetSelectorMutate} />}
|
||||
</XStack>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ScrollView, View } from 'tamagui'
|
||||
import { MultipleArtistsProps } from '../../types'
|
||||
import ItemRow from '../../Global/components/item-row'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function MultipleArtists({
|
||||
navigation,
|
||||
route,
|
||||
}: MultipleArtistsProps): React.JSX.Element {
|
||||
return (
|
||||
<ScrollView>
|
||||
{route.params.artists.map((artist) => {
|
||||
return (
|
||||
<ItemRow
|
||||
circular
|
||||
key={artist.Id}
|
||||
item={artist}
|
||||
queueName={''}
|
||||
navigation={navigation}
|
||||
onPress={() => {
|
||||
navigation.goBack()
|
||||
navigation.goBack()
|
||||
navigation.navigate('Tabs', {
|
||||
screen: 'Library',
|
||||
params: {
|
||||
screen: 'Artist',
|
||||
params: {
|
||||
artist: artist,
|
||||
},
|
||||
},
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
@@ -28,10 +28,14 @@ export default function ItemRow({
|
||||
item,
|
||||
queueName,
|
||||
navigation,
|
||||
onPress,
|
||||
circular,
|
||||
}: {
|
||||
item: BaseItemDto
|
||||
queueName: string
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
onPress?: () => void
|
||||
circular?: boolean
|
||||
}): React.JSX.Element {
|
||||
const { useStartPlayback } = usePlayerContext()
|
||||
const { useLoadNewQueue } = useQueueContext()
|
||||
@@ -77,6 +81,11 @@ export default function ItemRow({
|
||||
})
|
||||
}}
|
||||
onPress={() => {
|
||||
if (onPress) {
|
||||
onPress()
|
||||
return
|
||||
}
|
||||
|
||||
switch (item.Type) {
|
||||
case 'MusicArtist': {
|
||||
navigation.navigate('Artist', {
|
||||
@@ -101,7 +110,7 @@ export default function ItemRow({
|
||||
item={item}
|
||||
height={'$12'}
|
||||
width={'$12'}
|
||||
circular={item.Type === 'MusicArtist'}
|
||||
circular={item.Type === 'MusicArtist' || circular}
|
||||
/>
|
||||
</YStack>
|
||||
|
||||
|
||||
@@ -62,16 +62,22 @@ export default function SongInfo({
|
||||
color={'$color'}
|
||||
onPress={() => {
|
||||
if (nowPlaying!.item.ArtistItems) {
|
||||
navigation.goBack() // Dismiss player modal
|
||||
navigation.navigate('Tabs', {
|
||||
screen: 'Library',
|
||||
params: {
|
||||
screen: 'Artist',
|
||||
if (nowPlaying!.item.ArtistItems!.length > 1) {
|
||||
navigation.navigate('MultipleArtists', {
|
||||
artists: nowPlaying!.item.ArtistItems!,
|
||||
})
|
||||
} else {
|
||||
navigation.goBack() // Dismiss player modal
|
||||
navigation.navigate('Tabs', {
|
||||
screen: 'Library',
|
||||
params: {
|
||||
artist: nowPlaying!.item.ArtistItems![0],
|
||||
screen: 'Artist',
|
||||
params: {
|
||||
artist: nowPlaying!.item.ArtistItems![0],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
Vendored
+5
@@ -87,6 +87,10 @@ export type StackParamList = {
|
||||
Player: undefined
|
||||
Queue: undefined
|
||||
|
||||
MultipleArtists: {
|
||||
artists: BaseItemDto[]
|
||||
}
|
||||
|
||||
Artist: {
|
||||
artist: BaseItemDto
|
||||
}
|
||||
@@ -126,6 +130,7 @@ export type LibrarySelectionProps = NativeStackScreenProps<StackParamList, 'Libr
|
||||
|
||||
export type TabProps = NativeStackScreenProps<StackParamList, 'Tabs'>
|
||||
export type PlayerProps = NativeStackScreenProps<StackParamList, 'Player'>
|
||||
export type MultipleArtistsProps = NativeStackScreenProps<StackParamList, 'MultipleArtists'>
|
||||
|
||||
export type ProvidedHomeProps = NativeStackScreenProps<StackParamList, 'HomeScreen'>
|
||||
export type AddPlaylistProps = NativeStackScreenProps<StackParamList, 'AddPlaylist'>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { MultipleArtistsProps } from '../../components/types'
|
||||
import MultipleArtists from '../../components/Context/components/multiple-artists'
|
||||
|
||||
export default function MultipleArtistsSheet(props: MultipleArtistsProps): React.JSX.Element {
|
||||
return <MultipleArtists {...props} />
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import PlayerScreen from '../../components/Player'
|
||||
import Queue from '../../components/Player/queue'
|
||||
import DetailsScreen from '../Detail'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import MultipleArtistsSheet from '../Context/multiple-artists'
|
||||
|
||||
export const PlayerStack = createNativeStackNavigator<StackParamList>()
|
||||
|
||||
@@ -34,6 +35,21 @@ export default function Player(): React.JSX.Element {
|
||||
headerTitle: '',
|
||||
}}
|
||||
/>
|
||||
|
||||
<PlayerStack.Group
|
||||
screenOptions={{
|
||||
presentation: 'formSheet',
|
||||
sheetAllowedDetents: [0.2],
|
||||
}}
|
||||
>
|
||||
<PlayerStack.Screen
|
||||
name='MultipleArtists'
|
||||
component={MultipleArtistsSheet}
|
||||
options={{
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
</PlayerStack.Group>
|
||||
</PlayerStack.Navigator>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ export default function Root(): React.JSX.Element {
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: 'modal',
|
||||
sheetAllowedDetents: Platform.OS === 'ios' ? 'fitToContents' : [1.0],
|
||||
}}
|
||||
/>
|
||||
<RootStack.Screen
|
||||
|
||||
@@ -86,6 +86,7 @@ export function Tabs({
|
||||
name='Library'
|
||||
component={LibraryStack}
|
||||
options={{
|
||||
lazy: false,
|
||||
headerShown: false,
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<MaterialCommunityIcons
|
||||
|
||||
Reference in New Issue
Block a user