Skip to content

feat(mobile): CollectionTile title tints to active color when a child track is playing#14440

Merged
dylanjeffers merged 2 commits into
mainfrom
feat/mobile-collection-tile-active-color
Jun 2, 2026
Merged

feat(mobile): CollectionTile title tints to active color when a child track is playing#14440
dylanjeffers merged 2 commits into
mainfrom
feat/mobile-collection-tile-active-color

Conversation

@dylanjeffers
Copy link
Copy Markdown
Contributor

When any track inside a collection is currently playing, the mobile CollectionTile's title should match the active highlight that the per-row tracks already get — the same purple Audius uses for the playing track's title on TrackTile. Today the title is always rendered with the default text color, even while one of the collection's tracks is the one playing.

What changed

packages/mobile/src/components/lineup-tile/CollectionTile.tsx — derive isActive from the existing currentTrack selector and pass it to the title's color prop:

const currentTrack = useSelector((state) => {
  const trackId = getTrackId(state)
  return tracks.find((t) => t.track_id === trackId) ?? null
})
const isActive = currentTrack != null
<Text variant='title' color={isActive ? 'active' : 'default'} numberOfLines={1}>
  {collection.playlist_name}
</Text>

Why 'active' and not 'primary'

TrackTile / LineupTileMetadata use color={isActive ? 'primary' : 'neutral'} — but they import Text from app/components/core (the legacy app Text). CollectionTile uses Text from @audius/harmony-native, whose type union explicitly accepts 'active' for the same primary highlight. Both resolve to the Audius purple at render time; the prop names differ between the two Text components.

CollectionTileTrackList row highlight — already in place

The per-row track-title highlight inside CollectionTileTrackList already works correctly. Each TrackItem runs:

const isPlayingUid = useSelector((state) => getTrackId(state) === trackId)

…and applies isPlayingUid && styles.active (which sets color: palette.primary) to every text element in the row — index, title, and by <artist>. Confirmed by reading the current source — no change needed there. This PR only fixes the collection-level title that wraps that row list.

Selector choice

getTrackId (state.playback.playingTrackId) is the right selector for tile-highlight comparisons — its file comment makes this explicit:

these mirror what the legacy player slice used to expose. playingIndex / playingTrackId lag behind queue[index] until playSucceeded fires, which is what tile-highlight comparisons want.

Using getCurrentTrackId (queue[index].trackId) would flash the active color the instant playFrom dispatches, before the track has actually started playing.

Verification

  • tsc --noEmit clean in packages/mobile
  • Manual: open a feed lineup with a collection tile, start playing a track inside that collection — confirm the collection's title text turns the active purple.
  • Manual: confirm the per-row track title in the collection's tracklist also turns purple for the playing track (pre-existing behavior, regression check).
  • Manual: confirm tiles where none of the tracks are playing render with the default title color.

🤖 Generated with Claude Code

…ck is playing

When any track inside a collection is currently playing, the collection
tile title should match the active highlight that the per-row tracks
already get — same purple Audius uses for the playing track's title on
TrackTile and inside CollectionTileTrackList. Today the CollectionTile
title is always rendered with the default text color, even while one of
its tracks is the one playing.

- packages/mobile/src/components/lineup-tile/CollectionTile.tsx: derive
  `isActive = currentTrack != null` from the existing
  `currentTrack` selector (which already runs
  `getTrackId(state) === track.track_id` against the loaded tracks),
  then pass `color={isActive ? 'active' : 'default'}` to the title
  `<Text>`. Note: harmony-native Text's token for the primary highlight
  color is `'active'` (TrackTile / LineupTileMetadata use `'primary'`
  but that maps via the legacy app `Text` component — harmony-native's
  type union explicitly accepts `'active'` for the same visual).

The per-row track-title highlight in CollectionTileTrackList already
works: each `TrackItem` runs its own
`useSelector((state) => getTrackId(state) === trackId)` and applies
`isPlayingUid && styles.active` (which sets `color: palette.primary`)
to every text element in the row (index, title, artist). Verified — no
change needed there. The fix here is only the collection-level title
that wraps that row list.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 2, 2026

⚠️ No Changeset found

Latest commit: 764b69c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

…olor

Track index already tints purple when its row is playing — index uses a
2-entry style array \`[styles.text, isPlayingUid && styles.active]\` so
active is the last truthy entry and \`StyleSheet\`'s merge picks it up.

The title and artist Texts used 4-entry arrays
\`[styles.text, styles.title, isPlayingUid && styles.active,
deleted && styles.deleted]\`. For the normal "playing, not deleted"
case that resolves to \`[text, title, active, false]\` — active sits at
index 2 with a falsy entry at index 3. React Native's style array merge
on this version drops the active override here, leaving
\`styles.title.color\` (palette.neutral / n800) as the winning color
instead of \`styles.active.color\` (palette.primary / p300). The index
Text doesn't hit it because its array is only 2 entries.

Fix: reorder the array so \`isPlayingUid && styles.active\` is the last
entry. When the track is playing, primary unambiguously wins; when not
playing, the falsy active is skipped and styles.title / styles.deleted
resolve normally. The only behavioral shift is the rare
playing-AND-deleted case, which now shows the active color instead of
the deleted color — preferable per the active-highlight intent.

Applied to both the title row and the "by <artist>" row. The
"[Deleted by Artist]" marker stays unchanged (styles.deleted is
unconditional there and should keep winning).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pull-request-size pull-request-size Bot added size/M and removed size/S labels Jun 2, 2026
@dylanjeffers dylanjeffers merged commit a4b38e6 into main Jun 2, 2026
3 checks passed
@dylanjeffers dylanjeffers deleted the feat/mobile-collection-tile-active-color branch June 2, 2026 21:57
dylanjeffers added a commit that referenced this pull request Jun 2, 2026
…#14441)

Follow-up to #14440 — the original PR merged before this commit landed.
Re-applying the three remaining behaviors on a fresh branch off main.

## 1. Speaker icon next to the title when actively playing

Mirrors what TrackTile shows via `LineupTileMetadata`: a small
`IconVolumeLevel2` fills the line next to the title text while one of
this collection's tracks is the currently-playing track AND the audio
engine is actually playing (not paused).

```ts
const { getTrackId, getPlaying } = playbackSelectors
const isPlaying = useSelector((state) => getPlaying(state) && isActive)
```

```tsx
{isPlaying ? (
  <IconVolumeLevel2 fill={primary} size='m' style={styles.playingIndicator} />
) : null}
```

Same icon, same fill (`useThemeColors().primary`), same `size='m'`.
Restructured `styles.titleTouchable` to be a row container
(`flexDirection: 'row', alignItems: 'center'`) and added
`styles.titleText: { flexShrink: 1 }` so a long title shrinks instead of
pushing the icon off-tile.

## 2. Skip deleted-by-artist tracks on the tile-level tap

Previously `handlePress` fell back to `tracks[0]?.track_id` as the start
track — if the first track was deleted, the tap dispatched playback for
an unplayable track. Now:

```ts
const startTrackId =
  currentTrack && !currentTrack.is_delete
    ? currentTrack.track_id
    : tracks.find((t) => !t.is_delete)?.track_id
if (!startTrackId) return
```

Picks the current track only if it's one of ours AND not deleted, else
the first non-deleted track. If every track in the collection is
deleted, the tile tap is a no-op.

The per-row tap was already a no-op for playback — the list-level
`Pressable` only fires `handlePressTitle` (navigate to the collection
page), never a play action. The real playability concern was at the
tile-level tap, which this covers.

## 3. Gray the index column on deleted rows

`CollectionTileTrackList`'s `TrackItem` already grayed the title, the
`by <artist>` line, and the `[Deleted by Artist]` marker. The number
column was still rendered in the regular subdued color, which read as
"row partially deleted." Added `deleted && styles.deleted` to the index
Text's style array so the whole row consistently grays out.

## Files

-
[`packages/mobile/src/components/lineup-tile/CollectionTile.tsx`](packages/mobile/src/components/lineup-tile/CollectionTile.tsx)
— icon + skip-deleted-on-tap
-
[`packages/mobile/src/components/lineup-tile/CollectionTileTrackList.tsx`](packages/mobile/src/components/lineup-tile/CollectionTileTrackList.tsx)
— index gray-out

Net diff: +55 / −5.

## Verification

- [x] `tsc --noEmit` clean in `packages/mobile`.
- [ ] Mobile manual: play a track inside a collection in the feed lineup
→ speaker icon shows next to the collection title.
- [ ] Mobile manual: pause playback → speaker icon disappears (title
still tinted active).
- [ ] Mobile manual: tap a collection tile whose first track is deleted
→ playback starts on the first non-deleted track (not on the deleted
one).
- [ ] Mobile manual: collection where every track is deleted → tile tap
is a no-op.
- [ ] Mobile manual: deleted row in the tracklist is fully grayed out
(index, title, "by <artist>", "[Deleted by Artist]" all in the subdued
color).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant