diff --git a/packages/mobile/src/components/lineup-tile/TrackRowTile.tsx b/packages/mobile/src/components/lineup-tile/TrackRowTile.tsx
new file mode 100644
index 00000000000..6e3b0565a14
--- /dev/null
+++ b/packages/mobile/src/components/lineup-tile/TrackRowTile.tsx
@@ -0,0 +1,253 @@
+import { memo, useCallback, useContext } from 'react'
+
+import { useCurrentUserId, useTrack, useUser } from '@audius/common/api'
+import { useGatedTrackAccess } from '@audius/common/hooks'
+import {
+ ModalSource,
+ PlaybackSource,
+ SquareSizes,
+ isContentUSDCPurchaseGated
+} from '@audius/common/models'
+import {
+ PurchaseableContentType,
+ gatedContentActions,
+ playbackPositionSelectors,
+ playbackSelectors,
+ usePremiumContentPurchaseModal
+} from '@audius/common/store'
+import type { CommonState } from '@audius/common/store'
+import { Genre, formatLineupTileDuration } from '@audius/common/utils'
+import { View } from 'react-native'
+import { useDispatch, useSelector } from 'react-redux'
+
+import {
+ Flex,
+ IconPin,
+ IconPlay,
+ IconText,
+ Text,
+ TrackRow
+} from '@audius/harmony-native'
+import { LineupContext } from 'app/components/lineup/LineupContext'
+import type { TrackTileProps } from 'app/components/lineup-tile/types'
+import { AnimatedEqBars } from 'app/components/track-list/AnimatedEqBars'
+import { UserBadges } from 'app/components/user-badges'
+import * as haptics from 'app/haptics'
+import { useIsUSDCEnabled } from 'app/hooks/useIsUSDCEnabled'
+import { setVisibility } from 'app/store/drawers/slice'
+
+import { TrackImage } from '../image/TrackImage'
+
+import { LineupTileAccessStatus } from './LineupTileAccessStatus'
+import { useOpenTrackOverflow } from './useOpenTrackOverflow'
+
+const { getTrackId, getPlaying } = playbackSelectors
+const { getTrackPosition } = playbackPositionSelectors
+const { setLockedContentId } = gatedContentActions
+
+const messages = {
+ artistPick: 'Artist Pick',
+ timeLeft: 'left',
+ played: 'Played',
+ play: (title: string) => `Play ${title}`,
+ pause: (title: string) => `Pause ${title}`
+}
+
+const overlayStyle = {
+ position: 'absolute' as const,
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ // Above Artwork's image, which sets zIndex 2
+ zIndex: 3,
+ alignItems: 'center' as const,
+ backgroundColor: 'rgba(0,0,0,0.45)'
+}
+
+const TrackRowTileComponent = (props: TrackTileProps) => {
+ const {
+ id,
+ uid,
+ index,
+ isTrending,
+ togglePlay,
+ onPress,
+ showArtistPick = false,
+ source
+ } = props
+
+ const dispatch = useDispatch()
+ const { isOnArtistsTracksTab } = useContext(LineupContext)
+ const { data: currentUserId } = useCurrentUserId()
+ const { onOpen: openPremiumContentPurchaseModal } =
+ usePremiumContentPurchaseModal()
+ const isUSDCEnabled = useIsUSDCEnabled()
+ const { hasStreamAccess } = useGatedTrackAccess(id)
+
+ const { data: track } = useTrack(id, {
+ select: (track) => ({
+ track_id: track.track_id,
+ title: track.title,
+ owner_id: track.owner_id,
+ duration: track.duration,
+ genre: track.genre,
+ stream_conditions: track.stream_conditions,
+ preview_cid: track.preview_cid,
+ is_delete: track.is_delete
+ })
+ })
+ const { data: user } = useUser(track?.owner_id, {
+ select: (user) => ({
+ name: user.name,
+ artist_pick_track_id: user.artist_pick_track_id,
+ is_deactivated: user.is_deactivated
+ })
+ })
+
+ const isActive = useSelector((state: CommonState) => getTrackId(state) === id)
+ const isPlaying = useSelector(
+ (state: CommonState) => getTrackId(state) === id && getPlaying(state)
+ )
+ const playbackPosition = useSelector((state) =>
+ getTrackPosition(state, { trackId: id, userId: currentUserId })
+ )
+
+ const handlePressOverflow = useOpenTrackOverflow(id, {
+ includeSocial: true,
+ hideViewArtist: isOnArtistsTracksTab
+ })
+
+ const handleLongPress = useCallback(() => {
+ haptics.medium()
+ handlePressOverflow()
+ }, [handlePressOverflow])
+
+ const handlePress = useCallback(() => {
+ if (!track) return
+ const isStreamGated = !!track.stream_conditions
+ if (isStreamGated && !hasStreamAccess && !track.preview_cid) {
+ if (
+ isUSDCEnabled &&
+ isContentUSDCPurchaseGated(track.stream_conditions)
+ ) {
+ openPremiumContentPurchaseModal(
+ {
+ contentId: track.track_id,
+ contentType: PurchaseableContentType.TRACK
+ },
+ { source: ModalSource.LineUpTrackTile }
+ )
+ } else {
+ dispatch(setLockedContentId({ id: track.track_id }))
+ dispatch(setVisibility({ drawer: 'LockedContent', visible: true }))
+ }
+ return
+ }
+ togglePlay({ uid, id: track.track_id, source: PlaybackSource.TRACK_TILE })
+ onPress?.(track.track_id)
+ }, [
+ track,
+ hasStreamAccess,
+ isUSDCEnabled,
+ openPremiumContentPurchaseModal,
+ dispatch,
+ togglePlay,
+ uid,
+ onPress
+ ])
+
+ const rank = isTrending && index !== undefined ? index + 1 : undefined
+
+ if (!track || !user) return
+ if (track.is_delete || user.is_deactivated) return null
+
+ const isOwner = currentUserId === track.owner_id
+ const isLocked = !!track.stream_conditions && !hasStreamAccess && !isOwner
+ const isLongForm =
+ track.genre === Genre.Podcasts || track.genre === Genre.Audiobooks
+
+ const duration = !track.duration
+ ? undefined
+ : isLongForm && playbackPosition?.status === 'COMPLETED'
+ ? messages.played
+ : isLongForm && playbackPosition?.status === 'IN_PROGRESS'
+ ? `${formatLineupTileDuration(
+ track.duration - playbackPosition.playbackPosition,
+ true
+ )} ${messages.timeLeft}`
+ : formatLineupTileDuration(track.duration, isLongForm)
+
+ const isArtistPick =
+ showArtistPick && user.artist_pick_track_id === track.track_id
+
+ return (
+
+
+ {isActive ? (
+
+ {isPlaying ? (
+
+ ) : (
+
+ )}
+
+ ) : null}
+ >
+ }
+ title={track.title}
+ subtitle={
+
+
+ {user.name}
+
+
+
+ }
+ context={
+ isArtistPick ? (
+ {messages.artistPick}
+ ) : undefined
+ }
+ status={
+ isLocked && track.stream_conditions ? (
+
+ ) : undefined
+ }
+ duration={duration}
+ isActive={isActive}
+ onPress={handlePress}
+ onLongPress={handleLongPress}
+ accessibilityLabel={
+ isPlaying ? messages.pause(track.title) : messages.play(track.title)
+ }
+ />
+ )
+}
+
+export const TrackRowTile = memo(TrackRowTileComponent)
diff --git a/packages/mobile/src/components/lineup-tile/TrackTile.tsx b/packages/mobile/src/components/lineup-tile/TrackTile.tsx
index ae53d8830c1..0c3df259523 100644
--- a/packages/mobile/src/components/lineup-tile/TrackTile.tsx
+++ b/packages/mobile/src/components/lineup-tile/TrackTile.tsx
@@ -18,12 +18,8 @@ import {
} from '@audius/common/models'
import {
tracksSocialActions,
- mobileOverflowMenuUIActions,
shareModalUIActions,
- OverflowAction,
- OverflowSource,
playbackSelectors,
- playbackPositionSelectors,
trackPageActions,
PurchaseableContentType,
gatedContentActions,
@@ -31,7 +27,7 @@ import {
usePublishConfirmationModal
} from '@audius/common/store'
import type { CommonState } from '@audius/common/store'
-import { Genre, removeNullable } from '@audius/common/utils'
+import { Genre } from '@audius/common/utils'
import { useDispatch, useSelector } from 'react-redux'
import { Paper, type ImageProps } from '@audius/harmony-native'
@@ -48,12 +44,11 @@ import { LineupTileActionButtons } from './LineupTileActionButtons'
import { LineupTileMetadata } from './LineupTileMetadata'
import { TilePressBlockContext } from './TilePressBlockContext'
import { TrackTileStats } from './TrackTileStats'
+import { useOpenTrackOverflow } from './useOpenTrackOverflow'
const { getTrackId } = playbackSelectors
const { requestOpen: requestOpenShareModal } = shareModalUIActions
-const { open: openOverflowMenu } = mobileOverflowMenuUIActions
const { repostTrack, undoRepostTrack } = tracksSocialActions
-const { getTrackPosition } = playbackPositionSelectors
const { setLockedContentId } = gatedContentActions
const TrackTileComponent = (props: TrackTileProps) => {
@@ -195,60 +190,9 @@ const TrackTileComponent = (props: TrackTileProps) => {
childPressedRef.current = true
}, [])
- const playbackPositionInfo = useSelector((state) =>
- getTrackPosition(state, {
- trackId: track?.track_id ?? 0,
- userId: currentUserId
- })
- )
-
- const handlePressOverflow = useCallback(() => {
- if (!track) return
- const isLongFormContent =
- track.genre === Genre.Podcasts || track.genre === Genre.Audiobooks
- const isOwner = currentUserId === track.owner_id
- const isArtistPick =
- isOwner && user?.artist_pick_track_id === track.track_id
-
- const overflowActions = [
- !track.is_unlisted || isOwner ? OverflowAction.PLAY_NEXT : null,
- !track.is_unlisted || isOwner ? OverflowAction.ADD_TO_QUEUE : null,
- isOwner && !track.ddex_app ? OverflowAction.ADD_TO_ALBUM : null,
- !track.is_unlisted || isOwner ? OverflowAction.ADD_TO_PLAYLIST : null,
- isLongFormContent
- ? OverflowAction.VIEW_EPISODE_PAGE
- : OverflowAction.VIEW_TRACK_PAGE,
- track.album_backlink ? OverflowAction.VIEW_ALBUM_PAGE : null,
- isLongFormContent
- ? playbackPositionInfo?.status === 'COMPLETED'
- ? OverflowAction.MARK_AS_UNPLAYED
- : OverflowAction.MARK_AS_PLAYED
- : null,
- isOwner && !isArtistPick ? OverflowAction.SET_ARTIST_PICK : null,
- isArtistPick ? OverflowAction.UNSET_ARTIST_PICK : null,
- isOnArtistsTracksTab ? null : OverflowAction.VIEW_ARTIST_PAGE,
- isOwner && !track.ddex_app ? OverflowAction.EDIT_TRACK : null,
- isOwner && track?.is_scheduled_release && track?.is_unlisted
- ? OverflowAction.RELEASE_NOW
- : null,
- isOwner && !track.ddex_app ? OverflowAction.DELETE_TRACK : null
- ].filter(removeNullable)
-
- dispatch(
- openOverflowMenu({
- source: OverflowSource.TRACKS,
- id: track.track_id,
- overflowActions
- })
- )
- }, [
- track,
- currentUserId,
- user?.artist_pick_track_id,
- isOnArtistsTracksTab,
- playbackPositionInfo?.status,
- dispatch
- ])
+ const handlePressOverflow = useOpenTrackOverflow(id, {
+ hideViewArtist: isOnArtistsTracksTab
+ })
const handlePressShare = useCallback(() => {
if (!track) return
diff --git a/packages/mobile/src/components/lineup-tile/index.ts b/packages/mobile/src/components/lineup-tile/index.ts
index 962a5ed7fb4..2c7cc4db87f 100644
--- a/packages/mobile/src/components/lineup-tile/index.ts
+++ b/packages/mobile/src/components/lineup-tile/index.ts
@@ -2,3 +2,4 @@ export { CollectionTile } from './CollectionTile'
export { CollectionTileSkeleton } from './CollectionTileSkeleton'
export { LineupTileSkeleton } from './LineupTileSkeleton'
export { TrackTile } from './TrackTile'
+export { TrackRowTile } from './TrackRowTile'
diff --git a/packages/mobile/src/components/lineup-tile/useOpenTrackOverflow.ts b/packages/mobile/src/components/lineup-tile/useOpenTrackOverflow.ts
new file mode 100644
index 00000000000..426d75f8f1e
--- /dev/null
+++ b/packages/mobile/src/components/lineup-tile/useOpenTrackOverflow.ts
@@ -0,0 +1,117 @@
+import { useCallback } from 'react'
+
+import { useCurrentUserId, useTrack, useUser } from '@audius/common/api'
+import { useGatedTrackAccess } from '@audius/common/hooks'
+import type { ID } from '@audius/common/models'
+import {
+ mobileOverflowMenuUIActions,
+ OverflowAction,
+ OverflowSource,
+ playbackPositionSelectors
+} from '@audius/common/store'
+import { Genre, removeNullable } from '@audius/common/utils'
+import { useDispatch, useSelector } from 'react-redux'
+
+const { open: openOverflowMenu } = mobileOverflowMenuUIActions
+const { getTrackPosition } = playbackPositionSelectors
+
+type Options = {
+ /**
+ * Adds share, favorite and repost. Rows have no inline buttons for these,
+ * so they go in the menu.
+ */
+ includeSocial?: boolean
+ hideViewArtist?: boolean
+}
+
+/** Opens the track overflow menu used by lineup tiles and rows. */
+export const useOpenTrackOverflow = (id: ID, options: Options = {}) => {
+ const { includeSocial = false, hideViewArtist = false } = options
+ const dispatch = useDispatch()
+ const { data: currentUserId } = useCurrentUserId()
+ const { hasStreamAccess } = useGatedTrackAccess(id)
+
+ const { data: track } = useTrack(id, {
+ select: (track) => ({
+ track_id: track.track_id,
+ owner_id: track.owner_id,
+ genre: track.genre,
+ is_unlisted: track.is_unlisted,
+ is_scheduled_release: track.is_scheduled_release,
+ ddex_app: track.ddex_app,
+ album_backlink: track.album_backlink,
+ has_current_user_saved: track.has_current_user_saved,
+ has_current_user_reposted: track.has_current_user_reposted
+ })
+ })
+ const { data: artistPickTrackId } = useUser(track?.owner_id, {
+ select: (user) => user.artist_pick_track_id
+ })
+ const playbackStatus = useSelector(
+ (state) =>
+ getTrackPosition(state, { trackId: id, userId: currentUserId })?.status
+ )
+
+ return useCallback(() => {
+ if (!track) return
+ const isLongFormContent =
+ track.genre === Genre.Podcasts || track.genre === Genre.Audiobooks
+ const isOwner = currentUserId === track.owner_id
+ const isArtistPick = isOwner && artistPickTrackId === track.track_id
+ const canShare = !track.is_unlisted || isOwner
+ const canEngage =
+ includeSocial && !isOwner && !track.is_unlisted && hasStreamAccess
+
+ const overflowActions = [
+ !track.is_unlisted || isOwner ? OverflowAction.PLAY_NEXT : null,
+ !track.is_unlisted || isOwner ? OverflowAction.ADD_TO_QUEUE : null,
+ includeSocial && canShare ? OverflowAction.SHARE : null,
+ canEngage
+ ? track.has_current_user_saved
+ ? OverflowAction.UNFAVORITE
+ : OverflowAction.FAVORITE
+ : null,
+ canEngage
+ ? track.has_current_user_reposted
+ ? OverflowAction.UNREPOST
+ : OverflowAction.REPOST
+ : null,
+ isOwner && !track.ddex_app ? OverflowAction.ADD_TO_ALBUM : null,
+ !track.is_unlisted || isOwner ? OverflowAction.ADD_TO_PLAYLIST : null,
+ isLongFormContent
+ ? OverflowAction.VIEW_EPISODE_PAGE
+ : OverflowAction.VIEW_TRACK_PAGE,
+ track.album_backlink ? OverflowAction.VIEW_ALBUM_PAGE : null,
+ isLongFormContent
+ ? playbackStatus === 'COMPLETED'
+ ? OverflowAction.MARK_AS_UNPLAYED
+ : OverflowAction.MARK_AS_PLAYED
+ : null,
+ isOwner && !isArtistPick ? OverflowAction.SET_ARTIST_PICK : null,
+ isArtistPick ? OverflowAction.UNSET_ARTIST_PICK : null,
+ hideViewArtist ? null : OverflowAction.VIEW_ARTIST_PAGE,
+ isOwner && !track.ddex_app ? OverflowAction.EDIT_TRACK : null,
+ isOwner && track.is_scheduled_release && track.is_unlisted
+ ? OverflowAction.RELEASE_NOW
+ : null,
+ isOwner && !track.ddex_app ? OverflowAction.DELETE_TRACK : null
+ ].filter(removeNullable)
+
+ dispatch(
+ openOverflowMenu({
+ source: OverflowSource.TRACKS,
+ id: track.track_id,
+ overflowActions
+ })
+ )
+ }, [
+ track,
+ currentUserId,
+ artistPickTrackId,
+ includeSocial,
+ hasStreamAccess,
+ hideViewArtist,
+ playbackStatus,
+ dispatch
+ ])
+}
diff --git a/packages/mobile/src/components/lineup/TrackLineup.tsx b/packages/mobile/src/components/lineup/TrackLineup.tsx
index bebabeee22b..0e8c3fb7b1c 100644
--- a/packages/mobile/src/components/lineup/TrackLineup.tsx
+++ b/packages/mobile/src/components/lineup/TrackLineup.tsx
@@ -36,10 +36,12 @@ import type {
import { StyleSheet, View } from 'react-native'
import { useDispatch, useStore } from 'react-redux'
+import { TrackRow } from '@audius/harmony-native'
import { SectionList } from 'app/components/core'
import { useBottomChinHeight } from 'app/components/core/BottomChin'
import {
TrackTile,
+ TrackRowTile,
CollectionTile,
LineupTileSkeleton
} from 'app/components/lineup-tile'
@@ -72,7 +74,10 @@ type Section = {
const SkeletonTileView = memo(function SkeletonTileView(props: {
itemStyles?: ViewStyle
+ isRow?: boolean
+ rank?: number
}) {
+ if (props.isRow) return
return (
@@ -113,6 +118,8 @@ export type TrackLineupProps = {
initialPageSize?: number
maxEntries?: number
isTrending?: boolean
+ /** 'row' renders tracks as dense rows; collections stay tiles. */
+ tileVariant?: 'tile' | 'row'
rankIconCount?: number
showArtistPick?: boolean
header?: SectionListProps['ListHeaderComponent']
@@ -166,6 +173,7 @@ export const TrackLineup = ({
initialPageSize,
maxEntries = Infinity,
isTrending = false,
+ tileVariant = 'tile',
rankIconCount = 0,
showArtistPick = false,
header,
@@ -358,13 +366,37 @@ export const TrackLineup = ({
maxEntries
])
+ const isRow = tileVariant === 'row'
+
const renderItem = useCallback(
({ item, index }: { item: RenderItem; index: number }) => {
if ((item as LoadingItem)._loading) {
- return
+ return (
+
+ )
}
const entry = item as Entry
const delineator = delineatorMap?.[index] ?? null
+ if (isRow && entry.kind === 'track') {
+ return (
+ <>
+
+ {delineator}
+ >
+ )
+ }
return (
<>
@@ -395,6 +427,7 @@ export const TrackLineup = ({
},
[
itemStyles,
+ isRow,
isTrending,
togglePlay,
onPressItem,
diff --git a/packages/mobile/src/harmony-native/components/TrackRow/TrackRow.tsx b/packages/mobile/src/harmony-native/components/TrackRow/TrackRow.tsx
new file mode 100644
index 00000000000..d1fe608e99c
--- /dev/null
+++ b/packages/mobile/src/harmony-native/components/TrackRow/TrackRow.tsx
@@ -0,0 +1,175 @@
+import type { ReactNode } from 'react'
+
+import { useTheme } from '@emotion/react'
+import { Pressable, View } from 'react-native'
+
+import { Skeleton } from '../Skeleton'
+import { Text } from '../Text/Text'
+
+export const TRACK_ROW_HEIGHT = 64
+const ARTWORK_SIZE = 48
+const RANK_WIDTH = 20
+
+// Skeleton fills its parent absolutely by default; rows lay it out inline.
+const skeletonStyle = { position: 'relative' as const, borderRadius: 4 }
+
+export type TrackRowProps = {
+ /** 1-based position shown before the artwork. Omit for unranked lists. */
+ rank?: number
+ /** 48px square artwork, including any playing overlay. */
+ artwork?: ReactNode
+ title?: string
+ /** Artist line under the title. */
+ subtitle?: ReactNode
+ /** Shown in place of the duration, e.g. a price button on a locked track. */
+ status?: ReactNode
+ /** Pre-formatted duration, e.g. "2:30". */
+ duration?: string
+ /** One short line above the title, such as "Artist Pick". */
+ context?: ReactNode
+ /** This track is the one loaded in the player. */
+ isActive?: boolean
+ isLoading?: boolean
+ isDisabled?: boolean
+ onPress?: () => void
+ /** Opens the track's actions. Also exposed to screen readers as an action. */
+ onLongPress?: () => void
+ /** Accessible name for the row's press action. */
+ accessibilityLabel?: string
+}
+
+export const TrackRow = (props: TrackRowProps) => {
+ const {
+ rank,
+ artwork,
+ title,
+ subtitle,
+ status,
+ duration,
+ context,
+ isActive,
+ isLoading,
+ isDisabled,
+ onPress,
+ onLongPress,
+ accessibilityLabel
+ } = props
+ const { color, spacing } = useTheme()
+
+ const rankCell =
+ rank === undefined ? null : (
+
+
+ {rank}
+
+
+ )
+ const paddingLeft = rank === undefined ? spacing.l : spacing.m
+
+ if (isLoading) {
+ return (
+
+ {rankCell}
+
+
+
+
+
+
+ )
+ }
+
+ return (
+ {
+ if (event.nativeEvent.actionName === 'longpress') onLongPress?.()
+ }}
+ style={({ pressed }) => ({
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: spacing.m,
+ minHeight: TRACK_ROW_HEIGHT,
+ paddingVertical: spacing.s,
+ paddingLeft,
+ paddingRight: spacing.l,
+ backgroundColor: pressed ? color.background.surface1 : undefined,
+ opacity: isDisabled ? 0.5 : 1
+ })}
+ >
+ {rankCell}
+
+ {artwork}
+
+
+ {context}
+
+ {title}
+
+ {subtitle}
+
+ {status ??
+ (duration ? (
+
+ {duration}
+
+ ) : null)}
+
+ )
+}
diff --git a/packages/mobile/src/harmony-native/components/index.ts b/packages/mobile/src/harmony-native/components/index.ts
index f82a532997b..ce2cc849d51 100644
--- a/packages/mobile/src/harmony-native/components/index.ts
+++ b/packages/mobile/src/harmony-native/components/index.ts
@@ -36,3 +36,4 @@ export * from './Skeleton'
export * from './NotificationCount/NotificationCount'
export * from './LoadingSpinner/LoadingSpinner'
export * from './IconLogoWhiteBackground'
+export * from './TrackRow/TrackRow'
diff --git a/packages/mobile/src/screens/profile-screen/ProfileTabs/RepostsTab.tsx b/packages/mobile/src/screens/profile-screen/ProfileTabs/RepostsTab.tsx
index 928f02b3280..f97cb4efb49 100644
--- a/packages/mobile/src/screens/profile-screen/ProfileTabs/RepostsTab.tsx
+++ b/packages/mobile/src/screens/profile-screen/ProfileTabs/RepostsTab.tsx
@@ -5,12 +5,17 @@ import {
useProfileReposts,
getProfileRepostsQueryKey
} from '@audius/common/api'
+import { useFeatureFlag } from '@audius/common/hooks'
+import { FeatureFlags } from '@audius/common/services'
import { TrackLineup } from 'app/components/lineup/TrackLineup'
import { EmptyProfileTile } from '../EmptyProfileTile'
export const RepostsTab = () => {
+ const { isEnabled: isTrackRowsEnabled } = useFeatureFlag(
+ FeatureFlags.TRACK_ROWS
+ )
const { handle, repost_count = 0 } =
useProfileUser({
select: (user) => ({
@@ -45,6 +50,7 @@ export const RepostsTab = () => {
return (
{
+ const { isEnabled: isTrackRowsEnabled } = useFeatureFlag(
+ FeatureFlags.TRACK_ROWS
+ )
const { handle, track_count = 0 } =
useProfileUser({
select: (user) => ({
@@ -43,6 +48,7 @@ export const TracksTab = () => {
return (
{
+ const { isEnabled: isTrackRowsEnabled } = useFeatureFlag(
+ FeatureFlags.TRACK_ROWS
+ )
const [query] = useSearchQuery()
const [filters] = useSearchFilters()
const {
@@ -68,6 +73,7 @@ export const TrackResults = () => {
return (
{
+ const { isEnabled: isTrackRowsEnabled } = useFeatureFlag(
+ FeatureFlags.TRACK_ROWS
+ )
const navigation = useNavigation()
const dispatch = useDispatch()
const genre = useSelector(getTrendingGenre)
@@ -75,6 +80,7 @@ export const TrendingLineup = ({
return (
{
+ const { isEnabled: isTrackRowsEnabled } = useFeatureFlag(
+ FeatureFlags.TRACK_ROWS
+ )
const { trackIds, isPending, isFetching, hasNextPage, loadNextPage } =
useTrendingUnderground({ pageSize: PAGE_SIZE })
@@ -35,6 +40,7 @@ export const TrendingUndergroundLineup = ({
return (