diff --git a/packages/mobile/src/components/details-tile/DetailsTile.tsx b/packages/mobile/src/components/details-tile/DetailsTile.tsx
deleted file mode 100644
index 1ec41a10704..00000000000
--- a/packages/mobile/src/components/details-tile/DetailsTile.tsx
+++ /dev/null
@@ -1,415 +0,0 @@
-import { useCallback } from 'react'
-
-import { useGatedContentAccessMap } from '@audius/common/hooks'
-import { isContentUSDCPurchaseGated } from '@audius/common/models'
-import {
- accountSelectors,
- playerSelectors,
- playbackPositionSelectors,
- cacheCollectionsSelectors
-} from '@audius/common/store'
-import type { CommonState } from '@audius/common/store'
-import { getDogEarType, Genre, getLocalTimezone } from '@audius/common/utils'
-import moment from 'moment'
-import { TouchableOpacity } from 'react-native'
-import { useSelector } from 'react-redux'
-
-import {
- Flex,
- Text,
- IconCalendarMonth,
- IconPause,
- IconPlay,
- IconRepeatOff,
- Paper,
- spacing,
- Button,
- Divider,
- Box,
- MusicBadge,
- IconVisibilityHidden
-} from '@audius/harmony-native'
-import CoSign from 'app/components/co-sign/CoSign'
-import { Size } from 'app/components/co-sign/types'
-import { UserGeneratedText, DogEar, Tag } from 'app/components/core'
-import UserBadges from 'app/components/user-badges'
-import { light } from 'app/haptics'
-import { useNavigation } from 'app/hooks/useNavigation'
-import { makeStyles } from 'app/styles'
-
-import { OfflineStatusRow } from '../offline-downloads'
-
-import { CollectionMetadataList } from './CollectionMetadataList'
-import { DeletedTile } from './DeletedTile'
-import { DetailsProgressInfo } from './DetailsProgressInfo'
-import { DetailsTileActionButtons } from './DetailsTileActionButtons'
-import { DetailsTileAiAttribution } from './DetailsTileAiAttribution'
-import { DetailsTileHasAccess } from './DetailsTileHasAccess'
-import { DetailsTileNoAccess } from './DetailsTileNoAccess'
-import { DetailsTileStats } from './DetailsTileStats'
-import { TrackMetadataList } from './TrackMetadataList'
-import type { DetailsTileProps } from './types'
-
-const { getTrackId } = playerSelectors
-const { getTrackPosition } = playbackPositionSelectors
-const { getCollectionTracks } = cacheCollectionsSelectors
-
-const messages = {
- play: 'Play',
- pause: 'Pause',
- resume: 'Resume',
- replay: 'Replay',
- preview: 'Preview',
- hidden: 'Hidden',
- releases: (releaseDate: string) =>
- `Releases ${moment(releaseDate).format(
- 'M/D/YY [@] h:mm A'
- )} ${getLocalTimezone()}`
-}
-
-const useStyles = makeStyles(({ palette, spacing }) => ({
- coverArt: {
- borderWidth: 1,
- borderColor: palette.neutralLight8,
- borderRadius: spacing(2),
- height: 224,
- width: 224,
- alignSelf: 'center'
- }
-}))
-
-/**
- * The details shown at the top of the Track Screen and Collection Screen
- */
-export const DetailsTile = ({
- contentId,
- contentType,
- coSign,
- description,
- descriptionLinkPressSource,
- hasReposted,
- hasSaved,
- hasStreamAccess,
- streamConditions,
- hideFavorite,
- hideFavoriteCount,
- hidePlayCount,
- hideOverflow,
- hideRepost,
- hideRepostCount,
- hideShare,
- isPlaying,
- isPreviewing,
- isDeleted = false,
- isPlayable = true,
- isCollection = false,
- isPublished = true,
- isUnlisted = false,
- onPressEdit,
- onPressFavorites,
- onPressOverflow,
- onPressPlay,
- onPressPreview,
- onPressPublish,
- onPressRepost,
- onPressReposts,
- onPressSave,
- onPressShare,
- playCount,
- renderBottomContent,
- renderImage,
- repostCount,
- saveCount,
- headerText,
- title,
- user,
- track,
- ddexApp,
- releaseDate
-}: DetailsTileProps) => {
- const styles = useStyles()
- const navigation = useNavigation()
-
- const currentUserId = useSelector(accountSelectors.getUserId)
- const isCurrentTrack = useSelector((state: CommonState) => {
- return track && track.track_id === getTrackId(state)
- })
-
- const tracks = useSelector((state: CommonState) =>
- getCollectionTracks(state, { id: contentId })
- )
- const trackAccessMap = useGatedContentAccessMap(tracks ?? [])
- const doesUserHaveAccessToAnyTrack = Object.values(trackAccessMap).some(
- ({ hasStreamAccess }) => hasStreamAccess
- )
-
- const isOwner = user?.user_id === currentUserId
- const isLongFormContent =
- track?.genre === Genre.PODCASTS || track?.genre === Genre.AUDIOBOOKS
- const aiAttributionUserId = track?.ai_attribution_user_id
- const isUSDCPurchaseGated = isContentUSDCPurchaseGated(streamConditions)
-
- const isPlayingPreview = isPreviewing && isPlaying
- const isPlayingFullAccess = isPlaying && !isPreviewing
- const isUnpublishedScheduledRelease =
- track?.is_scheduled_release && track?.is_unlisted && releaseDate
-
- // Show play if user has access to the collection or any of its contents.
- // Show preview only if the user is the owner on a track screen.
- const shouldShowPlay =
- (isPlayable && hasStreamAccess) || doesUserHaveAccessToAnyTrack
- const shouldShowPreview =
- isUSDCPurchaseGated &&
- ((!isCollection && isOwner) || // own track
- (isCollection && !hasStreamAccess && !shouldShowPlay) || // premium collection
- (!isCollection && !hasStreamAccess)) && // premium track
- !!onPressPreview
-
- const handlePressArtistName = useCallback(() => {
- if (!user) {
- return
- }
- navigation.push('Profile', { handle: user.handle })
- }, [navigation, user])
-
- const handlePressPlay = useCallback(() => {
- light()
- onPressPlay()
- }, [onPressPlay])
-
- const handlePressPreview = useCallback(() => {
- light()
- onPressPreview?.()
- }, [onPressPreview])
-
- const renderDogEar = () => {
- const dogEarType = getDogEarType({
- isOwner,
- streamConditions
- })
- return dogEarType ? : null
- }
-
- const innerImageElement = renderImage({
- style: styles.coverArt
- })
-
- const imageElement = coSign ? (
- {innerImageElement}
- ) : (
- innerImageElement
- )
-
- const playbackPositionInfo = useSelector((state) =>
- getTrackPosition(state, { trackId: contentId, userId: currentUserId })
- )
-
- const playText = playbackPositionInfo?.status
- ? playbackPositionInfo?.status === 'IN_PROGRESS' || isCurrentTrack
- ? messages.resume
- : messages.replay
- : messages.play
-
- const PlayIcon =
- playbackPositionInfo?.status === 'COMPLETED' && !isCurrentTrack
- ? IconRepeatOff
- : IconPlay
-
- const PreviewButton = () => (
-
- )
-
- const badges = [
- aiAttributionUserId ? (
-
- ) : null,
- isUnpublishedScheduledRelease ? (
-
- {messages.releases(releaseDate)}
-
- ) : isUnlisted ? (
- {messages.hidden}
- ) : null
- ].filter((badge) => badge !== null)
-
- const handlePressTag = useCallback(
- (tag: string) => {
- navigation.push('TagSearch', { query: tag })
- },
- [navigation]
- )
-
- const renderTags = () => {
- if (!track || (isUnlisted && !track.field_visibility?.tags)) {
- return null
- }
-
- const filteredTags = (track.tags || '').split(',').filter(Boolean)
- return filteredTags.length > 0 ? (
-
- {filteredTags.map((tag) => (
- handlePressTag(tag)}>
- {tag}
-
- ))}
-
- ) : null
- }
-
- if (isDeleted) {
- return (
-
- )
- }
-
- return (
-
- {renderDogEar()}
-
-
- {headerText}
-
-
- {badges.length > 0 ? (
-
- {badges.map((badge) => badge)}
-
- ) : null}
- {imageElement}
-
-
- {title}
-
- {user ? (
-
-
-
- {user.name}
-
-
-
-
- ) : null}
-
- {isLongFormContent && track ? (
-
- ) : null}
- {shouldShowPlay ? (
-
- ) : null}
- {shouldShowPreview ? : null}
-
-
-
- {!hasStreamAccess && !isOwner && streamConditions && contentId ? (
-
- ) : null}
- {(hasStreamAccess || isOwner) && streamConditions ? (
-
- ) : null}
- {!isPublished ? null : (
-
- )}
- {description ? (
-
-
- {description}
-
-
- ) : null}
- {isCollection ? (
- contentId ? (
-
- ) : null
- ) : contentId ? (
-
- ) : null}
- {renderTags()}
-
-
-
- {renderBottomContent?.()}
-
- )
-}
diff --git a/packages/mobile/src/components/details-tile/index.ts b/packages/mobile/src/components/details-tile/index.ts
deleted file mode 100644
index 9762470b4a2..00000000000
--- a/packages/mobile/src/components/details-tile/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { DetailsTile } from './DetailsTile'
diff --git a/packages/mobile/src/screens/collection-screen/CollectionScreen.tsx b/packages/mobile/src/screens/collection-screen/CollectionScreen.tsx
index 6249e251820..3ff24677a8e 100644
--- a/packages/mobile/src/screens/collection-screen/CollectionScreen.tsx
+++ b/packages/mobile/src/screens/collection-screen/CollectionScreen.tsx
@@ -28,7 +28,7 @@ import {
favoritesUserListActions,
RepostType
} from '@audius/common/store'
-import { encodeUrlName, formatDate, removeNullable } from '@audius/common/utils'
+import { encodeUrlName, removeNullable } from '@audius/common/utils'
import type { Nullable } from '@audius/common/utils'
import { useDispatch, useSelector } from 'react-redux'
@@ -173,15 +173,6 @@ const CollectionScreenComponent = (props: CollectionScreenComponentProps) => {
const currentUserId = useSelector(getUserId)
const isOwner = currentUserId === playlist_owner_id
- const extraDetails = useMemo(
- () => [
- {
- label: 'Modified',
- value: formatDate(updated_at || Date.now().toString())
- }
- ],
- [updated_at]
- )
const isCollectionMarkedForDownload = useSelector(
getIsCollectionMarkedForDownload(playlist_id.toString())
@@ -286,12 +277,10 @@ const CollectionScreenComponent = (props: CollectionScreenComponentProps) => {
<>
{
return getCollectionTracksLineup(state).status !== Status.SUCCESS
}
-const selectCollectionDuration = createSelector(
- (state: AppState) => getCollectionTracksLineup(state).entries,
- (state: AppState) => state.tracks.entries,
- (entries, tracks) => {
- return entries
- .map((entry) => tracks[entry.id]?.metadata.duration)
- .filter(removeNullable)
- .reduce((totalDuration, trackDuration) => {
- return totalDuration + trackDuration
- }, 0)
- }
-)
-
const selectIsQueued = createSelector(
selectTrackUids,
getUid,
@@ -107,7 +120,17 @@ const getMessages = (
emptyPublic: `This ${collectionType} is empty`,
detailsPlaceholder: '---',
collectionType: `${isPremium ? 'premium ' : ''}${collectionType}`,
- hiddenType: `Hidden ${collectionType}`
+ hiddenType: `Hidden ${collectionType}`,
+ play: 'Play',
+ pause: 'Pause',
+ resume: 'Resume',
+ replay: 'Replay',
+ preview: 'Preview',
+ hidden: 'Hidden',
+ releases: (releaseDate: string) =>
+ `Releases ${dayjs(releaseDate).format(
+ 'M/D/YY [@] h:mm A'
+ )} ${getLocalTimezone()}`
})
const useStyles = makeStyles(({ palette, spacing }) => ({
@@ -117,16 +140,22 @@ const useStyles = makeStyles(({ palette, spacing }) => ({
marginBottom: spacing(8),
textAlign: 'center',
lineHeight: 20
+ },
+ coverArt: {
+ borderWidth: 1,
+ borderColor: palette.neutralLight8,
+ borderRadius: spacing(2),
+ height: 224,
+ width: 224,
+ alignSelf: 'center'
}
}))
type CollectionScreenDetailsTileProps = {
isAlbum?: boolean
- isPrivate?: boolean
isOwner?: boolean
isPublishing?: boolean
isDeleted?: boolean
- extraDetails?: DetailsTileDetail[]
collectionId: number | SmartCollectionVariant
hasStreamAccess?: boolean
streamConditions?: Nullable
@@ -153,25 +182,42 @@ const recordPlay = (id: Maybe, play = true) => {
export const CollectionScreenDetailsTile = ({
description,
- extraDetails = [],
collectionId,
isAlbum,
- isPrivate,
isPublishing,
renderImage,
trackCount: trackCountProp,
- isOwner,
+ isOwner = false,
hideOverflow,
hideRepost,
hideFavorite,
hasStreamAccess,
streamConditions,
ddexApp,
- isDeleted,
- ...detailsTileProps
+ hideShare,
+ playCount,
+ hidePlayCount,
+ hideFavoriteCount,
+ hideRepostCount,
+ repostCount,
+ saveCount,
+ hasSaved,
+ title,
+ hasReposted,
+ onPressEdit,
+ onPressFavorites,
+ onPressOverflow,
+ onPressPublish,
+ onPressRepost,
+ onPressReposts,
+ onPressSave,
+ onPressShare,
+ user,
+ releaseDate
}: CollectionScreenDetailsTileProps) => {
const styles = useStyles()
const dispatch = useDispatch()
+ const navigation = useNavigation()
const isReachable = useSelector(getIsReachable)
@@ -186,23 +232,73 @@ export const CollectionScreenDetailsTile = ({
},
{ disabled: typeof collectionId !== 'number' }
)
- const isStreamGated = collection?.is_stream_gated
+ const {
+ is_stream_gated: isStreamGated,
+ is_scheduled_release: isScheduledRelease,
+ is_private: isPrivate
+ } = collection ?? {}
+ const numericCollectionId =
+ typeof collectionId === 'number' ? collectionId : undefined
+ const collectionTracks = useSelector((state: CommonState) =>
+ getCollectionTracks(state, { id: numericCollectionId })
+ )
+ const trackAccessMap = useGatedContentAccessMap(collectionTracks ?? [])
+ const doesUserHaveAccessToAnyTrack = Object.values(trackAccessMap).some(
+ ({ hasStreamAccess }) => hasStreamAccess
+ )
const trackUids = useSelector(selectTrackUids)
const collectionTrackCount = useSelector(selectTrackCount)
const trackCount = trackCountProp ?? collectionTrackCount
const isLineupLoading = useSelector(selectIsLineupLoading)
- const collectionDuration = useSelector(selectCollectionDuration)
const playingUid = useSelector(getUid)
const isQueued = useSelector(selectIsQueued)
const isPlaying = useSelector(getPlaying)
const isPreviewing = useSelector(getPreviewing)
+ const isPlayingPreview = isPreviewing && isPlaying
const playingTrack = useSelector(getCurrentTrack)
const playingTrackId = playingTrack?.track_id
const firstTrack = useSelector(selectFirstTrack)
const messages = getMessages(isAlbum ? 'album' : 'playlist', isStreamGated)
+ const headerText = isPrivate ? messages.hiddenType : messages.collectionType
+ const isPublished = !isPrivate || isPublishing
+ const isUnpublishedScheduledRelease =
+ isScheduledRelease && isPrivate && releaseDate
+ const shouldHideOverflow =
+ hideOverflow || !isReachable || (isPrivate && !isOwner)
+ const isUSDCPurchaseGated = isContentUSDCPurchaseGated(streamConditions)
+
+ const uids = isLineupLoading ? Array(Math.min(5, trackCount ?? 0)) : trackUids
+ const tracks = useSelector((state) => getTracks(state, { uids }))
+ const areAllTracksDeleted = Object.values(tracks).every(
+ (track) => track.is_delete
+ )
+ const isPlayable =
+ Object.values(tracks).length === 0
+ ? true
+ : !areAllTracksDeleted && (isQueued || (trackCount > 0 && !!firstTrack))
+
+ const shouldShowPlay =
+ (isPlayable && hasStreamAccess) || doesUserHaveAccessToAnyTrack
+ const shouldShowPreview =
+ isUSDCPurchaseGated && !hasStreamAccess && !shouldShowPlay
+
useRefetchLineupOnTrackAdd(collectionId)
+ const badges = [
+ isUnpublishedScheduledRelease ? (
+
+ {messages.releases(releaseDate)}
+
+ ) : isPrivate ? (
+ {messages.hidden}
+ ) : null
+ ].filter((badge) => badge !== null)
+
+ const imageElement = renderImage({
+ style: styles.coverArt
+ })
+
const play = useCallback(
({ isPreview = false }: { isPreview?: boolean } = {}) => {
if (isPlaying && isQueued && isPreviewing === isPreview) {
@@ -250,19 +346,6 @@ export const CollectionScreenDetailsTile = ({
[dispatch, isPlaying, playingUid]
)
- const numericCollectionId =
- typeof collectionId === 'number' ? collectionId : undefined
-
- const uids = isLineupLoading ? Array(Math.min(5, trackCount ?? 0)) : trackUids
- const tracks = useSelector((state) => getTracks(state, { uids }))
- const areAllTracksDeleted = Object.values(tracks).every(
- (track) => track.is_delete
- )
- const isPlayable =
- Object.values(tracks).length === 0
- ? true
- : !areAllTracksDeleted && (isQueued || (trackCount > 0 && !!firstTrack))
-
const renderTrackList = useCallback(() => {
return (
-
+
{isOwner ? messages.empty : messages.emptyPublic}
@@ -294,33 +377,156 @@ export const CollectionScreenDetailsTile = ({
messages
])
+ const renderDogEar = () => {
+ const dogEarType = getDogEarType({
+ isOwner,
+ streamConditions
+ })
+ return dogEarType ? : null
+ }
+
+ const handlePressArtistName = useCallback(() => {
+ if (!user) {
+ return
+ }
+ navigation.push('Profile', { handle: user.handle })
+ }, [navigation, user])
+
+ const PreviewButton = () => (
+
+ )
+
return (
-
+
+ {renderDogEar()}
+
+
+ {headerText}
+
+
+ {badges.length > 0 ? (
+
+ {badges.map((badge) => badge)}
+
+ ) : null}
+ {imageElement}
+
+
+ {title}
+
+ {user ? (
+
+
+
+ {user.name}
+
+
+
+
+ ) : null}
+
+ {shouldShowPlay ? (
+
+ ) : null}
+ {shouldShowPreview ? : null}
+
+
+
+ {!hasStreamAccess &&
+ !isOwner &&
+ streamConditions &&
+ numericCollectionId ? (
+
+ ) : null}
+ {(hasStreamAccess || isOwner) && streamConditions ? (
+
+ ) : null}
+ {!isPublished ? null : (
+
+ )}
+ {description ? (
+
+
+ {description}
+
+
+ ) : null}
+ {numericCollectionId ? (
+
+ ) : null}
+
+
+
+ {renderTrackList()}
+
)
}
diff --git a/packages/mobile/src/screens/track-screen/TrackScreenDetailsTile.tsx b/packages/mobile/src/screens/track-screen/TrackScreenDetailsTile.tsx
index 3fdf5a6f474..0164ca98875 100644
--- a/packages/mobile/src/screens/track-screen/TrackScreenDetailsTile.tsx
+++ b/packages/mobile/src/screens/track-screen/TrackScreenDetailsTile.tsx
@@ -1,4 +1,4 @@
-import { useCallback } from 'react'
+import React, { useCallback } from 'react'
import {
useGatedContentAccess,
@@ -11,7 +11,8 @@ import {
FavoriteSource,
PlaybackSource,
FavoriteType,
- SquareSizes
+ SquareSizes,
+ isContentUSDCPurchaseGated
} from '@audius/common/models'
import type {
UID,
@@ -20,7 +21,7 @@ import type {
Track,
User
} from '@audius/common/models'
-import { FeatureFlags } from '@audius/common/services'
+import type { CommonState } from '@audius/common/store'
import {
accountSelectors,
trackPageLineupActions,
@@ -38,16 +39,49 @@ import {
playbackPositionSelectors,
PurchaseableContentType
} from '@audius/common/store'
-import { Genre, removeNullable } from '@audius/common/utils'
+import {
+ Genre,
+ getDogEarType,
+ getLocalTimezone,
+ removeNullable
+} from '@audius/common/utils'
+import dayjs from 'dayjs'
+import { TouchableOpacity } from 'react-native'
import { useDispatch, useSelector } from 'react-redux'
import { trpc } from 'utils/trpcClientWeb'
-import type { ImageProps } from '@audius/harmony-native'
-import { DetailsTile } from 'app/components/details-tile'
+import {
+ Box,
+ Button,
+ Divider,
+ Flex,
+ IconCalendarMonth,
+ IconPause,
+ IconPlay,
+ IconRepeatOff,
+ IconVisibilityHidden,
+ MusicBadge,
+ Paper,
+ Text,
+ spacing,
+ type ImageProps
+} from '@audius/harmony-native'
+import CoSign, { Size } from 'app/components/co-sign'
+import { DogEar, Tag, UserGeneratedText } from 'app/components/core'
+import { DeletedTile } from 'app/components/details-tile/DeletedTile'
+import { DetailsProgressInfo } from 'app/components/details-tile/DetailsProgressInfo'
+import { DetailsTileActionButtons } from 'app/components/details-tile/DetailsTileActionButtons'
+import { DetailsTileAiAttribution } from 'app/components/details-tile/DetailsTileAiAttribution'
+import { DetailsTileHasAccess } from 'app/components/details-tile/DetailsTileHasAccess'
+import { DetailsTileNoAccess } from 'app/components/details-tile/DetailsTileNoAccess'
+import { DetailsTileStats } from 'app/components/details-tile/DetailsTileStats'
+import { TrackMetadataList } from 'app/components/details-tile/TrackMetadataList'
import { TrackImage } from 'app/components/image/TrackImage'
+import { OfflineStatusRow } from 'app/components/offline-downloads'
+import UserBadges from 'app/components/user-badges'
import { useNavigation } from 'app/hooks/useNavigation'
-import { useFeatureFlag } from 'app/hooks/useRemoteConfig'
import { make, track as record } from 'app/services/analytics'
+import { makeStyles } from 'app/styles'
import { DownloadSection } from './DownloadSection'
const { getPlaying, getTrackId, getPreviewing } = playerSelectors
@@ -73,9 +107,30 @@ const messages = {
specialAccess: 'special access',
premiumTrack: 'premium track',
generatedWithAi: 'generated with ai',
- trackDeleted: 'track [deleted by artist]'
+ trackDeleted: 'track [deleted by artist]',
+ play: 'Play',
+ pause: 'Pause',
+ resume: 'Resume',
+ replay: 'Replay',
+ preview: 'Preview',
+ hidden: 'Hidden',
+ releases: (releaseDate: string) =>
+ `Releases ${dayjs(releaseDate).format(
+ 'M/D/YY [@] h:mm A'
+ )} ${getLocalTimezone()}`
}
+const useStyles = makeStyles(({ palette, spacing }) => ({
+ coverArt: {
+ borderWidth: 1,
+ borderColor: palette.neutralLight8,
+ borderRadius: spacing(2),
+ height: 224,
+ width: 224,
+ alignSelf: 'center'
+ }
+}))
+
type TrackScreenDetailsTileProps = {
track: Track | SearchTrack
user: User | SearchUser
@@ -100,11 +155,8 @@ export const TrackScreenDetailsTile = ({
uid,
isLineupLoading
}: TrackScreenDetailsTileProps) => {
+ const styles = useStyles()
const { hasStreamAccess } = useGatedContentAccess(track as Track) // track is of type Track | SearchTrack but we only care about some of their common fields, maybe worth refactoring later
- const { isEnabled: isNewPodcastControlsEnabled } = useFeatureFlag(
- FeatureFlags.PODCAST_CONTROL_UPDATES_ENABLED,
- FeatureFlags.PODCAST_CONTROL_UPDATES_ENABLED_FALLBACK
- )
const navigation = useNavigation()
const isReachable = useSelector(getIsReachable)
@@ -114,35 +166,42 @@ export const TrackScreenDetailsTile = ({
const isPlaying = useSelector(getPlaying)
const isPreviewing = useSelector(getPreviewing)
const isPlayingId = playingId === track.track_id
+ const playbackPositionInfo = useSelector((state) =>
+ getTrackPosition(state, { trackId, userId: currentUserId })
+ )
+ const isCurrentTrack = useSelector((state: CommonState) => {
+ return track && track.track_id === getTrackId(state)
+ })
const {
- _co_sign,
+ _co_sign: coSign,
description,
genre,
- has_current_user_reposted,
- has_current_user_saved,
+ has_current_user_reposted: hasReposted,
+ has_current_user_saved: hasSaved,
is_unlisted: isUnlisted,
is_stream_gated: isStreamGated,
- owner_id,
- play_count,
- remix_of,
- repost_count,
- save_count,
+ owner_id: ownerId,
+ play_count: playCount,
+ remix_of: remixOf,
+ repost_count: repostCount,
+ save_count: saveCount,
title,
track_id: trackId,
stream_conditions: streamConditions,
ddex_app: ddexApp,
- is_delete,
- duration,
+ is_delete: isDeleted,
release_date: releaseDate,
is_scheduled_release: isScheduledRelease
} = track
- const isOwner = owner_id === currentUserId
+ const isOwner = ownerId === currentUserId
const hideFavorite = isUnlisted || !hasStreamAccess
const hideRepost = isUnlisted || !isReachable || !hasStreamAccess
+ const hideOverflow = !isReachable || (isUnlisted && !isOwner)
+ const hideShare = isUnlisted && !isOwner
- const remixParentTrackId = remix_of?.tracks?.[0]?.parent_track_id
+ const remixParentTrackId = remixOf?.tracks?.[0]?.parent_track_id
const isRemix = !!remixParentTrackId
const hasDownloadableAssets =
(track as Track)?.is_downloadable ||
@@ -154,12 +213,70 @@ export const TrackScreenDetailsTile = ({
{ enabled: !!trackId }
)
+ const isLongFormContent =
+ track?.genre === Genre.PODCASTS || track?.genre === Genre.AUDIOBOOKS
+ const aiAttributionUserId = track?.ai_attribution_user_id
+ const isUSDCPurchaseGated = isContentUSDCPurchaseGated(streamConditions)
+
+ const isPlayingPreview = isPreviewing && isPlaying
+ const isPlayingFullAccess = isPlaying && !isPreviewing
+ const isUnpublishedScheduledRelease =
+ track?.is_scheduled_release && track?.is_unlisted && releaseDate
+ const shouldShowPreview = isUSDCPurchaseGated && (isOwner || !hasStreamAccess)
+ const shouldHideFavoriteCount =
+ isUnlisted || (!isOwner && (saveCount ?? 0) <= 0)
+ const shouldHideRepostCount =
+ isUnlisted || (!isOwner && (repostCount ?? 0) <= 0)
+ const shouldHidePlayCount =
+ (!isOwner && isUnlisted) ||
+ isStreamGated ||
+ (!isOwner && (playCount ?? 0) <= 0)
+
+ const headerText = isRemix
+ ? messages.remix
+ : isStreamGated
+ ? messages.premiumTrack
+ : messages.track
+
+ const PlayIcon =
+ playbackPositionInfo?.status === 'COMPLETED' && !isCurrentTrack
+ ? IconRepeatOff
+ : IconPlay
+
+ const badges = [
+ aiAttributionUserId ? (
+
+ ) : null,
+ isUnpublishedScheduledRelease ? (
+
+ {messages.releases(releaseDate)}
+
+ ) : isUnlisted ? (
+ {messages.hidden}
+ ) : null
+ ].filter((badge) => badge !== null)
+
+ const playText = playbackPositionInfo?.status
+ ? playbackPositionInfo?.status === 'IN_PROGRESS' || isCurrentTrack
+ ? messages.resume
+ : messages.replay
+ : messages.play
+
const renderImage = useCallback(
(props: ImageProps) => (
),
[track]
)
+ const innerImageElement = renderImage({
+ style: styles.coverArt
+ })
+
+ const imageElement = coSign ? (
+ {innerImageElement}
+ ) : (
+ innerImageElement
+ )
const currentQueueItem = useSelector(getCurrentQueueItem)
const play = useCallback(
@@ -214,7 +331,7 @@ export const TrackScreenDetailsTile = ({
const handlePressSave = () => {
if (!isOwner) {
- if (has_current_user_saved) {
+ if (hasSaved) {
dispatch(unsaveTrack(trackId, FavoriteSource.TRACK_PAGE))
} else {
dispatch(saveTrack(trackId, FavoriteSource.TRACK_PAGE))
@@ -224,7 +341,7 @@ export const TrackScreenDetailsTile = ({
const handlePressRepost = () => {
if (!isOwner) {
- if (has_current_user_reposted) {
+ if (hasReposted) {
dispatch(undoRepostTrack(trackId, RepostSource.TRACK_PAGE))
} else {
dispatch(repostTrack(trackId, RepostSource.TRACK_PAGE))
@@ -242,13 +359,24 @@ export const TrackScreenDetailsTile = ({
)
}
+ const handlePressArtistName = useCallback(() => {
+ if (!user) {
+ return
+ }
+ navigation.push('Profile', { handle: user.handle })
+ }, [navigation, user])
+
+ const handlePressTag = useCallback(
+ (tag: string) => {
+ navigation.push('TagSearch', { query: tag })
+ },
+ [navigation]
+ )
+
const handlePressEdit = useCallback(() => {
navigation?.push('EditTrack', { id: trackId })
}, [navigation, trackId])
- const playbackPositionInfo = useSelector((state) =>
- getTrackPosition(state, { trackId, userId: currentUserId })
- )
const handlePressOverflow = () => {
const isLongFormContent =
genre === Genre.PODCASTS || genre === Genre.AUDIOBOOKS
@@ -265,7 +393,7 @@ export const TrackScreenDetailsTile = ({
: user.does_current_user_follow
? OverflowAction.UNFOLLOW_ARTIST
: OverflowAction.FOLLOW_ARTIST,
- isNewPodcastControlsEnabled && isLongFormContent
+ isLongFormContent
? playbackPositionInfo?.status === 'COMPLETED'
? OverflowAction.MARK_AS_UNPLAYED
: OverflowAction.MARK_AS_PLAYED
@@ -292,59 +420,174 @@ export const TrackScreenDetailsTile = ({
return hasDownloadableAssets ? : null
}
+ const renderDogEar = () => {
+ const dogEarType = getDogEarType({
+ isOwner,
+ streamConditions
+ })
+ return dogEarType ? : null
+ }
+
+ const PreviewButton = () => (
+
+ )
+
+ const renderTags = () => {
+ if (!track || (isUnlisted && !track.field_visibility?.tags)) {
+ return null
+ }
+
+ const filteredTags = (track.tags || '').split(',').filter(Boolean)
+ return filteredTags.length > 0 ? (
+
+ {filteredTags.map((tag) => (
+ handlePressTag(tag)}>
+ {tag}
+
+ ))}
+
+ ) : null
+ }
+
+ if (!trackId) return null
+
+ if (isDeleted) {
+ return (
+
+ )
+ }
+
return (
-
+
+ {renderDogEar()}
+
+
+ {headerText}
+
+
+ {badges.length > 0 ? (
+
+ {badges.map((badge) => badge)}
+
+ ) : null}
+ {imageElement}
+
+
+ {title}
+
+ {user ? (
+
+
+
+ {user.name}
+
+
+
+
+ ) : null}
+
+ {isLongFormContent && track ? (
+
+ ) : null}
+ {hasStreamAccess ? (
+
+ ) : null}
+ {shouldShowPreview ? : null}
+
+
+
+ {!hasStreamAccess && !isOwner && streamConditions && trackId ? (
+
+ ) : null}
+ {(hasStreamAccess || isOwner) && streamConditions ? (
+
+ ) : null}
+
+ {description ? (
+
+
+ {description}
+
+
+ ) : null}
+
+ {renderTags()}
+
+
+
+ {renderBottomContent?.()}
+
)
}