diff --git a/packages/common/src/store/cache/collections/selectors.ts b/packages/common/src/store/cache/collections/selectors.ts index 99639df50f7..3a0ff998f92 100644 --- a/packages/common/src/store/cache/collections/selectors.ts +++ b/packages/common/src/store/cache/collections/selectors.ts @@ -69,6 +69,32 @@ export const getCollectionsByUid = (state: CommonState) => { }, {} as { [uid: string]: Collection | null }) } +const getCollectionTracks = (state: CommonState, { id }: { id?: ID }) => { + const collection = getCollection(state, { id }) + const collectionTrackIds = collection?.playlist_contents.track_ids.map( + (track_id) => track_id.track // track === actual track id, oof + ) + return getTracks(state, { ids: collectionTrackIds }) +} + +export const getIsCollectionEmpty = ( + state: CommonState, + { id }: { id?: ID } +) => { + const collectionTracks = getCollectionTracks(state, { id }) + + return Object.values(collectionTracks).length === 0 +} + +export const getCollecitonHasHiddenTracks = ( + state: CommonState, + { id }: { id?: ID } +) => { + const collectionTracks = getCollectionTracks(state, { id }) + + return Object.values(collectionTracks)?.some((track) => track.is_unlisted) +} + export const getStatuses = (state: CommonState, props: { ids: ID[] }) => { const statuses: { [id: number]: Status } = {} props.ids.forEach((id) => { diff --git a/packages/common/src/store/ui/add-to-playlist/actions.ts b/packages/common/src/store/ui/add-to-playlist/actions.ts index 910010545b1..ea9d1511e41 100644 --- a/packages/common/src/store/ui/add-to-playlist/actions.ts +++ b/packages/common/src/store/ui/add-to-playlist/actions.ts @@ -8,10 +8,18 @@ export const CLOSE = 'ADD_TO_PLAYLIST/CLOSE' export const requestOpen = createCustomAction( REQUEST_OPEN, - (trackId: ID, trackTitle: string) => ({ trackId, trackTitle }) + (trackId: ID, trackTitle: string, isUnlisted?: boolean) => ({ + trackId, + trackTitle, + isUnlisted + }) ) export const open = createCustomAction( OPEN, - (trackId: ID, trackTitle: string) => ({ trackId, trackTitle }) + (trackId: ID, trackTitle: string, isUnlisted?: boolean) => ({ + trackId, + trackTitle, + isUnlisted + }) ) export const close = createCustomAction(CLOSE, () => {}) diff --git a/packages/common/src/store/ui/add-to-playlist/reducer.ts b/packages/common/src/store/ui/add-to-playlist/reducer.ts index 096ff3a687c..b0cb4cbbb43 100644 --- a/packages/common/src/store/ui/add-to-playlist/reducer.ts +++ b/packages/common/src/store/ui/add-to-playlist/reducer.ts @@ -9,12 +9,14 @@ type AddToPlaylistActions = ActionType export type AddToPlaylistState = { trackId: ID | null trackTitle: string | null + isUnlisted: boolean } const initialState = { isOpen: false, trackId: null, - trackTitle: null + trackTitle: null, + isUnlisted: false } const reducer = createReducer( @@ -24,14 +26,16 @@ const reducer = createReducer( return { ...state, trackId: action.trackId, - trackTitle: action.trackTitle + trackTitle: action.trackTitle, + isUnlisted: action.isUnlisted ?? false } }, [actions.CLOSE](state, _action) { return { ...state, trackId: null, - trackTitle: null + trackTitle: null, + isUnlisted: false } } } diff --git a/packages/common/src/store/ui/add-to-playlist/selectors.ts b/packages/common/src/store/ui/add-to-playlist/selectors.ts index 38f5b0560db..646d8e56929 100644 --- a/packages/common/src/store/ui/add-to-playlist/selectors.ts +++ b/packages/common/src/store/ui/add-to-playlist/selectors.ts @@ -5,3 +5,5 @@ const getBaseState = (state: CommonState) => state.ui.addToPlaylist export const getTrackId = (state: CommonState) => getBaseState(state).trackId export const getTrackTitle = (state: CommonState) => getBaseState(state).trackTitle +export const getTrackIsUnlisted = (state: CommonState) => + getBaseState(state).isUnlisted diff --git a/packages/mobile/src/components/add-to-playlist-drawer/AddToPlaylistDrawer.tsx b/packages/mobile/src/components/add-to-playlist-drawer/AddToPlaylistDrawer.tsx index 71f93d82719..5295df0b369 100644 --- a/packages/mobile/src/components/add-to-playlist-drawer/AddToPlaylistDrawer.tsx +++ b/packages/mobile/src/components/add-to-playlist-drawer/AddToPlaylistDrawer.tsx @@ -25,12 +25,14 @@ import { AddCollectionCard } from '../collection-list/AddCollectionCard' import type { ImageProps } from '../image/FastImage' const { addTrackToPlaylist, createPlaylist } = cacheCollectionsActions -const { getTrackId, getTrackTitle } = addToPlaylistUISelectors +const { getTrackId, getTrackTitle, getTrackIsUnlisted } = + addToPlaylistUISelectors const { getAccountWithOwnPlaylists } = accountSelectors const messages = { title: 'Add To Playlist', - addedToast: 'Added To Playlist!' + addedToast: 'Added To Playlist!', + hiddenAdd: 'You cannot add hidden tracks to a public playlist.' } const useStyles = makeStyles(() => ({ @@ -55,6 +57,7 @@ export const AddToPlaylistDrawer = () => { const { onClose } = useDrawerState('AddToPlaylist') const trackId = useSelector(getTrackId) const trackTitle = useSelector(getTrackTitle) + const isTrackUnlisted = useSelector(getTrackIsUnlisted) const user = useSelector(getAccountWithOwnPlaylists) const { isEnabled: isPlaylistUpdatesEnabled } = useFeatureFlag( FeatureFlags.PLAYLIST_UPDATES_PRE_QA @@ -97,12 +100,18 @@ export const AddToPlaylistDrawer = () => { /> ) : ( { + // Don't add if the track is hidden, but playlist is public + if (isTrackUnlisted && !item.is_private) { + toast({ content: messages.hiddenAdd }) + return + } toast({ content: messages.addedToast }) dispatch(addTrackToPlaylist(trackId!, item.playlist_id)) onClose() @@ -113,6 +122,7 @@ export const AddToPlaylistDrawer = () => { [ addToNewPlaylist, dispatch, + isTrackUnlisted, onClose, renderImage, toast, diff --git a/packages/mobile/src/components/details-tile/DetailsTile.tsx b/packages/mobile/src/components/details-tile/DetailsTile.tsx index 3ea0e5cbe0b..022d7704054 100644 --- a/packages/mobile/src/components/details-tile/DetailsTile.tsx +++ b/packages/mobile/src/components/details-tile/DetailsTile.tsx @@ -181,6 +181,7 @@ const useStyles = makeStyles(({ palette, spacing, typography }) => ({ * The details shown at the top of the Track Screen and Collection Screen */ export const DetailsTile = ({ + collectionId, coSign, description, descriptionLinkPressSource, @@ -393,6 +394,7 @@ export const DetailsTile = ({ hideShare={hideShare} isOwner={isOwner} isPlaylist={isPlaylist} + collectionId={collectionId} isPublished={isPublished} onPressEdit={onPressEdit} onPressOverflow={onPressOverflow} diff --git a/packages/mobile/src/components/details-tile/DetailsTileActionButtons.tsx b/packages/mobile/src/components/details-tile/DetailsTileActionButtons.tsx index 1c6508eee0c..5f624ab0ecd 100644 --- a/packages/mobile/src/components/details-tile/DetailsTileActionButtons.tsx +++ b/packages/mobile/src/components/details-tile/DetailsTileActionButtons.tsx @@ -1,5 +1,7 @@ -import { FeatureFlags } from '@audius/common' +import type { CommonState, ID } from '@audius/common' +import { FeatureFlags, cacheCollectionsSelectors } from '@audius/common' import { View } from 'react-native' +import { useSelector } from 'react-redux' import IconKebabHorizontal from 'app/assets/images/iconKebabHorizontal.svg' import IconPencil from 'app/assets/images/iconPencil.svg' @@ -13,12 +15,18 @@ import { flexRowCentered, makeStyles } from 'app/styles' import type { GestureResponderHandler } from 'app/types/gesture' import { useThemeColors } from 'app/utils/theme' -// const messages = { -// publishButtonDisabledContent: 'You must add at least 1 track.', -// shareButtonDisabledContent: 'You can’t share an empty playlist.' -// } +const { getCollecitonHasHiddenTracks, getIsCollectionEmpty } = + cacheCollectionsSelectors + +const messages = { + publishButtonEmptyDisabledContent: 'You must add at least 1 track.', + publishButtonHiddenDisabledContent: + 'You cannot make a playlist with hidden tracks public.', + shareButtonDisabledContent: 'You can’t share an empty playlist.' +} type DetailsTileActionButtonsProps = { + collectionId?: ID hasReposted: boolean hasSaved: boolean isOwner: boolean @@ -59,7 +67,7 @@ const useStyles = makeStyles(({ palette }) => ({ marginHorizontal: 16 }, - editButton: { + editButtonIcon: { width: 27 } })) @@ -68,6 +76,7 @@ const useStyles = makeStyles(({ palette }) => ({ * The action buttons on track and playlist screens */ export const DetailsTileActionButtons = ({ + collectionId, hasReposted, hasSaved, isPlaylist, @@ -89,6 +98,12 @@ export const DetailsTileActionButtons = ({ const { isEnabled: isPlaylistUpdatesEnabled } = useFeatureFlag( FeatureFlags.PLAYLIST_UPDATES_PRE_QA ) + const isCollectionEmpty = useSelector((state: CommonState) => + getIsCollectionEmpty(state, { id: collectionId }) + ) + const collectionHasHiddenTracks = useSelector((state: CommonState) => + getCollecitonHasHiddenTracks(state, { id: collectionId }) + ) const repostButton = (