Skip to content

Commit

Permalink
[PAY-1063][PAY-1085][PAY-1086] Update UI for inaccessible gated track…
Browse files Browse the repository at this point in the history
…s from favorites and history pages (#3100)

Co-authored-by: Saliou Diallo <saliou@audius.co>
  • Loading branch information
sddioulde and Saliou Diallo committed Mar 24, 2023
1 parent b0441f5 commit fc14c82
Show file tree
Hide file tree
Showing 16 changed files with 343 additions and 73 deletions.
40 changes: 39 additions & 1 deletion packages/common/src/hooks/usePremiumContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useMemo } from 'react'

import { useSelector } from 'react-redux'

import { Chain, PremiumConditions, Track } from 'models'
import { Chain, ID, PremiumConditions, Track } from 'models'
import { getAccountUser } from 'store/account/selectors'
import { cacheTracksSelectors, cacheUsersSelectors } from 'store/cache'
import { premiumContentSelectors } from 'store/premium-content'
Expand All @@ -14,6 +14,7 @@ const { getUser, getUsers } = cacheUsersSelectors
const { getLockedContentId, getPremiumTrackSignatureMap } =
premiumContentSelectors

// Returns whether user has access to given track.
export const usePremiumContentAccess = (track: Nullable<Partial<Track>>) => {
const premiumTrackSignatureMap = useSelector(getPremiumTrackSignatureMap)
const user = useSelector(getAccountUser)
Expand Down Expand Up @@ -44,6 +45,43 @@ export const usePremiumContentAccess = (track: Nullable<Partial<Track>>) => {
return { isUserAccessTBD, doesUserHaveAccess }
}

// Similar to `usePremiumContentAccess` above, but for multiple tracks.
// Returns a map of track id -> track access i.e.
// {[id: ID]: { isUserAccessTBD: boolean, doesUserHaveAccess: boolean }}
export const usePremiumContentAccessMap = (tracks: Partial<Track>[]) => {
const premiumTrackSignatureMap = useSelector(getPremiumTrackSignatureMap)
const user = useSelector(getAccountUser)

const result = useMemo(() => {
let map: {[id: ID]: { isUserAccessTBD: boolean, doesUserHaveAccess: boolean }} = {}

tracks.forEach(track => {
if (!track.track_id) {
return
}

const trackId = track.track_id
const isPremium = track.is_premium
const hasPremiumContentSignature =
!!(track.premium_content_signature || premiumTrackSignatureMap[trackId])
const isCollectibleGated = !!track.premium_conditions?.nft_collection
const isSignatureToBeFetched =
isCollectibleGated &&
premiumTrackSignatureMap[trackId] === undefined &&
!!user // We're only fetching a sig if the user is logged in

map[trackId] = {
isUserAccessTBD: !hasPremiumContentSignature && isSignatureToBeFetched,
doesUserHaveAccess: !isPremium || hasPremiumContentSignature
}
})

return map
}, [tracks, premiumTrackSignatureMap, user])

return result
}

export const usePremiumConditionsEntity = (
premiumConditions: Nullable<PremiumConditions>
) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ const useStyles = makeStyles(({ palette, spacing, typography }) => ({
},
headerContainer: {
...flexRowCentered(),
marginBottom: spacing(2),
justifyContent: 'space-between'
},
titleContainer: {
...flexRowCentered(),
marginBottom: spacing(2)
...flexRowCentered()
},
title: {
marginLeft: spacing(2),
Expand All @@ -79,6 +79,9 @@ const useStyles = makeStyles(({ palette, spacing, typography }) => ({
},
collectionName: {
color: palette.secondary
},
bottomMargin: {
marginBottom: spacing(2)
}
}))

Expand Down Expand Up @@ -125,7 +128,7 @@ const DetailsTileOwnerSection = ({

return (
<View style={[styles.root, style]}>
<View style={styles.titleContainer}>
<View style={[styles.titleContainer, styles.bottomMargin]}>
{nftCollection && (
<IconCollectible fill={neutral} width={16} height={16} />
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ const useStyles = makeStyles(({ palette, spacing, typography }) => ({
},
headerContainer: {
...flexRowCentered(),
marginBottom: spacing(2),
justifyContent: 'space-between'
},
titleContainer: {
...flexRowCentered(),
marginBottom: spacing(2)
...flexRowCentered()
},
title: {
marginLeft: spacing(2),
Expand Down Expand Up @@ -122,6 +122,9 @@ const useStyles = makeStyles(({ palette, spacing, typography }) => ({
},
mainButton: {
marginTop: spacing(7)
},
bottomMargin: {
marginBottom: spacing(2)
}
}))

Expand Down Expand Up @@ -156,7 +159,7 @@ const DetailsTileNoAccessSection = ({

return (
<View style={[styles.root, style]}>
<View style={styles.titleContainer}>
<View style={[styles.titleContainer, styles.bottomMargin]}>
<IconLock fill={neutral} width={16} height={16} />
<Text style={styles.title}>{messages.howToUnlock}</Text>
</View>
Expand Down
55 changes: 46 additions & 9 deletions packages/mobile/src/components/track-list/TrackListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { memo, useCallback, useMemo, useState } from 'react'

import type { ID, Track, UID, User } from '@audius/common'
import {
usePremiumContentAccess,
FeatureFlags,
playbackPositionSelectors,
Genre,
Expand All @@ -20,13 +21,14 @@ import { useDispatch, useSelector } from 'react-redux'
import IconDrag from 'app/assets/images/iconDrag.svg'
import IconHeart from 'app/assets/images/iconHeart.svg'
import IconKebabHorizontal from 'app/assets/images/iconKebabHorizontal.svg'
import IconLock from 'app/assets/images/iconLock.svg'
import IconRemoveTrack from 'app/assets/images/iconRemoveTrack.svg'
import { IconButton } from 'app/components/core'
import UserBadges from 'app/components/user-badges'
import { useIsGatedContentEnabled } from 'app/hooks/useIsGatedContentEnabled'
import { useFeatureFlag } from 'app/hooks/useRemoteConfig'
import { font, makeStyles } from 'app/styles'
import { useThemeColors } from 'app/utils/theme'
import { flexRowCentered, font, makeStyles } from 'app/styles'
import { useColor, useThemeColors } from 'app/utils/theme'

import { TrackDownloadStatusIndicator } from '../offline-downloads/TrackDownloadStatusIndicator'

Expand All @@ -41,7 +43,7 @@ const { getTrackPosition } = playbackPositionSelectors

export type TrackItemAction = 'save' | 'overflow' | 'remove'

const useStyles = makeStyles(({ palette, spacing }) => ({
const useStyles = makeStyles(({ palette, spacing, typography }) => ({
trackContainer: {
width: '100%',
height: 72,
Expand Down Expand Up @@ -116,11 +118,31 @@ const useStyles = makeStyles(({ palette, spacing }) => ({
},
hideDivider: {
opacity: 0
},
locked: {
...flexRowCentered(),
paddingVertical: spacing(0.5),
paddingHorizontal: spacing(2),
backgroundColor: palette.accentBlue,
borderRadius: 80
},
lockedIcon: {
fill: palette.white
},
lockedText: {
marginLeft: spacing(0.5),
fontFamily: typography.fontByWeight.demiBold,
fontSize: typography.fontSize.xxs,
color: palette.white
},
halfTransparent: {
opacity: 0.5
}
}))

const getMessages = ({ isDeleted = false }: { isDeleted?: boolean } = {}) => ({
deleted: isDeleted ? ' [Deleted By Artist]' : ''
deleted: isDeleted ? ' [Deleted By Artist]' : '',
locked: 'Locked'
})

export type TrackListItemProps = {
Expand Down Expand Up @@ -193,6 +215,9 @@ const TrackListItemComponent = (props: TrackListItemComponentProps) => {

const isDeleted = is_delete || !!is_deactivated || is_unlisted

const { isUserAccessTBD, doesUserHaveAccess } = usePremiumContentAccess(track)
const isLocked = !isUserAccessTBD && !doesUserHaveAccess

const isActive = useSelector((state) => {
const playingUid = getUid(state)
return uid !== undefined && uid === playingUid
Expand All @@ -211,6 +236,7 @@ const TrackListItemComponent = (props: TrackListItemComponentProps) => {
const styles = useStyles()
const dispatch = useDispatch()
const themeColors = useThemeColors()
const white = useColor('white')
const [titleWidth, setTitleWidth] = useState(0)

const deletedTextWidth = useMemo(
Expand All @@ -224,7 +250,7 @@ const TrackListItemComponent = (props: TrackListItemComponentProps) => {
)

const onPressTrack = () => {
if (uid && !isDeleted && togglePlay) {
if (uid && !isLocked && !isDeleted && togglePlay) {
togglePlay(uid, track_id)
}
}
Expand Down Expand Up @@ -315,15 +341,15 @@ const TrackListItemComponent = (props: TrackListItemComponentProps) => {
style={styles.trackInnerContainer}
onPress={onPressTrack}
onLongPress={drag}
disabled={isDeleted}
disabled={isDeleted || isLocked}
>
{!hideArt ? (
<TrackArtwork
track={track as Track}
isActive={isActive}
isPlaying={isPlaying}
/>
) : isActive && !isDeleted ? (
) : isActive && !isDeleted && !isLocked ? (
<View style={styles.playButtonContainer}>
<TablePlayButton
playing
Expand All @@ -334,7 +360,12 @@ const TrackListItemComponent = (props: TrackListItemComponentProps) => {
</View>
) : null}
{isReorderable && <IconDrag style={styles.dragIcon} />}
<View style={styles.nameArtistContainer}>
<View
style={[
styles.nameArtistContainer,
!isDeleted && isLocked ? styles.halfTransparent : null
]}
>
<View
style={styles.topLine}
onLayout={(e) => setTitleWidth(e.nativeEvent.layout.width)}
Expand Down Expand Up @@ -362,7 +393,13 @@ const TrackListItemComponent = (props: TrackListItemComponentProps) => {
<UserBadges user={user} badgeSize={12} hideName />
</Text>
</View>
{trackItemAction === 'save' ? (
{!isDeleted && isLocked ? (
<View style={styles.locked}>
<IconLock fill={white} width={10} height={10} />
<Text style={styles.lockedText}>{messages.locked}</Text>
</View>
) : null}
{(isDeleted || !isLocked) && trackItemAction === 'save' ? (
<IconButton
icon={IconHeart}
styles={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
createPlaylistModalUISelectors,
createPlaylistModalUIActions as createPlaylistActions,
imageBlank as placeholderCoverArt,
newCollectionMetadata
newCollectionMetadata,
usePremiumContentAccessMap
} from '@audius/common'
import { push as pushRoute } from 'connected-react-router'
import { connect } from 'react-redux'
Expand Down Expand Up @@ -339,6 +340,8 @@ const EditPlaylistPage = g(

useTemporaryNavContext(setters)

const trackAccessMap = usePremiumContentAccessMap(tracks ?? [])

// Put together track list if necessary
let trackList = null
if (tracks && reorderedTracks.length > 0) {
Expand All @@ -349,6 +352,10 @@ const EditPlaylistPage = g(
showRemoveTrackDrawer &&
t.track_id === confirmRemoveTrack?.trackId &&
playlistTrack?.time === confirmRemoveTrack?.timestamp
const { isUserAccessTBD, doesUserHaveAccess } = trackAccessMap[
t.track_id
] ?? { isUserAccessTBD: false, doesUserHaveAccess: true }
const isLocked = !isUserAccessTBD && !doesUserHaveAccess

return {
isLoading: false,
Expand All @@ -359,6 +366,7 @@ const EditPlaylistPage = g(
time: playlistTrack?.time,
isPremium: t.is_premium,
isDeleted: t.is_delete || !!t.user.is_deactivated,
isLocked,
isRemoveActive
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ type OverflowMenuButtonProps = {
handle: string
hiddenUntilHover?: boolean
includeEdit?: boolean
includeAddToPlaylist?: boolean
includeFavorite?: boolean
index?: number
isArtistPick?: boolean
isDeleted?: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export const SpecialAccessAvailability = ({
text={messages.supportersInfo}
mouseEnterDelay={0.1}
mount={'parent'}
color='--secondary'
>
<IconInfo className={styles.icon} />
</Tooltip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,16 @@ const ConnectedTrackListItem = (props: ConnectedTrackListItemProps) => {

const onClickOverflow = () => {
const overflowActions = [
props.isReposted ? OverflowAction.UNREPOST : OverflowAction.REPOST,
props.isSaved ? OverflowAction.UNFAVORITE : OverflowAction.FAVORITE,
props.isLocked
? null
: props.isReposted
? OverflowAction.UNREPOST
: OverflowAction.REPOST,
props.isLocked
? null
: props.isSaved
? OverflowAction.UNFAVORITE
: OverflowAction.FAVORITE,
!isGatedContentEnabled || !props.isPremium
? OverflowAction.ADD_TO_PLAYLIST
: null,
Expand Down
2 changes: 2 additions & 0 deletions packages/web/src/components/track/mobile/TrackList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type TrackListProps = {
time?: number
coverArtSizes?: CoverArtSizes
isDeleted: boolean
isLocked: boolean
}>
showTopDivider?: boolean
showDivider?: boolean
Expand Down Expand Up @@ -102,6 +103,7 @@ const TrackList = ({
coverArtSizes={track.coverArtSizes}
uid={track.uid}
isDeleted={track.isDeleted}
isLocked={track.isLocked}
onSave={onSave}
isRemoveActive={track.isRemoveActive}
onRemove={onRemove}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
transition: background 0.17s ease-in-out, border 0.17s ease-in-out;
}

.trackContainer:active {
.trackContainer:active:not(:has(.locked)) {
background-color: var(--neutral-light-9);
}

Expand Down Expand Up @@ -184,3 +184,29 @@
.removeTrackContainer.isRemoveActive {
transform: rotate(-90deg);
}

.locked {
display: flex;
align-items: center;
padding: 2px var(--unit-2);
background: var(--accent-blue);
border-radius: 80px;
font-weight: var(--font-demi-bold);
font-size: var(--font-2xs);
color: var(--white);
}

.locked svg {
margin-right: 2px;
width: 10px;
height: 10px;
}

.locked svg path {
fill: var(--white);
}

.lockedTrackTitle,
.lockedBadges {
opacity: 0.5;
}
Loading

0 comments on commit fc14c82

Please sign in to comment.