Skip to content

Commit 26a673b

Browse files
Fix mobile track artist centering (#14488)
## Summary - Keep the mobile track-page artist names visually centered under the title while still showing user badges in a separate centered badge row. - Show accepted collaborators in mobile feed track rows as comma-separated artist names, with one-line ellipsis behavior when the row is too long. - Preserve badges in feed rows for both the owner and accepted collaborators. - Add focused unit tests for centered artist badge rendering plus artist-name composition. ## Test Plan - `/usr/local/bin/node ../../node_modules/.bin/jest src/components/user-link/TrackArtists.test.tsx src/components/lineup-tile/artistNames.test.ts --moduleNameMapper='{"^react-native$":"<rootDir>/../../node_modules/react-native","^@testing-library/react-native$":"<rootDir>/node_modules/@testing-library/react-native"}'` - `/usr/local/bin/node ../../node_modules/.bin/eslint --cache --ext=js,jsx,ts,tsx src/components/user-link/UserLink.tsx src/components/user-link/TrackArtists.tsx src/components/user-link/TrackArtists.test.tsx src/screens/track-screen/TrackScreenDetailsTile.tsx src/components/track/TrackCard.tsx src/components/lineup-tile/TrackTile.tsx src/components/lineup-tile/LineupTileMetadata.tsx src/components/lineup-tile/styles.ts src/components/lineup-tile/artistNames.ts src/components/lineup-tile/artistNames.test.ts` - `/usr/local/bin/node ../../node_modules/.bin/tsc --noEmit`
1 parent 7b53bf0 commit 26a673b

9 files changed

Lines changed: 294 additions & 32 deletions

File tree

packages/mobile/src/components/lineup-tile/LineupTileMetadata.tsx

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ import { useSelector } from 'react-redux'
55

66
import { IconVolumeLevel2 } from '@audius/harmony-native'
77
import { Text, FadeInView } from 'app/components/core'
8-
import { UserLink } from 'app/components/user-link'
8+
import { UserBadges } from 'app/components/user-badges'
99
import { useNavigation } from 'app/hooks/useNavigation'
1010
import { makeStyles } from 'app/styles'
1111
import type { GestureResponderHandler } from 'app/types/gesture'
1212
import { useThemeColors } from 'app/utils/theme'
1313

1414
import { LineupTileArt } from './LineupTileArt'
1515
import { LineupTileTopRight } from './LineupTileTopRight'
16+
import type { ArtistNameCollaborator } from './artistNames'
17+
import { getTrackArtistNames } from './artistNames'
1618
import { useStyles as useTileStyles } from './styles'
1719
import type { RenderImage } from './types'
1820

@@ -27,6 +29,16 @@ const useStyles = makeStyles(({ palette }) => ({
2729
playingIndicator: {
2830
marginLeft: 8
2931
},
32+
artistText: {
33+
flexShrink: 1,
34+
minWidth: 0
35+
},
36+
artistBadges: {
37+
flexDirection: 'row',
38+
alignItems: 'center',
39+
flexShrink: 0,
40+
gap: 4
41+
},
3042
coSignLabel: {
3143
position: 'absolute',
3244
bottom: -3,
@@ -45,6 +57,8 @@ type Props = {
4557
renderImage: RenderImage
4658
title: string
4759
userId: ID
60+
userName: string
61+
collaborators?: ArtistNameCollaborator[] | null
4862
isPlayingUid: boolean
4963
type: 'track' | 'playlist' | 'album'
5064
trackId: ID
@@ -59,6 +73,8 @@ export const LineupTileMetadata = ({
5973
renderImage,
6074
title,
6175
userId,
76+
userName,
77+
collaborators,
6278
isPlayingUid,
6379
type,
6480
trackId,
@@ -72,6 +88,7 @@ export const LineupTileMetadata = ({
7288
const navigation = useNavigation()
7389

7490
const isActive = isPlayingUid
91+
const artistNames = getTrackArtistNames(userName, collaborators)
7592

7693
const isPlaying = useSelector((state) => {
7794
return getPlaying(state) && isActive
@@ -139,14 +156,24 @@ export const LineupTileMetadata = ({
139156
onPressIn={onPressWithPropagationBlock}
140157
onPress={handlePressArtist}
141158
activeOpacity={0.7}
142-
style={{ alignSelf: 'flex-start' }}
159+
style={tileStyles.artist}
143160
>
144-
<View pointerEvents='none'>
145-
<UserLink
146-
variant={isActive ? 'active' : 'default'}
147-
textVariant='body'
148-
userId={userId}
149-
/>
161+
<Text
162+
color={isActive ? 'primary' : 'neutral'}
163+
numberOfLines={1}
164+
ellipsizeMode='tail'
165+
style={styles.artistText}
166+
>
167+
{artistNames}
168+
</Text>
169+
<View style={styles.artistBadges}>
170+
<UserBadges userId={userId} />
171+
{collaborators?.map((collaborator) => (
172+
<UserBadges
173+
key={collaborator.user_id}
174+
userId={collaborator.user_id}
175+
/>
176+
))}
150177
</View>
151178
</TouchableOpacity>
152179
</FadeInView>

packages/mobile/src/components/lineup-tile/TrackTile.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ const TrackTileComponent = (props: TrackTileProps) => {
8585
title: track.title,
8686
track_id: track.track_id,
8787
genre: track.genre,
88+
collaborators: track.collaborators,
8889
stream_conditions: track.stream_conditions,
8990

9091
ddex_app: track.ddex_app,
@@ -100,6 +101,7 @@ const TrackTileComponent = (props: TrackTileProps) => {
100101
const { data: user } = useUser(track?.owner_id, {
101102
select: (user) => ({
102103
artist_pick_track_id: user.artist_pick_track_id,
104+
name: user.name,
103105
user_id: user.user_id,
104106
is_deactivated: user.is_deactivated
105107
})
@@ -309,6 +311,8 @@ const TrackTileComponent = (props: TrackTileProps) => {
309311
onPressWithPropagationBlock={handlePressWithPropagationBlock}
310312
title={track.title}
311313
userId={user.user_id}
314+
userName={user.name}
315+
collaborators={track.collaborators}
312316
isPlayingUid={isPlayingUid}
313317
type='track'
314318
trackId={track.track_id}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { getTrackArtistNames } from './artistNames'
2+
3+
describe('getTrackArtistNames', () => {
4+
it('combines owner and collaborator names', () => {
5+
expect(
6+
getTrackArtistNames('ray61626b', [
7+
{ user_id: 2, name: 'dj g8r' },
8+
{ user_id: 3, name: 'tim' }
9+
])
10+
).toBe('ray61626b, dj g8r, tim')
11+
})
12+
13+
it('omits missing collaborator names', () => {
14+
expect(
15+
getTrackArtistNames('ray61626b', [
16+
{ user_id: 2, name: null },
17+
{ user_id: 3, name: undefined },
18+
{ user_id: 4, name: 'dj g8r' }
19+
])
20+
).toBe('ray61626b, dj g8r')
21+
})
22+
})
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { ID } from '@audius/common/models'
2+
3+
export type ArtistNameCollaborator = {
4+
user_id: ID
5+
name?: string | null
6+
}
7+
8+
export const getTrackArtistNames = (
9+
userName: string,
10+
collaborators?: ArtistNameCollaborator[] | null
11+
) => {
12+
const collaboratorNames =
13+
collaborators?.map((collaborator) => collaborator.name).filter(Boolean) ??
14+
[]
15+
16+
return [userName, ...collaboratorNames].join(', ')
17+
}

packages/mobile/src/components/lineup-tile/styles.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ export const useStyles = makeStyles(({ palette }) => ({
5656
},
5757
artist: {
5858
...flexRowCentered(),
59+
justifyContent: 'flex-start',
60+
gap: spacing(1),
61+
width: '100%',
5962
marginBottom: 'auto',
6063
paddingRight: spacing(10),
6164
minHeight: 20
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { render, screen, within } from '@testing-library/react-native'
2+
3+
import { TrackArtists } from './TrackArtists'
4+
5+
const mockUseUsers = jest.fn()
6+
7+
jest.mock('@audius/common/api', () => ({
8+
useUsers: (...args: unknown[]) => mockUseUsers(...args)
9+
}))
10+
11+
jest.mock(
12+
'@audius/harmony-native',
13+
() => {
14+
const React = require('react')
15+
const { Text, View } = require('react-native')
16+
17+
return {
18+
Flex: ({ children, style, testID }: any) =>
19+
React.createElement(View, { style, testID }, children),
20+
Text: ({ children }: any) => React.createElement(Text, null, children),
21+
TextLink: ({ children, to }: any) =>
22+
React.createElement(Text, null, `${to.params.id}:${children}`)
23+
}
24+
},
25+
{ virtual: true }
26+
)
27+
28+
jest.mock('./UserLink', () => {
29+
const React = require('react')
30+
const { Text } = require('react-native')
31+
32+
return {
33+
UserLink: ({
34+
hideBadges,
35+
userId
36+
}: {
37+
hideBadges?: boolean
38+
userId: number
39+
}) =>
40+
React.createElement(
41+
Text,
42+
null,
43+
`${userId}:${hideBadges ? 'badges-hidden' : 'badges-visible'}`
44+
)
45+
}
46+
})
47+
48+
jest.mock('../user-badges', () => {
49+
const React = require('react')
50+
const { Text } = require('react-native')
51+
52+
return {
53+
UserBadges: ({ userId }: { userId: number }) =>
54+
React.createElement(Text, null, `badges:${userId}`)
55+
}
56+
})
57+
58+
describe('TrackArtists', () => {
59+
beforeEach(() => {
60+
mockUseUsers.mockReturnValue({
61+
byId: {
62+
1: { name: 'ray61626b' },
63+
2: { name: 'dj g8r' }
64+
}
65+
})
66+
})
67+
68+
it('centers artist groups with each artist badge beside its name', () => {
69+
render(<TrackArtists userId={1} collaborators={[{ user_id: 2 }]} />)
70+
71+
const ownerGroup = screen.getByTestId('track-artist-1')
72+
const collaboratorGroup = screen.getByTestId('track-artist-2')
73+
74+
expect(mockUseUsers).toHaveBeenCalledWith([1, 2])
75+
expect(within(ownerGroup).getByText('1:ray61626b')).toBeOnTheScreen()
76+
expect(within(ownerGroup).getByText('badges:1')).toBeOnTheScreen()
77+
expect(within(collaboratorGroup).getByText('2:dj g8r')).toBeOnTheScreen()
78+
expect(within(collaboratorGroup).getByText('badges:2')).toBeOnTheScreen()
79+
})
80+
})

0 commit comments

Comments
 (0)