From 536f82dfc83bbe86046c7ea2a8202bc040804c68 Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Tue, 6 Feb 2024 23:59:06 -0800 Subject: [PATCH 1/8] [PAY-2456] Update mobile purchase flow for lossless --- .../audius-api-client/AudiusAPIClient.ts | 3 +- .../CooldownSummaryTable.tsx | 2 +- .../PremiumTrackPurchaseDrawer.tsx | 12 +++++- .../PurchaseSummaryTable.tsx | 39 +++++++++++++++--- .../components/summary-table/SummaryTable.tsx | 39 ++++++++++-------- .../track-details-tile/TrackDetailsTile.tsx | 40 +++++++++++-------- .../cache/tracks/utils/retrieveTracks.ts | 23 ++++++++--- .../web/src/common/store/pages/track/sagas.ts | 14 ++----- .../components/PurchaseSummaryTable.tsx | 2 +- 9 files changed, 114 insertions(+), 60 deletions(-) diff --git a/packages/common/src/services/audius-api-client/AudiusAPIClient.ts b/packages/common/src/services/audius-api-client/AudiusAPIClient.ts index 2150b869e57..20adb15062c 100644 --- a/packages/common/src/services/audius-api-client/AudiusAPIClient.ts +++ b/packages/common/src/services/audius-api-client/AudiusAPIClient.ts @@ -1985,7 +1985,8 @@ export class AudiusAPIClient { _encodeOrThrow(id: ID): OpaqueID { const encoded = encodeHashId(id) if (!encoded) { - throw new Error(`Unable to encode id: ${id}`) + return 'abc' + // throw new Error(`Unable to encode id: ${id}`) } return encoded } diff --git a/packages/mobile/src/components/challenge-rewards-drawer/CooldownSummaryTable.tsx b/packages/mobile/src/components/challenge-rewards-drawer/CooldownSummaryTable.tsx index a54d81479b1..302b78ca93c 100644 --- a/packages/mobile/src/components/challenge-rewards-drawer/CooldownSummaryTable.tsx +++ b/packages/mobile/src/components/challenge-rewards-drawer/CooldownSummaryTable.tsx @@ -22,7 +22,7 @@ export const CooldownSummaryTable = ({ diff --git a/packages/mobile/src/components/premium-track-purchase-drawer/PremiumTrackPurchaseDrawer.tsx b/packages/mobile/src/components/premium-track-purchase-drawer/PremiumTrackPurchaseDrawer.tsx index 2c500f31b8a..3ad981a633c 100644 --- a/packages/mobile/src/components/premium-track-purchase-drawer/PremiumTrackPurchaseDrawer.tsx +++ b/packages/mobile/src/components/premium-track-purchase-drawer/PremiumTrackPurchaseDrawer.tsx @@ -315,6 +315,13 @@ const RenderForm = ({ } }, [setPurchaseVendor, showCoinflow, purchaseVendor]) + const stemsPurchaseCount = track.is_download_gated + ? track._stems?.length ?? 0 + : 0 + const downloadPurchaseCount = + track.is_download_gated && track.download?.is_downloadable ? 1 : 0 + const streamPurchaseCount = track.is_stream_gated ? 1 : 0 + return ( {page === PurchaseContentPage.PURCHASE ? ( @@ -324,7 +331,7 @@ const RenderForm = ({ ) : null} - + {isPurchaseSuccessful ? null : ( {isIOSDisabled || isUnlocking || isPurchaseSuccessful ? null : ( diff --git a/packages/mobile/src/components/premium-track-purchase-drawer/PurchaseSummaryTable.tsx b/packages/mobile/src/components/premium-track-purchase-drawer/PurchaseSummaryTable.tsx index 4ed65260c65..65d0106cd8b 100644 --- a/packages/mobile/src/components/premium-track-purchase-drawer/PurchaseSummaryTable.tsx +++ b/packages/mobile/src/components/premium-track-purchase-drawer/PurchaseSummaryTable.tsx @@ -9,31 +9,58 @@ const messages = { summary: 'Total', payExtra: 'Pay Extra', premiumTrack: 'Premium Track', + downloadableFiles: 'Downloadable Files', existingBalance: 'Existing Balance', total: 'Total', youPaid: 'You Paid', price: (price: string) => `$${price}`, - subtractPrice: (price: string) => `-$${price}` + subtractPrice: (price: string) => `-$${price}`, + included: 'included' } type PurchaseSummaryTableProps = { totalPriceInCents: number basePrice: number extraAmount?: number + // How many "streams" are available for purchase + // Prior to albums/bundles this should be 0 or 1. + streamPurchaseCount?: number + // How many "downloads" are available for purchase + // Prior to albums/bundles this should be 0 or 1. + downloadPurchaseCount?: number + // How many stems are available for purchase + stemsPurchaseCount?: number } export const PurchaseSummaryTable = ({ totalPriceInCents, basePrice, - extraAmount + extraAmount, + streamPurchaseCount, + downloadPurchaseCount, + stemsPurchaseCount }: PurchaseSummaryTableProps) => { - const items: SummaryTableItem[] = [ - { + const items: SummaryTableItem[] = [] + if (streamPurchaseCount) { + items.push({ id: 'premiumTrack', label: messages.premiumTrack, value: messages.price(formatPrice(basePrice)) - } - ] + }) + } + + const downloadCount = (stemsPurchaseCount || 0) + (downloadPurchaseCount || 0) + if (downloadCount > 0) { + items.push({ + id: 'premiumTrackDownload', + label: `${messages.downloadableFiles} (${downloadCount})`, + value: streamPurchaseCount + ? messages.included + : messages.price(formatPrice(basePrice)), + color: streamPurchaseCount ? 'subdued' : 'default' + }) + } + if (extraAmount != null) { items.push({ id: 'payExtra', diff --git a/packages/mobile/src/components/summary-table/SummaryTable.tsx b/packages/mobile/src/components/summary-table/SummaryTable.tsx index 0248ef6d4ef..6776b271045 100644 --- a/packages/mobile/src/components/summary-table/SummaryTable.tsx +++ b/packages/mobile/src/components/summary-table/SummaryTable.tsx @@ -4,10 +4,10 @@ import React, { useCallback, useState } from 'react' import { removeNullable } from '@audius/common/utils' import { LayoutAnimation, View } from 'react-native' -import { Text } from 'app/components/core' +import type { TextColors } from '@audius/harmony-native' +import { Text } from '@audius/harmony-native' import { Expandable, ExpandableArrowIcon } from 'app/components/expandable' import { flexRowCentered, makeStyles } from 'app/styles' -import { type ThemeColors } from 'app/utils/theme' const useStyles = makeStyles(({ spacing, palette }) => ({ container: { @@ -42,6 +42,7 @@ export type SummaryTableItem = { icon?: React.FC content?: ReactNode // expandable content disabled?: boolean + color?: TextColors } export type SummaryTableProps = { @@ -49,8 +50,8 @@ export type SummaryTableProps = { summaryItem?: SummaryTableItem title: ReactNode secondaryTitle?: ReactNode - summaryLabelColor?: keyof ThemeColors - summaryValueColor?: keyof ThemeColors + summaryLabelColor?: TextColors + summaryValueColor?: TextColors renderBody?: (items: SummaryTableItem[]) => ReactNode /** Enables an expand/collapse interaction. Only the title shows when collapsed. */ collapsible?: boolean @@ -62,7 +63,7 @@ export const SummaryTable = ({ title, secondaryTitle, summaryLabelColor, - summaryValueColor = 'secondary', + summaryValueColor = 'accent', renderBody: renderBodyProp, collapsible = false }: SummaryTableProps) => { @@ -82,16 +83,20 @@ export const SummaryTable = ({ - {title} + + {title} + - + {secondaryTitle} ) : ( - {title} - + + {title} + + {secondaryTitle} @@ -104,16 +109,16 @@ export const SummaryTable = ({ {summaryItem.label} {summaryItem.value} @@ -127,7 +132,7 @@ export const SummaryTable = ({ <> {renderBodyProp ? renderBodyProp(items) - : nonNullItems.map(({ id, label, value }, index) => ( + : nonNullItems.map(({ id, label, value, color }, index) => ( - {label} - {value} + {label} + + {value} + ))} {renderSummaryItem()} diff --git a/packages/mobile/src/components/track-details-tile/TrackDetailsTile.tsx b/packages/mobile/src/components/track-details-tile/TrackDetailsTile.tsx index 5298855b1bc..9e0805410eb 100644 --- a/packages/mobile/src/components/track-details-tile/TrackDetailsTile.tsx +++ b/packages/mobile/src/components/track-details-tile/TrackDetailsTile.tsx @@ -80,9 +80,13 @@ const useStyles = makeStyles(({ spacing, palette }) => ({ type TrackDetailsTileProps = { trackId: ID + showLabel?: boolean } -export const TrackDetailsTile = ({ trackId }: TrackDetailsTileProps) => { +export const TrackDetailsTile = ({ + trackId, + showLabel = true +}: TrackDetailsTileProps) => { const styles = useStyles() const { accentBlue, specialLightGreen } = useThemeColors() const track = useSelector((state) => getTrack(state, { id: trackId })) @@ -145,22 +149,24 @@ export const TrackDetailsTile = ({ trackId }: TrackDetailsTileProps) => { size={SquareSizes.SIZE_150_BY_150} /> - - - - {title} - - + {showLabel ? ( + + + + {title} + + + ) : null} 1) { throw new Error('Can only query for single unlisted track') } else { + const { id, url_title, handle } = ids[0] fetched = yield* call([apiClient, 'getTrack'], { - id: ids[0].id, + id, currentUserId, - unlistedArgs: { - urlTitle: ids[0].url_title, - handle: ids[0].handle - } + unlistedArgs: + url_title && handle + ? { + urlTitle: url_title, + handle + } + : undefined }) } } else { diff --git a/packages/web/src/common/store/pages/track/sagas.ts b/packages/web/src/common/store/pages/track/sagas.ts index 6df14d0ea85..d6b2180e00c 100644 --- a/packages/web/src/common/store/pages/track/sagas.ts +++ b/packages/web/src/common/store/pages/track/sagas.ts @@ -163,18 +163,10 @@ function* watchFetchTrack() { forceRetrieveFromSource }) } else { - const ids = - canBeUnlisted && slug && handle - ? [{ id: trackId, url_title: slug, handle }] - : [trackId] + const ids = canBeUnlisted + ? [{ id: trackId, url_title: slug, handle }] + : [trackId] - retrieveTracks({ - trackIds: ids, - canBeUnlisted, - withStems: true, - withRemixes, - withRemixParents: true - }) const tracks: Track[] = yield* call(retrieveTracks, { trackIds: ids, canBeUnlisted, diff --git a/packages/web/src/components/premium-content-purchase-modal/components/PurchaseSummaryTable.tsx b/packages/web/src/components/premium-content-purchase-modal/components/PurchaseSummaryTable.tsx index a5e9c994ba7..a19f7ffa904 100644 --- a/packages/web/src/components/premium-content-purchase-modal/components/PurchaseSummaryTable.tsx +++ b/packages/web/src/components/premium-content-purchase-modal/components/PurchaseSummaryTable.tsx @@ -12,7 +12,7 @@ const messages = { youPaid: 'You Paid', zero: '$0.00', price: (val: string) => `$${val}`, - included: 'Included' + included: 'included' } type PurchaseSummaryTableProps = { From 3d3b8acbbbf49b0d58ef740b7040d3385040e5e5 Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Wed, 7 Feb 2024 00:00:17 -0800 Subject: [PATCH 2/8] [PAY-2438] Hide download all button when unnecessary --- .../src/screens/track-screen/DownloadSection.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/mobile/src/screens/track-screen/DownloadSection.tsx b/packages/mobile/src/screens/track-screen/DownloadSection.tsx index 8b74ef14024..3da1ee4ddfe 100644 --- a/packages/mobile/src/screens/track-screen/DownloadSection.tsx +++ b/packages/mobile/src/screens/track-screen/DownloadSection.tsx @@ -2,7 +2,8 @@ import { useCallback, useState } from 'react' import { useCurrentStems, - useDownloadableContentAccess + useDownloadableContentAccess, + useGatedContentAccess } from '@audius/common/hooks' import { ModalSource, DownloadQuality } from '@audius/common/models' import type { ID } from '@audius/common/models' @@ -60,8 +61,14 @@ export const DownloadSection = ({ trackId }: { trackId: ID }) => { const { onOpen: openWaitForDownloadModal } = useWaitForDownloadModal() const [quality, setQuality] = useState(DownloadQuality.MP3) const [isExpanded, setIsExpanded] = useState(false) + const track = useSelector((state: CommonState) => + getTrack(state, { id: trackId }) + ) const { stemTracks } = useCurrentStems({ trackId }) - const shouldDisplayDownloadAll = stemTracks.length > 1 + const { hasStreamAccess } = useGatedContentAccess(track) + // Hide the download all button if there aren't multiple downloads and if the user + // happens to not have stream access to the track + const shouldDisplayDownloadAll = stemTracks.length > 1 && hasStreamAccess const { price, shouldDisplayPremiumDownloadLocked, @@ -76,9 +83,6 @@ export const DownloadSection = ({ trackId }: { trackId: ID }) => { maximumFractionDigits: 2 }) : undefined - const track = useSelector((state: CommonState) => - getTrack(state, { id: trackId }) - ) const shouldHideDownload = !track?.access.download && !shouldDisplayDownloadFollowGated From 144c33c902d91882708ff66ced639324de063e7c Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Wed, 7 Feb 2024 00:06:53 -0800 Subject: [PATCH 3/8] [PAY-2435] fire handlePressOut on native harmony button long press --- .../src/harmony-native/components/Button/Button/Button.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/mobile/src/harmony-native/components/Button/Button/Button.tsx b/packages/mobile/src/harmony-native/components/Button/Button/Button.tsx index d61c8e1e534..9ec2c9c6e57 100644 --- a/packages/mobile/src/harmony-native/components/Button/Button/Button.tsx +++ b/packages/mobile/src/harmony-native/components/Button/Button/Button.tsx @@ -271,6 +271,7 @@ export const Button = (props: ButtonProps) => { const loaderSize = size === 'small' ? 16 : size === 'large' ? 24 : 20 const [isPressing, setIsPressing] = useState(false) + console.log({isPressing}) const handlePressIn = () => { setIsPressing(true) @@ -299,6 +300,7 @@ export const Button = (props: ButtonProps) => { Date: Wed, 7 Feb 2024 00:13:32 -0800 Subject: [PATCH 4/8] Undo api client change --- .../common/src/services/audius-api-client/AudiusAPIClient.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/common/src/services/audius-api-client/AudiusAPIClient.ts b/packages/common/src/services/audius-api-client/AudiusAPIClient.ts index 20adb15062c..2150b869e57 100644 --- a/packages/common/src/services/audius-api-client/AudiusAPIClient.ts +++ b/packages/common/src/services/audius-api-client/AudiusAPIClient.ts @@ -1985,8 +1985,7 @@ export class AudiusAPIClient { _encodeOrThrow(id: ID): OpaqueID { const encoded = encodeHashId(id) if (!encoded) { - return 'abc' - // throw new Error(`Unable to encode id: ${id}`) + throw new Error(`Unable to encode id: ${id}`) } return encoded } From 0334b3146a3b884fc7cda1c369685bcc8b0d12cf Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Wed, 7 Feb 2024 00:15:11 -0800 Subject: [PATCH 5/8] Remove comment --- .../src/harmony-native/components/Button/Button/Button.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/mobile/src/harmony-native/components/Button/Button/Button.tsx b/packages/mobile/src/harmony-native/components/Button/Button/Button.tsx index 9ec2c9c6e57..04b1417af89 100644 --- a/packages/mobile/src/harmony-native/components/Button/Button/Button.tsx +++ b/packages/mobile/src/harmony-native/components/Button/Button/Button.tsx @@ -271,7 +271,6 @@ export const Button = (props: ButtonProps) => { const loaderSize = size === 'small' ? 16 : size === 'large' ? 24 : 20 const [isPressing, setIsPressing] = useState(false) - console.log({isPressing}) const handlePressIn = () => { setIsPressing(true) From c88fe30bc554fa950a8b7628d438e37a93b93606 Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Wed, 7 Feb 2024 00:19:02 -0800 Subject: [PATCH 6/8] Add download access for download all button --- packages/mobile/src/screens/track-screen/DownloadSection.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/mobile/src/screens/track-screen/DownloadSection.tsx b/packages/mobile/src/screens/track-screen/DownloadSection.tsx index 3da1ee4ddfe..818c09e3557 100644 --- a/packages/mobile/src/screens/track-screen/DownloadSection.tsx +++ b/packages/mobile/src/screens/track-screen/DownloadSection.tsx @@ -65,10 +65,11 @@ export const DownloadSection = ({ trackId }: { trackId: ID }) => { getTrack(state, { id: trackId }) ) const { stemTracks } = useCurrentStems({ trackId }) - const { hasStreamAccess } = useGatedContentAccess(track) + const { hasStreamAccess, hasDownloadAccess } = useGatedContentAccess(track) // Hide the download all button if there aren't multiple downloads and if the user // happens to not have stream access to the track - const shouldDisplayDownloadAll = stemTracks.length > 1 && hasStreamAccess + const shouldDisplayDownloadAll = + stemTracks.length > 1 && hasStreamAccess && hasDownloadAccess const { price, shouldDisplayPremiumDownloadLocked, From 0306d604df479440524fc27bdb61afff2990faab Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Wed, 7 Feb 2024 10:32:04 -0800 Subject: [PATCH 7/8] Address comments --- .../premium-track-purchase-drawer/PurchaseSummaryTable.tsx | 2 +- .../components/PurchaseSummaryTable.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mobile/src/components/premium-track-purchase-drawer/PurchaseSummaryTable.tsx b/packages/mobile/src/components/premium-track-purchase-drawer/PurchaseSummaryTable.tsx index 65d0106cd8b..6a0c618b180 100644 --- a/packages/mobile/src/components/premium-track-purchase-drawer/PurchaseSummaryTable.tsx +++ b/packages/mobile/src/components/premium-track-purchase-drawer/PurchaseSummaryTable.tsx @@ -49,7 +49,7 @@ export const PurchaseSummaryTable = ({ }) } - const downloadCount = (stemsPurchaseCount || 0) + (downloadPurchaseCount || 0) + const downloadCount = (stemsPurchaseCount ?? 0) + (downloadPurchaseCount ?? 0) if (downloadCount > 0) { items.push({ id: 'premiumTrackDownload', diff --git a/packages/web/src/components/premium-content-purchase-modal/components/PurchaseSummaryTable.tsx b/packages/web/src/components/premium-content-purchase-modal/components/PurchaseSummaryTable.tsx index a19f7ffa904..4e216b41981 100644 --- a/packages/web/src/components/premium-content-purchase-modal/components/PurchaseSummaryTable.tsx +++ b/packages/web/src/components/premium-content-purchase-modal/components/PurchaseSummaryTable.tsx @@ -45,7 +45,7 @@ export const PurchaseSummaryTable = ({ value: messages.price(formatPrice(basePrice)) }) } - const downloadCount = (stemsPurchaseCount || 0) + (downloadPurchaseCount || 0) + const downloadCount = (stemsPurchaseCount ?? 0) + (downloadPurchaseCount ?? 0) if (downloadCount > 0) { items.push({ id: 'premiumTrackDownload', From 36df262ceb2a4e10e92ed1cd6b39ab87a4e5dba0 Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Wed, 7 Feb 2024 10:43:35 -0800 Subject: [PATCH 8/8] Remove hasStreamAccess check --- .../mobile/src/screens/track-screen/DownloadSection.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/mobile/src/screens/track-screen/DownloadSection.tsx b/packages/mobile/src/screens/track-screen/DownloadSection.tsx index 818c09e3557..8071bb075e7 100644 --- a/packages/mobile/src/screens/track-screen/DownloadSection.tsx +++ b/packages/mobile/src/screens/track-screen/DownloadSection.tsx @@ -65,11 +65,8 @@ export const DownloadSection = ({ trackId }: { trackId: ID }) => { getTrack(state, { id: trackId }) ) const { stemTracks } = useCurrentStems({ trackId }) - const { hasStreamAccess, hasDownloadAccess } = useGatedContentAccess(track) - // Hide the download all button if there aren't multiple downloads and if the user - // happens to not have stream access to the track - const shouldDisplayDownloadAll = - stemTracks.length > 1 && hasStreamAccess && hasDownloadAccess + const { hasDownloadAccess } = useGatedContentAccess(track) + const shouldDisplayDownloadAll = stemTracks.length > 1 && hasDownloadAccess const { price, shouldDisplayPremiumDownloadLocked,