diff --git a/packages/mobile/src/components/lineup-tile/CollectionTile.tsx b/packages/mobile/src/components/lineup-tile/CollectionTile.tsx index f3210188047..96c7b25bf41 100644 --- a/packages/mobile/src/components/lineup-tile/CollectionTile.tsx +++ b/packages/mobile/src/components/lineup-tile/CollectionTile.tsx @@ -29,12 +29,19 @@ import { formatLineupTileDuration, removeNullable } from '@audius/common/utils' import { TouchableOpacity, View } from 'react-native' import { useDispatch, useSelector } from 'react-redux' -import { Flex, Paper, Text, type ImageProps } from '@audius/harmony-native' +import { + Flex, + IconVolumeLevel2, + Paper, + Text, + type ImageProps +} from '@audius/harmony-native' import { UserLink } from 'app/components/user-link' import { useNavigation } from 'app/hooks/useNavigation' import { setVisibility } from 'app/store/drawers/slice' import { getIsCollectionMarkedForDownload } from 'app/store/offline-downloads/selectors' import { makeStyles } from 'app/styles' +import { useThemeColors } from 'app/utils/theme' import { CollectionDogEar } from '../collection/CollectionDogEar' import { CollectionImage } from '../image/CollectionImage' @@ -45,7 +52,7 @@ import { LineupTileActionButtons } from './LineupTileActionButtons' import { TilePressBlockContext } from './TilePressBlockContext' import { LineupTileSource, type CollectionTileProps } from './types' -const { getTrackId } = playbackSelectors +const { getTrackId, getPlaying } = playbackSelectors const { requestOpen: requestOpenShareModal } = shareModalUIActions const { open: openOverflowMenu } = mobileOverflowMenuUIActions const { @@ -78,7 +85,15 @@ const useStyles = makeStyles(({ spacing }) => ({ gap: spacing(1) }, titleTouchable: { - width: '100%' + width: '100%', + flexDirection: 'row', + alignItems: 'center' + }, + titleText: { + flexShrink: 1 + }, + playingIndicator: { + marginLeft: 8 }, artistTouchable: { alignSelf: 'flex-start' @@ -104,6 +119,7 @@ export const CollectionTile = (props: CollectionTileProps) => { const dispatch = useDispatch() const navigation = useNavigation() const styles = useStyles() + const { primary } = useThemeColors() const { data: currentUserId } = useCurrentUserId() // Mirror the web mobile CollectionTile path exactly: fetch the collection @@ -135,6 +151,10 @@ export const CollectionTile = (props: CollectionTileProps) => { // already runs its own `getTrackId(state) === trackId` selector and // applies `styles.active` / `palette.primary` to its title text). const isActive = currentTrack != null + // True only while audio is actively playing (not paused) AND one of this + // collection's tracks is the current track. Mirrors LineupTileMetadata's + // `isPlaying` derivation — drives the speaker icon next to the title. + const isPlaying = useSelector((state) => getPlaying(state) && isActive) const isCollectionMarkedForDownload = useSelector((state) => collection @@ -165,7 +185,14 @@ export const CollectionTile = (props: CollectionTileProps) => { childPressedRef.current = false return } - const startTrackId = currentTrack?.track_id ?? tracks[0]?.track_id + // Don't try to play a deleted-by-artist track. Pick the current + // track (if it's one of ours and not deleted), else the first + // non-deleted track in the collection. If everything is deleted, + // the tile tap is a no-op. + const startTrackId = + currentTrack && !currentTrack.is_delete + ? currentTrack.track_id + : tracks.find((t) => !t.is_delete)?.track_id if (!startTrackId) return togglePlay({ id: startTrackId, @@ -312,9 +339,21 @@ export const CollectionTile = (props: CollectionTileProps) => { variant='title' color={isActive ? 'active' : 'default'} numberOfLines={1} + style={styles.titleText} > {collection.playlist_name} + {/* Speaker icon next to the title while one of this + collection's tracks is the currently-playing track and + the audio engine is actively playing (not paused). Same + icon + sizing TrackTile uses via LineupTileMetadata. */} + {isPlaying ? ( + + ) : null} { ) : !track ? null : ( <> - + {/* Index also picks up the deleted style so the whole row + grays out consistently — without this, the number column + stays the default subdued color while the title/artist + go further-subdued, which reads as "row partially + deleted" rather than the intended unavailable state. */} + {index + 1} {/*