From fd97126f2f68d329728eee15d372654bc505f1a5 Mon Sep 17 00:00:00 2001
From: Violet Caulfield <42452695+anultravioletaurora@users.noreply.github.com>
Date: Tue, 15 Jul 2025 07:23:27 -0500
Subject: [PATCH] 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
---
maestro-tests/musicplayer.yaml | 4 -
package.json | 4 +-
src/components/Artists/component.tsx | 59 +-
.../Context/components/multiple-artists.tsx | 38 +
src/components/Global/components/item-row.tsx | 11 +-
.../Player/components/song-info.tsx | 22 +-
src/components/types.d.ts | 5 +
src/screens/Context/multiple-artists.tsx | 6 +
src/screens/Player/index.tsx | 16 +
src/screens/index.tsx | 1 +
src/screens/tabs.tsx | 1 +
yarn.lock | 1740 ++++++++---------
12 files changed, 996 insertions(+), 911 deletions(-)
create mode 100644 src/components/Context/components/multiple-artists.tsx
create mode 100644 src/screens/Context/multiple-artists.tsx
diff --git a/maestro-tests/musicplayer.yaml b/maestro-tests/musicplayer.yaml
index 5860cea6..2cc989ea 100644
--- a/maestro-tests/musicplayer.yaml
+++ b/maestro-tests/musicplayer.yaml
@@ -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'
diff --git a/package.json b/package.json
index 698c4e16..eb3a41fb 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/components/Artists/component.tsx b/src/components/Artists/component.tsx
index d58f269c..7ab7ffe7 100644
--- a/src/components/Artists/component.tsx
+++ b/src/components/Artists/component.tsx
@@ -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={() => }
estimatedItemSize={itemHeight}
data={artists}
- refreshControl={}
+ refreshControl={
+
+ }
renderItem={({ index, item: artist }) =>
typeof artist === 'string' ? (
-
-
- {artist.toUpperCase()}
-
-
+ // 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 : (
+
+
+ {artist.toUpperCase()}
+
+
+ )
) : typeof artist === 'number' ? null : typeof artist === 'object' ? (
- {showAlphabeticalSelector && (
-
- )}
+ {showAlphabeticalSelector && }
)
}
diff --git a/src/components/Context/components/multiple-artists.tsx b/src/components/Context/components/multiple-artists.tsx
new file mode 100644
index 00000000..b5c4e659
--- /dev/null
+++ b/src/components/Context/components/multiple-artists.tsx
@@ -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 (
+
+ {route.params.artists.map((artist) => {
+ return (
+ {
+ navigation.goBack()
+ navigation.goBack()
+ navigation.navigate('Tabs', {
+ screen: 'Library',
+ params: {
+ screen: 'Artist',
+ params: {
+ artist: artist,
+ },
+ },
+ })
+ }}
+ />
+ )
+ })}
+
+ )
+}
diff --git a/src/components/Global/components/item-row.tsx b/src/components/Global/components/item-row.tsx
index ac82b25a..de36b10c 100644
--- a/src/components/Global/components/item-row.tsx
+++ b/src/components/Global/components/item-row.tsx
@@ -28,10 +28,14 @@ export default function ItemRow({
item,
queueName,
navigation,
+ onPress,
+ circular,
}: {
item: BaseItemDto
queueName: string
navigation: NativeStackNavigationProp
+ 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}
/>
diff --git a/src/components/Player/components/song-info.tsx b/src/components/Player/components/song-info.tsx
index a355e45e..dbe3ce91 100644
--- a/src/components/Player/components/song-info.tsx
+++ b/src/components/Player/components/song-info.tsx
@@ -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],
+ },
},
- },
- })
+ })
+ }
}
}}
>
diff --git a/src/components/types.d.ts b/src/components/types.d.ts
index 9615ed83..4ef4dc18 100644
--- a/src/components/types.d.ts
+++ b/src/components/types.d.ts
@@ -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
export type PlayerProps = NativeStackScreenProps
+export type MultipleArtistsProps = NativeStackScreenProps
export type ProvidedHomeProps = NativeStackScreenProps
export type AddPlaylistProps = NativeStackScreenProps
diff --git a/src/screens/Context/multiple-artists.tsx b/src/screens/Context/multiple-artists.tsx
new file mode 100644
index 00000000..eb1c7b12
--- /dev/null
+++ b/src/screens/Context/multiple-artists.tsx
@@ -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
+}
diff --git a/src/screens/Player/index.tsx b/src/screens/Player/index.tsx
index 02e469ba..0862cafa 100644
--- a/src/screens/Player/index.tsx
+++ b/src/screens/Player/index.tsx
@@ -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()
@@ -34,6 +35,21 @@ export default function Player(): React.JSX.Element {
headerTitle: '',
}}
/>
+
+
+
+
)
}
diff --git a/src/screens/index.tsx b/src/screens/index.tsx
index e95fa8fa..03367002 100644
--- a/src/screens/index.tsx
+++ b/src/screens/index.tsx
@@ -37,6 +37,7 @@ export default function Root(): React.JSX.Element {
options={{
headerShown: false,
presentation: 'modal',
+ sheetAllowedDetents: Platform.OS === 'ios' ? 'fitToContents' : [1.0],
}}
/>
(