Skip to content

Commit

Permalink
[C-2771, C-2772] Add duplicate add to playlist confirmation drawer an…
Browse files Browse the repository at this point in the history
…d modal (#3601)
  • Loading branch information
Kyle-Shanks committed Jun 20, 2023
1 parent 381cc0c commit 3810b2f
Show file tree
Hide file tree
Showing 21 changed files with 419 additions and 16 deletions.
4 changes: 4 additions & 0 deletions apps/audius-client/packages/common/src/store/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ import createPlaylistModalReducer from './ui/createPlaylistModal/reducer'
import { CreatePlaylistModalState } from './ui/createPlaylistModal/types'
import deletePlaylistConfirmationReducer from './ui/delete-playlist-confirmation-modal/slice'
import { DeletePlaylistConfirmationModalState } from './ui/delete-playlist-confirmation-modal/types'
import duplicateAddConfirmationReducer from './ui/duplicate-add-confirmation-modal/slice'
import { DuplicateAddConfirmationModalState } from './ui/duplicate-add-confirmation-modal/types'
import mobileOverflowModalReducer from './ui/mobile-overflow-menu/slice'
import { MobileOverflowModalState } from './ui/mobile-overflow-menu/types'
import modalsReducer from './ui/modals/slice'
Expand Down Expand Up @@ -180,6 +182,7 @@ export const reducers = () => ({
createPlaylistModal: createPlaylistModalReducer,
collectibleDetails: collectibleDetailsReducer,
deletePlaylistConfirmationModal: deletePlaylistConfirmationReducer,
duplicateAddConfirmationModal: duplicateAddConfirmationReducer,
publishPlaylistConfirmationModal: publishPlaylistConfirmationReducer,
mobileOverflowModal: mobileOverflowModalReducer,
modals: modalsReducer,
Expand Down Expand Up @@ -298,6 +301,7 @@ export type CommonState = {
createPlaylistModal: CreatePlaylistModalState
collectibleDetails: CollectibleDetailsState
deletePlaylistConfirmationModal: DeletePlaylistConfirmationModalState
duplicateAddConfirmationModal: DuplicateAddConfirmationModalState
publishPlaylistConfirmationModal: PublishPlaylistConfirmationModalState
mobileOverflowModal: MobileOverflowModalState
modals: ModalsState
Expand Down
2 changes: 2 additions & 0 deletions apps/audius-client/packages/common/src/store/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
searchUsersModalSagas,
toastSagas,
deletePlaylistConfirmationModalUISagas,
duplicateAddConfirmationModalUISagas,
publishPlaylistConfirmationModalUISagas,
mobileOverflowMenuUISagas,
shareModalUISagas
Expand Down Expand Up @@ -48,6 +49,7 @@ export const sagas = (_ctx: CommonStoreContext) => ({
shareModalUI: shareModalUISagas,
mobileOverflowMenuUI: mobileOverflowMenuUISagas,
deletePlaylistConfirmationModalUI: deletePlaylistConfirmationModalUISagas,
duplidateAddConfirmationModalUI: duplicateAddConfirmationModalUISagas,
publishPlaylistConfirmationModalUI: publishPlaylistConfirmationModalUISagas,
player: playerSagas,
playbackPosition: playbackPositionSagas,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { takeEvery } from 'redux-saga/effects'
import { put } from 'typed-redux-saga'

import { setVisibility } from '../modals/slice'

import { open, OpenPayload, requestOpen } from './slice'

function* handleRequestOpen(action: OpenPayload) {
const { payload } = action
yield* put(open(payload))
yield* put(
setVisibility({ modal: 'DuplicateAddConfirmation', visible: true })
)
}

function* watchHandleRequestOpen() {
yield takeEvery(requestOpen, handleRequestOpen)
}

export default function sagas() {
return [watchHandleRequestOpen]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { CommonState } from 'store/commonStore'

export const getPlaylistId = (state: CommonState) =>
state.ui.duplicateAddConfirmationModal.playlistId

export const getTrackId = (state: CommonState) =>
state.ui.duplicateAddConfirmationModal.trackId
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'

import { Nullable } from 'utils/typeUtils'

import { ID } from '../../../models/Identifiers'

type DuplicateAddConfirmationState = {
playlistId: Nullable<ID>
trackId: Nullable<ID>
}

export type OpenPayload = PayloadAction<{ playlistId: ID; trackId: ID }>

const initialState: DuplicateAddConfirmationState = {
trackId: null,
playlistId: null
}

const slice = createSlice({
name: 'applications/ui/duplicateAddConfirmation',
initialState,
reducers: {
requestOpen: (_state, _action: OpenPayload) => {},
open: (state, action: OpenPayload) => {
state.playlistId = action.payload.playlistId
state.trackId = action.payload.trackId
}
}
})

export const { open, requestOpen } = slice.actions
export const actions = slice.actions
export default slice.reducer
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ID } from '../../../models/Identifiers'

export type DuplicateAddConfirmationModalState = {
isOpen: boolean
playlistId: ID | null
trackId: ID | null
}
8 changes: 8 additions & 0 deletions apps/audius-client/packages/common/src/store/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ export {
export { default as deletePlaylistConfirmationModalUISagas } from './delete-playlist-confirmation-modal/sagas'
export * from './delete-playlist-confirmation-modal/types'

export * as duplicateAddConfirmationModalUISelectors from './duplicate-add-confirmation-modal/selectors'
export {
default as duplicateAddConfirmationModalUIReducer,
actions as duplicateAddConfirmationModalUIActions
} from './duplicate-add-confirmation-modal/slice'
export { default as duplicateAddConfirmationModalUISagas } from './duplicate-add-confirmation-modal/sagas'
export * from './duplicate-add-confirmation-modal/types'

export * as mobileOverflowMenuUISelectors from './mobile-overflow-menu/selectors'
export {
default as mobileOverflowMenuUIReducer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const initialState: ModalsState = {
Overflow: false,
AddToPlaylist: false,
DeletePlaylistConfirmation: false,
DuplicateAddConfirmation: false,
FeatureFlagOverride: false,
BuyAudio: false,
BuyAudioRecovery: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ export type Modals =
| 'ProfileActions'
| 'PublishPlaylistConfirmation'
| 'AiAttributionSettings'
| 'DuplicateAddConfirmation'

export type ModalsState = { [modal in Modals]: boolean | 'closing' }
2 changes: 2 additions & 0 deletions apps/audius-client/packages/mobile/src/app/Drawers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { DeactivateAccountConfirmationDrawer } from 'app/components/deactivate-a
import { DeleteChatDrawer } from 'app/components/delete-chat-drawer'
import { DeletePlaylistConfirmationDrawer } from 'app/components/delete-playlist-confirmation-drawer'
import { DownloadTrackProgressDrawer } from 'app/components/download-track-progress-drawer'
import { DuplicateAddConfirmationDrawer } from 'app/components/duplicate-add-confirmation-drawer'
import { EditCollectiblesDrawer } from 'app/components/edit-collectibles-drawer'
import { EnablePushNotificationsDrawer } from 'app/components/enable-push-notifications-drawer'
import { FeedFilterDrawer } from 'app/components/feed-filter-drawer'
Expand Down Expand Up @@ -100,6 +101,7 @@ const commonDrawersMap: { [Modal in Modals]?: ComponentType } = {
AddToPlaylist: AddToPlaylistDrawer,
AudioBreakdown: AudioBreakdownDrawer,
DeletePlaylistConfirmation: DeletePlaylistConfirmationDrawer,
DuplicateAddConfirmation: DuplicateAddConfirmationDrawer,
VipDiscord: VipDiscordDrawer,
ProfileActions: ProfileActionsDrawer,
PlaybackRate: PlaybackRateDrawer,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useCallback } from 'react'
import { useCallback, useMemo } from 'react'

import type { Collection } from '@audius/common'
import {
duplicateAddConfirmationModalUIActions,
SquareSizes,
CreatePlaylistSource,
accountSelectors,
Expand All @@ -28,6 +29,8 @@ const { addTrackToPlaylist, createPlaylist } = cacheCollectionsActions
const { getTrackId, getTrackTitle, getTrackIsUnlisted } =
addToPlaylistUISelectors
const { getAccountWithOwnPlaylists } = accountSelectors
const { requestOpen: openDuplicateAddConfirmation } =
duplicateAddConfirmationModalUIActions

const messages = {
title: 'Add To Playlist',
Expand Down Expand Up @@ -77,6 +80,15 @@ export const AddToPlaylistDrawer = () => {

const userPlaylists = user?.playlists ?? []

const playlistTrackIdMap = useMemo(() => {
const playlists = user?.playlists ?? []
return playlists.reduce((acc, playlist) => {
const trackIds = playlist.playlist_contents.track_ids.map((t) => t.track)
acc[playlist.playlist_id] = trackIds
return acc
}, {})
}, [user?.playlists])

const addToNewPlaylist = useCallback(() => {
const metadata = { playlist_name: trackTitle ?? 'New Playlist' }
dispatch(
Expand Down Expand Up @@ -107,13 +119,28 @@ export const AddToPlaylistDrawer = () => {
primaryText={item.playlist_name}
secondaryText={user?.name}
onPress={() => {
if (!trackId) return

// 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))

const doesPlaylistContainTrack =
playlistTrackIdMap[item.playlist_id]?.includes(trackId)

if (isPlaylistUpdatesEnabled && doesPlaylistContainTrack) {
dispatch(
openDuplicateAddConfirmation({
playlistId: item.playlist_id,
trackId
})
)
} else {
toast({ content: messages.addedToast })
dispatch(addTrackToPlaylist(trackId, item.playlist_id))
}
onClose()
}}
renderImage={renderImage(item)}
Expand All @@ -122,8 +149,10 @@ export const AddToPlaylistDrawer = () => {
[
addToNewPlaylist,
dispatch,
isPlaylistUpdatesEnabled,
isTrackUnlisted,
onClose,
playlistTrackIdMap,
renderImage,
toast,
trackId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { useCallback } from 'react'

import {
cacheCollectionsActions,
cacheCollectionsSelectors,
duplicateAddConfirmationModalUISelectors,
fillString
} from '@audius/common'
import { View } from 'react-native'
import { useDispatch, useSelector } from 'react-redux'

import { Text, Button } from 'app/components/core'
import { useToast } from 'app/hooks/useToast'
import { makeStyles } from 'app/styles'

import { useDrawerState } from '../drawer'
import Drawer from '../drawer/Drawer'
const { getPlaylistId, getTrackId } = duplicateAddConfirmationModalUISelectors
const { addTrackToPlaylist } = cacheCollectionsActions
const { getCollection } = cacheCollectionsSelectors

const messages = {
drawerTitle: 'Already Added',
drawerBody: 'This is already in your%0 playlist.',
buttonAddText: 'Add Anyway',
buttonCancelText: "Don't Add",
addedToast: 'Added To Playlist!'
}

const useStyles = makeStyles(({ palette, spacing }) => ({
title: {
flexDirection: 'row',
justifyContent: 'center',
gap: spacing(2),
alignItems: 'center',
paddingVertical: spacing(6),
borderBottomColor: palette.neutralLight8,
borderBottomWidth: 1
},
titleText: {
textTransform: 'uppercase'
},
container: {
marginHorizontal: spacing(4)
},
body: {
margin: spacing(4),
lineHeight: spacing(6),
textAlign: 'center'
},
buttonContainer: {
gap: spacing(2),
marginBottom: spacing(8)
}
}))

export const DuplicateAddConfirmationDrawer = () => {
const playlistId = useSelector(getPlaylistId)
const trackId = useSelector(getTrackId)
const playlist = useSelector((state) =>
getCollection(state, { id: playlistId })
)
const dispatch = useDispatch()
const styles = useStyles()
const { toast } = useToast()
const { isOpen, onClose } = useDrawerState('DuplicateAddConfirmation')

const handleAdd = useCallback(() => {
if (playlistId && trackId) {
toast({ content: messages.addedToast })
dispatch(addTrackToPlaylist(trackId, playlistId))
}
onClose()
}, [playlistId, trackId, onClose, toast, dispatch])

return (
<Drawer isOpen={isOpen} onClose={onClose}>
<View style={styles.container}>
<View style={styles.title}>
<Text
weight='heavy'
color='neutral'
fontSize='xl'
style={styles.titleText}
>
{messages.drawerTitle}
</Text>
</View>
<Text style={styles.body} fontSize='large' weight='medium'>
{fillString(
messages.drawerBody,
playlist ? ` "${playlist.playlist_name}"` : ''
)}
</Text>
<View style={styles.buttonContainer}>
<Button
title={messages.buttonCancelText}
variant='primary'
size='large'
fullWidth
onPress={onClose}
/>
<Button
title={messages.buttonAddText}
variant='common'
size='large'
fullWidth
onPress={handleAdd}
/>
</View>
</View>
</Drawer>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './DuplicateAddConfirmationDrawer'
2 changes: 2 additions & 0 deletions apps/audius-client/packages/mobile/src/store/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
premiumContentSagas,
remoteConfigSagas as remoteConfig,
deletePlaylistConfirmationModalUISagas as deletePlaylistConfirmationModalSagas,
duplicateAddConfirmationModalUISagas as duplicateAddConfirmationModalSagas,
publishPlaylistConfirmationModalUISagas as publishPlaylistConfirmationModalSagas,
mobileOverflowMenuUISagas as overflowMenuSagas,
shareModalUISagas as shareModalSagas,
Expand Down Expand Up @@ -172,6 +173,7 @@ export default function* rootSaga() {
...rateCtaSagas(),
...deactivateAccountSagas(),
...deletePlaylistConfirmationModalSagas(),
...duplicateAddConfirmationModalSagas(),
...shareModalSagas(),
...vipDiscordModalSagas(),
...themeSagas(),
Expand Down
Loading

0 comments on commit 3810b2f

Please sign in to comment.