Skip to content

Commit

Permalink
[PAY-1985] Implement now playing previews (#6380)
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondjacobson authored Oct 18, 2023
1 parent 96aa772 commit 7100708
Show file tree
Hide file tree
Showing 27 changed files with 718 additions and 185 deletions.
2 changes: 1 addition & 1 deletion docs/src/css/colors.css
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
--tile-shadow-3: rgba(133, 129, 153, 0.25);
--tile-shadow-1-alt: rgba(133, 129, 153, 0.2);

--currently-playing-border: #ffffff;
--currently-playing-border: #f2f2f4;
--currently-playing-default-shadow: rgba(242, 242, 242, 0.5);

--card-image-shadow: rgba(133, 129, 153, 0.75);
Expand Down
2 changes: 1 addition & 1 deletion docs/src/theme/light.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const theme = {
'--tile-shadow-3': 'rgba(133,129,153,0.25)',
'--tile-shadow-1-alt': 'rgba(133,129,153,0.12)',

'--currently-playing-border': '#FFFFFF',
'--currently-playing-border': '#F2F2F4',
'--currently-playing-default-shadow': 'rgba(242,242,242,0.5)',

'--card-image-shadow': 'rgba(133,129,153,0.75)',
Expand Down
31 changes: 26 additions & 5 deletions packages/mobile/src/components/core/LockedStatusBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@ import { makeStyles } from 'app/styles'
import { spacing } from 'app/styles/spacing'
import { useColor } from 'app/utils/theme'

import { Text } from './Text'

const useStyles = makeStyles(({ palette, spacing, typography }) => ({
root: {
backgroundColor: palette.accentBlue,
paddingHorizontal: spacing(2),
paddingVertical: 1,
borderRadius: spacing(10),
justifyContent: 'center'
justifyContent: 'center',
alignItems: 'center',
gap: spacing(1),
flexDirection: 'row'
},
premium: {
backgroundColor: palette.specialLightGreen
Expand All @@ -25,12 +30,19 @@ const useStyles = makeStyles(({ palette, spacing, typography }) => ({
export type LockedStatusBadgeProps = {
locked: boolean
variant?: 'purchase' | 'gated'
text?: string
/** Whether the badge is colored when locked */
coloredWhenLocked?: boolean
iconSize?: 'medium' | 'small'
}

/** Renders a small badge with locked or unlocked icon */
export const LockedStatusBadge = ({
locked,
variant = 'gated'
variant = 'gated',
text,
coloredWhenLocked = false,
iconSize = 'medium'
}: LockedStatusBadgeProps) => {
const styles = useStyles()
const staticWhite = useColor('staticWhite')
Expand All @@ -39,14 +51,23 @@ export const LockedStatusBadge = ({
<View
style={[
styles.root,
locked ? styles.locked : variant === 'purchase' ? styles.premium : null
locked && !coloredWhenLocked
? styles.locked
: variant === 'purchase'
? styles.premium
: null
]}
>
<LockComponent
fill={staticWhite}
width={spacing(3.5)}
height={spacing(3.5)}
width={iconSize === 'medium' ? spacing(3.5) : spacing(3)}
height={iconSize === 'medium' ? spacing(3.5) : spacing(3)}
/>
{text ? (
<Text fontSize='xs' variant='label' color='white'>
{text}
</Text>
) : null}
</View>
)
}
67 changes: 59 additions & 8 deletions packages/mobile/src/components/now-playing-drawer/ActionsBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import {
OverflowAction,
OverflowSource,
mobileOverflowMenuUIActions,
shareModalUIActions
shareModalUIActions,
usePremiumContentAccess,
formatPrice
} from '@audius/common'
import { View, Platform } from 'react-native'
import { CastButton } from 'react-native-google-cast'
Expand All @@ -28,11 +30,13 @@ import IconChromecast from 'app/assets/images/iconChromecast.svg'
import IconKebabHorizontal from 'app/assets/images/iconKebabHorizontal.svg'
import IconShare from 'app/assets/images/iconShare.svg'
import { useAirplay } from 'app/components/audio/Airplay'
import { IconButton } from 'app/components/core'
import { Button, IconButton } from 'app/components/core'
import { useIsOfflineModeEnabled } from 'app/hooks/useIsOfflineModeEnabled'
import { useFeatureFlag } from 'app/hooks/useRemoteConfig'
import { useToast } from 'app/hooks/useToast'
import { setVisibility } from 'app/store/drawers/slice'
import { makeStyles } from 'app/styles'
import { spacing } from 'app/styles/spacing'
import { useThemeColors } from 'app/utils/theme'

import { FavoriteButton } from './FavoriteButton'
Expand All @@ -58,16 +62,25 @@ const useStyles = makeStyles(({ palette, spacing }) => ({
container: {
marginTop: spacing(10),
height: spacing(12),
flexDirection: 'row',
gap: spacing(2)
},
actions: {
borderRadius: 10,
height: spacing(12),
backgroundColor: palette.neutralLight8,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-evenly'
justifyContent: 'space-evenly',
flexGrow: 1
},
button: {
flexGrow: 1,
alignItems: 'center'
},
buyButton: {
backgroundColor: palette.specialLightGreen
},
animatedIcon: {
width: spacing(7),
height: spacing(7)
Expand Down Expand Up @@ -97,6 +110,23 @@ export const ActionsBar = ({ track }: ActionsBarProps) => {
FeatureFlags.PODCAST_CONTROL_UPDATES_ENABLED_FALLBACK
)

const handlePurchasePress = useCallback(() => {
if (track?.track_id) {
dispatch(
setVisibility({
drawer: 'PremiumTrackPurchase',
visible: true,
data: { trackId: track.track_id }
})
)
}
}, [dispatch, track?.track_id])
const { doesUserHaveAccess } = usePremiumContentAccess(track)
const shouldShowPurchasePill =
track?.premium_conditions &&
'usdc_purchase' in track.premium_conditions &&
!doesUserHaveAccess

useLayoutEffect(() => {
if (Platform.OS === 'android' && castMethod === 'airplay') {
dispatch(updateMethod({ method: 'chromecast' }))
Expand Down Expand Up @@ -179,6 +209,24 @@ export const ActionsBar = ({ track }: ActionsBarProps) => {

const { openAirplayDialog } = useAirplay()

const renderPurchaseButton = () => {
if (
track?.premium_conditions &&
'usdc_purchase' in track.premium_conditions
) {
const price = track.premium_conditions.usdc_purchase.price
return (
<Button
style={styles.buyButton}
styles={{ icon: { width: spacing(4), height: spacing(4) } }}
title={`$${formatPrice(price)}`}
size='large'
onPress={handlePurchasePress}
/>
)
}
}

const renderCastButton = () => {
if (castMethod === 'airplay') {
return (
Expand Down Expand Up @@ -258,11 +306,14 @@ export const ActionsBar = ({ track }: ActionsBarProps) => {

return (
<View style={styles.container}>
{renderCastButton()}
{renderRepostButton()}
{renderFavoriteButton()}
{renderShareButton()}
{renderOptionsButton()}
{shouldShowPurchasePill ? renderPurchaseButton() : null}
<View style={styles.actions}>
{!shouldShowPurchasePill ? renderCastButton() : null}
{!shouldShowPurchasePill ? renderRepostButton() : null}
{renderFavoriteButton()}
{renderShareButton()}
{renderOptionsButton()}
</View>
</View>
)
}
18 changes: 16 additions & 2 deletions packages/mobile/src/components/now-playing-drawer/Artwork.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import type { CommonState, Nullable, Track } from '@audius/common'
import { SquareSizes, averageColorSelectors } from '@audius/common'
import {
DogEarType,
SquareSizes,
averageColorSelectors,
usePremiumContentAccess
} from '@audius/common'
import { Dimensions } from 'react-native'
import { useSelector } from 'react-redux'

import { Shadow } from 'app/components/core'
import { DogEar, Shadow } from 'app/components/core'
import { TrackImage } from 'app/components/image/TrackImage'
import { makeStyles } from 'app/styles'
const { getDominantColorsByTrack } = averageColorSelectors
Expand Down Expand Up @@ -50,8 +55,17 @@ export const Artwork = ({ track }: ArtworkProps) => {
shadowColor = `rgb(${r.toFixed()},${g.toFixed()},${b.toFixed()})`
}

const { doesUserHaveAccess } = usePremiumContentAccess(track)
const shouldShowDogEar =
track?.premium_conditions &&
'usdc_purchase' in track.premium_conditions &&
!doesUserHaveAccess

return (
<Shadow opacity={0.2} radius={8} color={shadowColor} style={styles.root}>
{shouldShowDogEar ? (
<DogEar type={DogEarType.USDC_PURCHASE} borderOffset={2} />
) : null}
<TrackImage
style={styles.image}
track={track}
Expand Down
80 changes: 65 additions & 15 deletions packages/mobile/src/components/now-playing-drawer/PlayBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,32 @@ import {
SquareSizes,
FavoriteSource,
accountSelectors,
tracksSocialActions
tracksSocialActions,
usePremiumContentAccess
} from '@audius/common'
import { TouchableOpacity, Animated, View, Dimensions } from 'react-native'
import { useDispatch, useSelector } from 'react-redux'

import IconLock from 'app/assets/images/iconLock.svg'
import { FavoriteButton } from 'app/components/favorite-button'
import { TrackImage } from 'app/components/image/TrackImage'
import Text from 'app/components/text'
import { makeStyles } from 'app/styles'
import { useColor } from 'app/utils/theme'
import { zIndex } from 'app/utils/zIndex'

import { LockedStatusBadge } from '../core'

import { PlayButton } from './PlayButton'
import { TrackingBar } from './TrackingBar'
import { NOW_PLAYING_HEIGHT, PLAY_BAR_HEIGHT } from './constants'
const { getAccountUser } = accountSelectors
const { saveTrack, unsaveTrack } = tracksSocialActions

const messages = {
preview: 'PREVIEW'
}

const useStyles = makeStyles(({ palette, spacing }) => ({
root: {
width: '100%',
Expand All @@ -34,6 +43,7 @@ const useStyles = makeStyles(({ palette, spacing }) => ({
width: '100%',
paddingLeft: spacing(3),
paddingRight: spacing(3),
gap: spacing(3),
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start'
Expand All @@ -51,24 +61,41 @@ const useStyles = makeStyles(({ palette, spacing }) => ({
flexShrink: 1,
flexGrow: 1,
alignItems: 'center',
flexDirection: 'row'
flexDirection: 'row',
gap: spacing(3)
},
artwork: {
marginLeft: spacing(3),
artworkContainer: {
position: 'relative',
height: 26,
width: 26,
overflow: 'hidden',
backgroundColor: palette.neutralLight7,
borderRadius: 2
borderRadius: 2,
overflow: 'hidden'
},
artwork: {
height: '100%',
width: '100%',
backgroundColor: palette.neutralLight7
},
lockOverlay: {
backgroundColor: '#000',
opacity: 0.4,
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center',
elevation: 10,
zIndex: 10
},
trackText: {
alignItems: 'center',
marginLeft: spacing(3),
flexDirection: 'row'
},
title: {
color: palette.neutral,
maxWidth: Dimensions.get('window').width / 3,
maxWidth: Dimensions.get('window').width / 3.5,
fontSize: spacing(3)
},
separator: {
Expand Down Expand Up @@ -98,6 +125,13 @@ export const PlayBar = (props: PlayBarProps) => {
const styles = useStyles()
const dispatch = useDispatch()
const currentUser = useSelector(getAccountUser)
const staticWhite = useColor('staticWhite')

const { doesUserHaveAccess } = usePremiumContentAccess(track)
const shouldShowPreviewLock =
track?.premium_conditions &&
'usdc_purchase' in track.premium_conditions &&
!doesUserHaveAccess

const onPressFavoriteButton = useCallback(() => {
if (track) {
Expand Down Expand Up @@ -142,18 +176,25 @@ export const PlayBar = (props: PlayBarProps) => {
translateYAnimation={translationAnim}
/>
<View style={styles.container}>
{renderFavoriteButton()}
{shouldShowPreviewLock ? null : renderFavoriteButton()}
<TouchableOpacity
activeOpacity={1}
style={styles.trackInfo}
onPress={onPress}
>
{track ? (
<TrackImage
style={styles.artwork}
track={track}
size={SquareSizes.SIZE_150_BY_150}
/>
<View style={styles.artworkContainer}>
{shouldShowPreviewLock ? (
<View style={styles.lockOverlay}>
<IconLock fill={staticWhite} width={10} height={10} />
</View>
) : null}
<TrackImage
style={styles.artwork}
track={track}
size={SquareSizes.SIZE_150_BY_150}
/>
</View>
) : null}
<View style={styles.trackText}>
<Text numberOfLines={1} weight='bold' style={styles.title}>
Expand All @@ -170,6 +211,15 @@ export const PlayBar = (props: PlayBarProps) => {
{user?.name ?? ''}
</Text>
</View>
{shouldShowPreviewLock ? (
<LockedStatusBadge
variant='purchase'
locked
coloredWhenLocked
iconSize='small'
text={messages.preview}
/>
) : null}
</TouchableOpacity>
<PlayButton wrapperStyle={styles.playIcon} />
</View>
Expand Down
Loading

0 comments on commit 7100708

Please sign in to comment.