diff --git a/packages/common/src/api/library.ts b/packages/common/src/api/library.ts index 99000a9d0a..d6fc097add 100644 --- a/packages/common/src/api/library.ts +++ b/packages/common/src/api/library.ts @@ -37,7 +37,7 @@ const fetchLibraryCollections = async ({ offset, limit, category, - query = '', + query, sortMethod = 'added_date', sortDirection = 'desc' } = args diff --git a/packages/common/src/models/Analytics.ts b/packages/common/src/models/Analytics.ts index d40e88b8aa..66cfa99e4b 100644 --- a/packages/common/src/models/Analytics.ts +++ b/packages/common/src/models/Analytics.ts @@ -586,7 +586,7 @@ export enum RepostSource { TRACK_PAGE = 'page', COLLECTION_PAGE = 'collection page', HISTORY_PAGE = 'history page', - FAVORITES_PAGE = 'favorites page', + LIBRARY_PAGE = 'library page', OVERFLOW = 'overflow', TRACK_LIST = 'track list' } @@ -597,7 +597,7 @@ export enum FavoriteSource { TRACK_PAGE = 'page', COLLECTION_PAGE = 'collection page', HISTORY_PAGE = 'history page', - FAVORITES_PAGE = 'favorites page', + LIBRARY_PAGE = 'library page', OVERFLOW = 'overflow', TRACK_LIST = 'track list', SIGN_UP = 'sign up', @@ -685,7 +685,7 @@ export enum CreatePlaylistSource { NAV = 'nav', CREATE_PAGE = 'create page', FROM_TRACK = 'from track', - FAVORITES_PAGE = 'favorites page', + LIBRARY_PAGE = 'library page', PROFILE_PAGE = 'profile page' } @@ -1025,7 +1025,7 @@ export enum PlaybackSource { PLAYLIST_TRACK = 'playlist page track list', PLAYLIST_TILE_TRACK = 'playlist track tile', HISTORY_PAGE = 'history page', - FAVORITES_PAGE = 'favorites page', + LIBRARY_PAGE = 'library page', PASSIVE = 'passive', EMBED_PLAYER = 'embed player', CHAT_TRACK = 'chat_track', diff --git a/packages/common/src/store/cache/collections/selectors.ts b/packages/common/src/store/cache/collections/selectors.ts index 217e5fe9a3..df835914e9 100644 --- a/packages/common/src/store/cache/collections/selectors.ts +++ b/packages/common/src/store/cache/collections/selectors.ts @@ -195,7 +195,7 @@ export const getTracksFromCollection = ( .filter(Boolean) as EnhancedCollectionTrack[] } -type EnhancedCollection = Collection & { user: User } +export type EnhancedCollection = Collection & { user: User } export const getCollectionWithUser = ( state: CommonState, props: { id?: ID } diff --git a/packages/common/src/store/cache/collections/utils/index.ts b/packages/common/src/store/cache/collections/utils/index.ts index cf6599bab6..36c65f4890 100644 --- a/packages/common/src/store/cache/collections/utils/index.ts +++ b/packages/common/src/store/cache/collections/utils/index.ts @@ -1,3 +1,4 @@ -// TODO: Migrate utils from packages/web/common/store/cache/collections/utils to here +// TODO: Finish common migration - migrate utils from packages/web/common/store/cache/collections/utils to here: +// https://linear.app/audius/issue/C-1280/migrate-remaining-webcommon-to-common export * from './reformatCollection' diff --git a/packages/common/src/store/pages/index.ts b/packages/common/src/store/pages/index.ts index f26f5360fa..473e9870c2 100644 --- a/packages/common/src/store/pages/index.ts +++ b/packages/common/src/store/pages/index.ts @@ -61,6 +61,7 @@ export { tracksActions as savedPageTracksLineupActions } from './saved-page/line export * as savedPageActions from './saved-page/actions' export * as savedPageSelectors from './saved-page/selectors' export * from './saved-page/types' +export * from './saved-page/utils' export { default as savedPageReducer } from './saved-page/reducer' export { diff --git a/packages/common/src/store/pages/saved-page/actions.ts b/packages/common/src/store/pages/saved-page/actions.ts index 13c303faf8..57fc4e738a 100644 --- a/packages/common/src/store/pages/saved-page/actions.ts +++ b/packages/common/src/store/pages/saved-page/actions.ts @@ -1,5 +1,7 @@ -// @ts-nocheck -// TODO(nkang) - convert to TS +import { Favorite } from 'models/Favorite' + +import { LibraryCategory, LibraryCategoryType, SavedPageTabs } from './types' + export const FETCH_SAVES = 'SAVED/FETCH_SAVES' export const FETCH_SAVES_REQUESTED = 'SAVED/FETCH_SAVES_REQUESTED' export const FETCH_SAVES_SUCCEEDED = 'SAVED/FETCH_SAVES_SUCCEEDED' @@ -13,12 +15,21 @@ export const FETCH_MORE_SAVES_FAILED = 'SAVED/FETCH_MORE_SAVES_FAILED' // Usually when filtering export const END_FETCHING = 'SAVED/END_FETCHING' -export const ADD_LOCAL_SAVE = 'SAVED/ADD_LOCAL_SAVE' -export const REMOVE_LOCAL_SAVE = 'SAVED/REMOVE_LOCAL_SAVE' +export const ADD_LOCAL_TRACK = 'SAVED/ADD_LOCAL_TRACK' +export const REMOVE_LOCAL_TRACK = 'SAVED/REMOVE_LOCAL_TRACK' +export const ADD_LOCAL_COLLECTION = 'SAVED/ADD_LOCAL_COLLECTION' +export const REMOVE_LOCAL_COLLECTION = 'SAVED/REMOVE_LOCAL_COLLECTION' + +export const SET_SELECTED_CATEGORY = 'SAVED/SET_SELECTED_CATEGORY' +export const INIT_COLLECTIONS_CATEGORY_FROM_LOCAL_STORAGE = + 'SAVED/INIT_COLLECTIONS_CATEGORY_FROM_LOCAL_STORAGE' +export const INIT_TRACKS_CATEGORY_FROM_LOCAL_STORAGE = + 'SAVED/INIT_TRACKS_CATEGORY_FROM_LOCAL_STORAGE' export const fetchSaves = ( // the filter query for the "get tracks" query query = '', + category: LibraryCategoryType = LibraryCategory.Favorite, // the sort method for the "get tracks" query sortMethod = '', // the sort direction for the "get tracks" query @@ -29,6 +40,7 @@ export const fetchSaves = ( limit = 50 ) => ({ type: FETCH_SAVES, + category, offset, limit, query, @@ -39,6 +51,7 @@ export const fetchSaves = ( export const fetchMoreSaves = ( // the filter query for the "get tracks" query query = '', + category: LibraryCategoryType = LibraryCategory.Favorite, // the sort method for the "get tracks" query sortMethod = '', // the sort direction for the "get tracks" query @@ -49,6 +62,7 @@ export const fetchMoreSaves = ( limit = 50 ) => ({ type: FETCH_MORE_SAVES, + category, offset, limit, query, @@ -60,7 +74,7 @@ export const fetchSavesRequested = () => ({ type: FETCH_SAVES_REQUESTED }) -export const fetchSavesSucceeded = (saves) => ({ +export const fetchSavesSucceeded = (saves: Favorite[]) => ({ type: FETCH_SAVES_SUCCEEDED, saves }) @@ -69,7 +83,7 @@ export const fetchSavesFailed = () => ({ type: FETCH_SAVES_FAILED }) -export const fetchMoreSavesSucceeded = (saves, offset) => ({ +export const fetchMoreSavesSucceeded = (saves: Favorite[], offset: number) => ({ type: FETCH_MORE_SAVES_SUCCEEDED, saves, offset @@ -79,18 +93,90 @@ export const fetchMoreSavesFailed = () => ({ type: FETCH_MORE_SAVES_FAILED }) -export const endFetching = (endIndex) => ({ +export const endFetching = (endIndex: number) => ({ type: END_FETCHING, endIndex }) -export const addLocalSave = (trackId, uid) => ({ - type: ADD_LOCAL_SAVE, +export const addLocalTrack = ({ trackId, - uid + uid, + category +}: { + trackId: number + uid: string + category: LibraryCategoryType +}) => ({ + type: ADD_LOCAL_TRACK, + trackId, + uid, + category +}) + +export const removeLocalTrack = ({ + trackId, + category +}: { + trackId: number + category: LibraryCategoryType +}) => ({ + type: REMOVE_LOCAL_TRACK, + trackId, + category +}) + +export const addLocalCollection = ({ + collectionId, + isAlbum, + category +}: { + collectionId: number + isAlbum: boolean + category: LibraryCategoryType +}) => ({ + type: ADD_LOCAL_COLLECTION, + collectionId, + isAlbum, + category +}) + +export const removeLocalCollection = ({ + collectionId, + isAlbum, + category +}: { + collectionId: number + isAlbum: boolean + category: LibraryCategoryType +}) => ({ + type: REMOVE_LOCAL_COLLECTION, + collectionId, + isAlbum, + category +}) + +export const initializeTracksCategoryFromLocalStorage = ( + category: LibraryCategoryType +) => ({ + type: INIT_TRACKS_CATEGORY_FROM_LOCAL_STORAGE, + category +}) + +export const initializeCollectionsCategoryFromLocalStorage = ( + category: LibraryCategoryType +) => ({ + type: INIT_COLLECTIONS_CATEGORY_FROM_LOCAL_STORAGE, + category }) -export const removeLocalSave = (trackId) => ({ - type: REMOVE_LOCAL_SAVE, - trackId +export const setSelectedCategory = ({ + category, + currentTab +}: { + category: LibraryCategoryType + currentTab: SavedPageTabs +}) => ({ + type: SET_SELECTED_CATEGORY, + category, + currentTab }) diff --git a/packages/common/src/store/pages/saved-page/reducer.ts b/packages/common/src/store/pages/saved-page/reducer.ts index a6f430f588..9b55f42c47 100644 --- a/packages/common/src/store/pages/saved-page/reducer.ts +++ b/packages/common/src/store/pages/saved-page/reducer.ts @@ -1,42 +1,102 @@ -// @ts-nocheck -// TODO(nkang) - convert to TS +import { ID } from 'models/Identifiers' import { asLineup } from 'store/lineup/reducer' import { + ADD_LOCAL_COLLECTION, + ADD_LOCAL_TRACK, + END_FETCHING, + FETCH_MORE_SAVES, + FETCH_MORE_SAVES_FAILED, + FETCH_MORE_SAVES_SUCCEEDED, FETCH_SAVES, + FETCH_SAVES_FAILED, FETCH_SAVES_REQUESTED, FETCH_SAVES_SUCCEEDED, - FETCH_SAVES_FAILED, - FETCH_MORE_SAVES, - FETCH_MORE_SAVES_SUCCEEDED, - FETCH_MORE_SAVES_FAILED, - ADD_LOCAL_SAVE, - REMOVE_LOCAL_SAVE, - END_FETCHING + INIT_COLLECTIONS_CATEGORY_FROM_LOCAL_STORAGE, + INIT_TRACKS_CATEGORY_FROM_LOCAL_STORAGE, + REMOVE_LOCAL_COLLECTION, + REMOVE_LOCAL_TRACK, + SET_SELECTED_CATEGORY } from 'store/pages/saved-page/actions' import tracksReducer, { initialState as initialLineupState } from 'store/pages/saved-page/lineups/tracks/reducer' import { signOut } from 'store/sign-out/slice' +import { ActionsMap } from 'utils/reducer' import { PREFIX as tracksPrefix } from './lineups/tracks/actions' +import { LibraryCategory, LibraryCategoryType, SavedPageState } from './types' +import { calculateNewLibraryCategories } from './utils' const initialState = { - // id => uid - localSaves: {}, - saves: [], + trackSaves: [], initialFetch: false, hasReachedEnd: false, fetchingMore: false, - tracks: initialLineupState + tracks: initialLineupState, + tracksCategory: LibraryCategory.Favorite, + collectionsCategory: LibraryCategory.Favorite, + local: { + track: { + favorites: { + added: {}, + removed: {} + }, + reposts: { + added: {}, + removed: {} + }, + purchased: { + added: {} + } + }, + album: { + favorites: { + added: [], + removed: [] + }, + reposts: { + added: [], + removed: [] + }, + purchased: { + added: [] + } + }, + playlist: { + favorites: { + added: [], + removed: [] + }, + reposts: { + added: [], + removed: [] + } + } + } +} as SavedPageState + +const getCategoryLocalStateKey = ( + category: Omit +) => { + switch (category) { + case LibraryCategory.Favorite: + return 'favorites' + case LibraryCategory.Purchase: + return 'purchased' + case LibraryCategory.Repost: + return 'reposts' + default: + return 'favorites' + } } -const actionsMap = { - [FETCH_SAVES](state, action) { +const actionsMap: ActionsMap = { + [FETCH_SAVES](state) { return { ...state } }, - [FETCH_SAVES_REQUESTED](state, action) { + [FETCH_SAVES_REQUESTED](state) { return { ...state, initialFetch: true, @@ -46,70 +106,122 @@ const actionsMap = { [FETCH_SAVES_SUCCEEDED](state, action) { return { ...state, - saves: action.saves, + trackSaves: action.saves, initialFetch: false } }, - [FETCH_MORE_SAVES](state, action) { + [FETCH_MORE_SAVES](state) { return { ...state, fetchingMore: true } }, - [FETCH_SAVES_FAILED](state, action) { + [FETCH_SAVES_FAILED](state) { return { ...state, fetchingMore: false, - saves: [] + trackSaves: [] } }, [FETCH_MORE_SAVES_SUCCEEDED](state, action) { - const savesCopy = state.saves.slice() + const savesCopy = state.trackSaves.slice() savesCopy.splice(action.offset, action.saves.length, ...action.saves) return { ...state, fetchingMore: false, - saves: savesCopy + trackSaves: savesCopy } }, - [FETCH_MORE_SAVES_FAILED](state, action) { + [FETCH_MORE_SAVES_FAILED](state) { return { ...state } }, [END_FETCHING](state, action) { - const savesCopy = state.saves.slice(0, action.endIndex) + const savesCopy = state.trackSaves.slice(0, action.endIndex) return { ...state, - saves: savesCopy, + trackSaves: savesCopy, hasReachedEnd: true } }, - [ADD_LOCAL_SAVE](state, action) { - return { - ...state, - localSaves: { - ...state.localSaves, - [action.trackId]: action.uid - } + [ADD_LOCAL_TRACK](state, action) { + const categoryKey = getCategoryLocalStateKey(action.category) + const newState = { ...state } + newState.local.track[categoryKey].added = { + ...newState.local.track[categoryKey].added, + [action.trackId]: action.uid } + return newState }, - [REMOVE_LOCAL_SAVE](state, action) { + [REMOVE_LOCAL_TRACK](state, action) { + const categoryKey = getCategoryLocalStateKey(action.category) const newState = { ...state } - delete newState.localSaves[action.trackId] - newState.saves = newState.saves.filter( + delete newState.local.track[categoryKey].added[action.trackId] + + newState.trackSaves = newState.trackSaves.filter( ({ save_item_id: id }) => id !== action.trackId ) return newState }, - [signOut.type](state) { + [ADD_LOCAL_COLLECTION](state, action) { + const kindKey = action.isAlbum ? 'album' : 'playlist' + const categoryKey = getCategoryLocalStateKey(action.category) + const newState = { ...state } + newState.local[kindKey][categoryKey].added = [ + action.collectionId, + ...newState.local[kindKey][categoryKey].added + ] + newState.local[kindKey][categoryKey].removed = newState.local[kindKey][ + categoryKey + ].removed.filter((id: ID) => id !== action.collectionId) + + return newState + }, + [REMOVE_LOCAL_COLLECTION](state, action) { + const kindKey = action.isAlbum ? 'album' : 'playlist' + const categoryKey = getCategoryLocalStateKey(action.category) + const newState = { ...state } + newState.local[kindKey][categoryKey].removed = [ + action.collectionId, + ...newState.local[kindKey][categoryKey].removed + ] + newState.local[kindKey][categoryKey].added = newState.local[kindKey][ + categoryKey + ].added.filter((id: ID) => id !== action.collectionId) + + return newState + }, + [SET_SELECTED_CATEGORY](state, action) { + return { + ...state, + ...calculateNewLibraryCategories({ + currentTab: action.currentTab, + chosenCategory: action.category, + prevTracksCategory: state.tracksCategory + }) + } + }, + [INIT_TRACKS_CATEGORY_FROM_LOCAL_STORAGE](state, action) { + return { + ...state, + tracksCategory: action.category + } + }, + [INIT_COLLECTIONS_CATEGORY_FROM_LOCAL_STORAGE](state, action) { + return { + ...state, + collectionsCategory: action.category + } + }, + [signOut.type]() { return initialState } } const tracksLineupReducer = asLineup(tracksPrefix, tracksReducer) -const reducer = (state = initialState, action) => { - const tracks = tracksLineupReducer(state.tracks, action) +const reducer = (state = initialState, action: any) => { + const tracks = tracksLineupReducer(state.tracks as any, action) if (tracks !== state.tracks) return { ...state, tracks } const matchingReduceFunction = actionsMap[action.type] diff --git a/packages/common/src/store/pages/saved-page/selectors.ts b/packages/common/src/store/pages/saved-page/selectors.ts index 98358fbb2d..fed17b8c01 100644 --- a/packages/common/src/store/pages/saved-page/selectors.ts +++ b/packages/common/src/store/pages/saved-page/selectors.ts @@ -1,13 +1,166 @@ +import { uniq } from 'lodash' + import { CommonState } from 'store/commonStore' import { ID } from '../../../models/Identifiers' +import { LibraryCategory, SavedPageTabs } from './types' + export const getSaved = (state: CommonState) => state.pages.savedPage -export const getSaves = (state: CommonState) => state.pages.savedPage.saves -export const getLocalSaves = (state: CommonState) => - state.pages.savedPage.localSaves -export const getLocalSave = (state: CommonState, props: { id: ID }) => - state.pages.savedPage.localSaves[props.id] +export const getTrackSaves = (state: CommonState) => + state.pages.savedPage.trackSaves + +export const getCollectionsCategory = (state: CommonState) => { + return state.pages.savedPage.collectionsCategory +} + +export const getTracksCategory = (state: CommonState) => { + return state.pages.savedPage.tracksCategory +} + +export const getCategory = ( + state: CommonState, + props: { currentTab: SavedPageTabs } +) => { + if (props.currentTab === SavedPageTabs.TRACKS) { + return getTracksCategory(state) + } else { + return getCollectionsCategory(state) + } +} + +export const getLocalTrackFavorites = (state: CommonState) => + state.pages.savedPage.local.track.favorites.added +export const getLocalTrackFavorite = (state: CommonState, props: { id: ID }) => + state.pages.savedPage.local.track.favorites.added[props.id] +export const getLocalTrackReposts = (state: CommonState) => + state.pages.savedPage.local.track.reposts.added +export const getLocalTrackRepost = (state: CommonState, props: { id: ID }) => + state.pages.savedPage.local.track.reposts.added[props.id] +export const getLocalTrackPurchases = (state: CommonState) => + state.pages.savedPage.local.track.purchased.added +export const getLocalTrackPurchase = (state: CommonState, props: { id: ID }) => + state.pages.savedPage.local.track.purchased.added[props.id] + +export const getLocalAlbumFavorites = (state: CommonState) => + state.pages.savedPage.local.album.favorites.added +export const getLocalAlbumReposts = (state: CommonState) => + state.pages.savedPage.local.album.reposts.added +export const getLocalAlbumPurchases = (state: CommonState) => + state.pages.savedPage.local.album.purchased.added +export const getLocalRemovedAlbumFavorites = (state: CommonState) => + state.pages.savedPage.local.album.favorites.removed +export const getLocalRemovedAlbumReposts = (state: CommonState) => + state.pages.savedPage.local.album.reposts.removed + +export const getLocalPlaylistFavorites = (state: CommonState) => + state.pages.savedPage.local.playlist.favorites.added +export const getLocalPlaylistReposts = (state: CommonState) => + state.pages.savedPage.local.playlist.reposts.added +export const getLocalRemovedPlaylistFavorites = (state: CommonState) => + state.pages.savedPage.local.playlist.favorites.removed +export const getLocalRemovedPlaylistReposts = (state: CommonState) => + state.pages.savedPage.local.playlist.favorites.removed + +/** Get the tracks in currently selected category that have been added to the library in current session */ +export const getSelectedCategoryLocalTrackAdds = (state: CommonState) => { + const selectedCategory = getCategory(state, { + currentTab: SavedPageTabs.TRACKS + }) + const localFavorites = getLocalTrackFavorites(state) + const localPurchases = getLocalTrackPurchases(state) + const localReposts = getLocalTrackReposts(state) + let localLibraryAdditions + if (selectedCategory === LibraryCategory.Favorite) { + localLibraryAdditions = localFavorites + } else if (selectedCategory === LibraryCategory.Purchase) { + localLibraryAdditions = localPurchases + } else if (selectedCategory === LibraryCategory.Repost) { + localLibraryAdditions = localReposts + } else { + // Category = ALL + localLibraryAdditions = { + ...localReposts, + ...localFavorites, + ...localPurchases + } + } + + return localLibraryAdditions +} + +const getSelectedCategoryLocalCollectionUpdates = ( + state: CommonState, + props: { collectionType: 'album' | 'playlist'; updateType: 'add' | 'remove' } +) => { + const { collectionType, updateType } = props + const currentTab = + collectionType === 'album' ? SavedPageTabs.ALBUMS : SavedPageTabs.PLAYLISTS + const selectedCategory = getCategory(state, { currentTab }) + let localFavorites: ID[], localPurchases: ID[], localReposts: ID[] + if (updateType === 'add') { + localFavorites = + collectionType === 'album' + ? getLocalAlbumFavorites(state) + : getLocalPlaylistFavorites(state) + localPurchases = + collectionType === 'album' ? getLocalAlbumPurchases(state) : [] // Can't buy playlists + localReposts = + collectionType === 'album' + ? getLocalAlbumReposts(state) + : getLocalPlaylistReposts(state) + } else { + localFavorites = + collectionType === 'album' + ? getLocalRemovedAlbumFavorites(state) + : getLocalRemovedPlaylistFavorites(state) + localPurchases = [] // Can't remove purchases + localReposts = + collectionType === 'album' + ? getLocalRemovedAlbumReposts(state) + : getLocalRemovedPlaylistReposts(state) + } + + switch (selectedCategory) { + case LibraryCategory.Favorite: + return localFavorites + case LibraryCategory.Purchase: + return localPurchases + case LibraryCategory.Repost: + return localReposts + default: + // Category = ALL + return uniq([...localReposts, ...localFavorites, ...localPurchases]) + } +} + +export const getSelectedCategoryLocalAlbumAdds = (state: CommonState) => { + return getSelectedCategoryLocalCollectionUpdates(state, { + collectionType: 'album', + updateType: 'add' + }) +} +export const getSelectedCategoryLocalAlbumRemovals = (state: CommonState) => { + return getSelectedCategoryLocalCollectionUpdates(state, { + collectionType: 'album', + updateType: 'remove' + }) +} +export const getSelectedCategoryLocalPlaylistRemovals = ( + state: CommonState +) => { + return getSelectedCategoryLocalCollectionUpdates(state, { + collectionType: 'playlist', + updateType: 'remove' + }) +} +export const getSelectedCategoryLocalPlaylistAdds = (state: CommonState) => { + return getSelectedCategoryLocalCollectionUpdates(state, { + collectionType: 'playlist', + updateType: 'add' + }) +} + export const getInitialFetchStatus = (state: CommonState) => state.pages.savedPage.initialFetch export const getIsFetchingMore = (state: CommonState) => diff --git a/packages/common/src/store/pages/saved-page/types.ts b/packages/common/src/store/pages/saved-page/types.ts index 8d4ceae3dc..de22d7d3e0 100644 --- a/packages/common/src/store/pages/saved-page/types.ts +++ b/packages/common/src/store/pages/saved-page/types.ts @@ -1,5 +1,8 @@ +import { full } from '@audius/sdk' import { Moment } from 'moment' +import { ValueOf } from 'utils/typeUtils' + import { UID, ID, @@ -9,13 +12,63 @@ import { LineupTrack } from '../../../models' +export const LIBRARY_TRACKS_CATEGORY_LS_KEY = 'libraryTracksCategory' + +export const LIBRARY_COLLECTIONS_CATEGORY_LS_KEY = 'libraryCollectionsCategory' + +export const LibraryCategory = full.GetUserLibraryTracksTypeEnum +export type LibraryCategoryType = ValueOf + +export function isLibraryCategory(value: string): value is LibraryCategoryType { + return Object.values(LibraryCategory).includes(value as LibraryCategoryType) +} export interface SavedPageState { - localSaves: { [id: number]: UID } + local: { + track: { + favorites: { + added: { [id: number]: UID } + removed: { [id: number]: UID } + } + reposts: { + added: { [id: number]: UID } + removed: { [id: number]: UID } + } + purchased: { + added: { [id: number]: UID } + } + } + album: { + favorites: { + added: ID[] + removed: ID[] + } + reposts: { + added: ID[] + removed: ID[] + } + purchased: { + added: ID[] + } + } + playlist: { + favorites: { + added: ID[] + removed: ID[] + } + reposts: { + added: ID[] + removed: ID[] + } + } + } tracks: LineupState - saves: Favorite[] + trackSaves: Favorite[] hasReachedEnd: boolean initialFetch: boolean fetchingMore: boolean + + tracksCategory: LibraryCategoryType + collectionsCategory: LibraryCategoryType } export enum SavedPageTabs { diff --git a/packages/common/src/store/pages/saved-page/utils.ts b/packages/common/src/store/pages/saved-page/utils.ts new file mode 100644 index 0000000000..6be4938463 --- /dev/null +++ b/packages/common/src/store/pages/saved-page/utils.ts @@ -0,0 +1,39 @@ +import { LibraryCategory, LibraryCategoryType, SavedPageTabs } from './types' + +export const calculateNewLibraryCategories = ({ + currentTab, + chosenCategory, + prevTracksCategory +}: { + currentTab: SavedPageTabs + chosenCategory: LibraryCategoryType + prevTracksCategory: unknown +}) => { + if ( + currentTab === SavedPageTabs.TRACKS && + chosenCategory === LibraryCategory.Purchase + ) { + // If the category is changed to "Purchased" on the tracks tab, change the collections tabs category to "All" because collections tabs don't have "Purchased". + return { + tracksCategory: chosenCategory, + collectionsCategory: LibraryCategory.All + } + } + if ( + (currentTab === SavedPageTabs.ALBUMS || + currentTab === SavedPageTabs.PLAYLISTS) && + prevTracksCategory === LibraryCategory.Purchase + ) { + // If tracks tab is on "Purchased", we want it to stay on "Purchased" until the user goes back to it. + return { + tracksCategory: prevTracksCategory, + collectionsCategory: chosenCategory + } + } + + // Default behavior: change category for all tabs. + return { + collectionsCategory: chosenCategory, + tracksCategory: chosenCategory + } +} diff --git a/packages/common/src/utils/collectionUtils.ts b/packages/common/src/utils/collectionUtils.ts index de47479459..207acb897e 100644 --- a/packages/common/src/utils/collectionUtils.ts +++ b/packages/common/src/utils/collectionUtils.ts @@ -1,22 +1,26 @@ import { AccountCollection } from 'store/account' +import { EnhancedCollection } from 'store/cache/collections/selectors' type FilterCollectionsOptions = { filterText?: string } -export function filterCollections( - collections: AccountCollection[], - { filterText = '' }: FilterCollectionsOptions -): AccountCollection[] { - return collections.filter((item: AccountCollection) => { - if (filterText) { - const matchesPlaylistName = - item.name.toLowerCase().indexOf(filterText.toLowerCase()) > -1 - const matchesOwnerName = - item.user.handle.toLowerCase().indexOf(filterText.toLowerCase()) > -1 +export const isAccountCollection = ( + collection: AccountCollection | EnhancedCollection +): collection is AccountCollection => { + return (collection as AccountCollection).name !== undefined +} + +export function filterCollections< + T extends AccountCollection | EnhancedCollection +>(collections: T[], { filterText = '' }: FilterCollectionsOptions): T[] { + return collections.filter((item: AccountCollection | EnhancedCollection) => { + const name = isAccountCollection(item) ? item.name : item.playlist_name + const matchesPlaylistName = + name.toLowerCase().indexOf(filterText.toLowerCase()) > -1 + const matchesOwnerName = + item.user.handle.toLowerCase().indexOf(filterText.toLowerCase()) > -1 - return matchesPlaylistName || matchesOwnerName - } - return true + return matchesPlaylistName || matchesOwnerName }) } diff --git a/packages/common/src/utils/typeUtils.ts b/packages/common/src/utils/typeUtils.ts index 6898a9b20b..31a8ced79b 100644 --- a/packages/common/src/utils/typeUtils.ts +++ b/packages/common/src/utils/typeUtils.ts @@ -29,3 +29,5 @@ export type Maybe = T | undefined * */ export type Brand = T & { _brand: U } + +export type ValueOf = T[keyof T] diff --git a/packages/mobile/android/app/build.gradle b/packages/mobile/android/app/build.gradle index 501572d998..7cfa644289 100755 --- a/packages/mobile/android/app/build.gradle +++ b/packages/mobile/android/app/build.gradle @@ -132,7 +132,7 @@ android { // versionCode is automatically incremented in CI versionCode 1 // Make sure this is above the currently released Android version in the play store if your changes touch native code: - versionName "1.1.397" + versionName "1.1.398" resValue "string", "build_config_package", "co.audius.app" resValue 'string', "CODE_PUSH_APK_BUILD_TIME", String.format("\"%d\"", System.currentTimeMillis()) resConfigs "en" diff --git a/packages/mobile/android/app/src/main/res/raw/library_dark.riv b/packages/mobile/android/app/src/main/res/raw/library_dark.riv new file mode 100755 index 0000000000..b252efd15c Binary files /dev/null and b/packages/mobile/android/app/src/main/res/raw/library_dark.riv differ diff --git a/packages/mobile/android/app/src/main/res/raw/library_default.riv b/packages/mobile/android/app/src/main/res/raw/library_default.riv new file mode 100755 index 0000000000..4ebdb7524a Binary files /dev/null and b/packages/mobile/android/app/src/main/res/raw/library_default.riv differ diff --git a/packages/mobile/android/app/src/main/res/raw/library_matrix.riv b/packages/mobile/android/app/src/main/res/raw/library_matrix.riv new file mode 100755 index 0000000000..119ba630b2 Binary files /dev/null and b/packages/mobile/android/app/src/main/res/raw/library_matrix.riv differ diff --git a/packages/mobile/ios/Assets/library_dark.riv b/packages/mobile/ios/Assets/library_dark.riv new file mode 100755 index 0000000000..b252efd15c Binary files /dev/null and b/packages/mobile/ios/Assets/library_dark.riv differ diff --git a/packages/mobile/ios/Assets/library_default.riv b/packages/mobile/ios/Assets/library_default.riv new file mode 100755 index 0000000000..4ebdb7524a Binary files /dev/null and b/packages/mobile/ios/Assets/library_default.riv differ diff --git a/packages/mobile/ios/Assets/library_matrix.riv b/packages/mobile/ios/Assets/library_matrix.riv new file mode 100755 index 0000000000..119ba630b2 Binary files /dev/null and b/packages/mobile/ios/Assets/library_matrix.riv differ diff --git a/packages/mobile/ios/AudiusReactNative.xcodeproj/project.pbxproj b/packages/mobile/ios/AudiusReactNative.xcodeproj/project.pbxproj index 3a68ee0937..e1f28104e8 100644 --- a/packages/mobile/ios/AudiusReactNative.xcodeproj/project.pbxproj +++ b/packages/mobile/ios/AudiusReactNative.xcodeproj/project.pbxproj @@ -26,6 +26,10 @@ 23C6F6592977C46F00F66384 /* trending_matrix.riv in Resources */ = {isa = PBXBuildFile; fileRef = 23C6F6542977C46F00F66384 /* trending_matrix.riv */; }; 23C6F65A2977C46F00F66384 /* notifications_matrix.riv in Resources */ = {isa = PBXBuildFile; fileRef = 23C6F6552977C46F00F66384 /* notifications_matrix.riv */; }; 23C6F65B2977C46F00F66384 /* feed_matrix.riv in Resources */ = {isa = PBXBuildFile; fileRef = 23C6F6562977C46F00F66384 /* feed_matrix.riv */; }; + AA7D28012A68C20F008DF4D3 /* library_dark.riv in Resources */ = {isa = PBXBuildFile; fileRef = AA7D27FE2A68C20F008DF4D3 /* library_dark.riv */; }; + AA7D28022A68C20F008DF4D3 /* library_matrix.riv in Resources */ = {isa = PBXBuildFile; fileRef = AA7D27FF2A68C20F008DF4D3 /* library_matrix.riv */; }; + AA7D28032A68C20F008DF4D3 /* library_default.riv in Resources */ = {isa = PBXBuildFile; fileRef = AA7D28002A68C20F008DF4D3 /* library_default.riv */; }; + B84EBFE7A8480433FB2462D9 /* libPods-AudiusReactNative.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 673EB20B90B4A9514D739B99 /* libPods-AudiusReactNative.a */; }; 2FD70DEA0D63544E733CB96F /* libPods-AudiusReactNative.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DDFBD1F0A838E1F603A49CAD /* libPods-AudiusReactNative.a */; }; 32FE1B49FE7E47B69CF28E06 /* AvenirNextLTPro-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = EA6D8E31A68B4451B423AFF0 /* AvenirNextLTPro-Bold.otf */; }; 5983D8FED74747CCA45F8FC4 /* AvenirNextLTPro-Medium.otf in Resources */ = {isa = PBXBuildFile; fileRef = 33E11CC84EEA4109B8A1AE2C /* AvenirNextLTPro-Medium.otf */; }; @@ -89,6 +93,9 @@ 8C29422A63AC4C07972EBA59 /* Pods-AudiusReactNative.staging.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AudiusReactNative.staging.release.xcconfig"; path = "Target Support Files/Pods-AudiusReactNative/Pods-AudiusReactNative.staging.release.xcconfig"; sourceTree = ""; }; 8FFE615B56774422849C5A08 /* AvenirNextLTPro-Light.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "AvenirNextLTPro-Light.otf"; path = "../src/assets/fonts/AvenirNextLTPro-Light.otf"; sourceTree = ""; }; A20D1DBF23ABDA2B003D6530 /* Audius Music.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = "Audius Music.entitlements"; path = "AudiusReactNative/Audius Music.entitlements"; sourceTree = ""; }; + AA7D27FE2A68C20F008DF4D3 /* library_dark.riv */ = {isa = PBXFileReference; lastKnownFileType = file; name = library_dark.riv; path = Assets/library_dark.riv; sourceTree = ""; }; + AA7D27FF2A68C20F008DF4D3 /* library_matrix.riv */ = {isa = PBXFileReference; lastKnownFileType = file; name = library_matrix.riv; path = Assets/library_matrix.riv; sourceTree = ""; }; + AA7D28002A68C20F008DF4D3 /* library_default.riv */ = {isa = PBXFileReference; lastKnownFileType = file; name = library_default.riv; path = Assets/library_default.riv; sourceTree = ""; }; BAEB40775EC64F48AC1983F7 /* AvenirNextLTPro-Thin.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "AvenirNextLTPro-Thin.otf"; path = "../src/assets/fonts/AvenirNextLTPro-Thin.otf"; sourceTree = ""; }; CCC75FDE7026DD7C68CFC0BA /* Pods-AudiusReactNative.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AudiusReactNative.debug.xcconfig"; path = "Target Support Files/Pods-AudiusReactNative/Pods-AudiusReactNative.debug.xcconfig"; sourceTree = ""; }; D40C2F0A0A4641D18D426558 /* AvenirNextLTPro-Heavy.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "AvenirNextLTPro-Heavy.otf"; path = "../src/assets/fonts/AvenirNextLTPro-Heavy.otf"; sourceTree = ""; }; @@ -151,6 +158,9 @@ 23C6F63D2977C23800F66384 /* Assets */ = { isa = PBXGroup; children = ( + AA7D27FE2A68C20F008DF4D3 /* library_dark.riv */, + AA7D28002A68C20F008DF4D3 /* library_default.riv */, + AA7D27FF2A68C20F008DF4D3 /* library_matrix.riv */, 71F51B7A29885F9700650D0A /* downloading_dark.riv */, 71F51B7B29885F9700650D0A /* downloading_default.riv */, 71F51B7C29885F9700650D0A /* downloading_matrix.riv */, @@ -338,14 +348,17 @@ 32FE1B49FE7E47B69CF28E06 /* AvenirNextLTPro-Bold.otf in Resources */, 71F51B7E29885F9700650D0A /* downloading_default.riv in Resources */, 23C6F64D2977C27A00F66384 /* feed_default.riv in Resources */, + AA7D28032A68C20F008DF4D3 /* library_default.riv in Resources */, C48560F6F96345AC8C69506F /* AvenirNextLTPro-Heavy.otf in Resources */, 5983D8FED74747CCA45F8FC4 /* AvenirNextLTPro-Medium.otf in Resources */, + AA7D28012A68C20F008DF4D3 /* library_dark.riv in Resources */, 9204DC5D38C1495FA330CF12 /* AvenirNextLTPro-Regular.otf in Resources */, 23B4E1CE297889CB00016FEE /* notifications_dark.riv in Resources */, 71F51B7F29885F9700650D0A /* downloading_matrix.riv in Resources */, CFE33B15004249189EB75CDC /* AvenirNextLTPro-UltLt.otf in Resources */, F50849B3CA2D48F3852465FA /* AvenirNextLTPro-Light.otf in Resources */, 71F51B7D29885F9700650D0A /* downloading_dark.riv in Resources */, + AA7D28022A68C20F008DF4D3 /* library_matrix.riv in Resources */, 23C6F6502977C27A00F66384 /* explore_dark.riv in Resources */, 9A21B5C22EF94A738EBABFED /* AvenirNextLTPro-Thin.otf in Resources */, 23C6F64F2977C27A00F66384 /* notifications_default.riv in Resources */, diff --git a/packages/mobile/ios/AudiusReactNative/Info.plist b/packages/mobile/ios/AudiusReactNative/Info.plist index 83f23573b4..e9f25d50f4 100644 --- a/packages/mobile/ios/AudiusReactNative/Info.plist +++ b/packages/mobile/ios/AudiusReactNative/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.1.72 + 1.1.73 CFBundleSignature ???? CFBundleURLTypes diff --git a/packages/mobile/src/app/Drawers.tsx b/packages/mobile/src/app/Drawers.tsx index ab0c698b0a..add9e2797b 100644 --- a/packages/mobile/src/app/Drawers.tsx +++ b/packages/mobile/src/app/Drawers.tsx @@ -45,7 +45,8 @@ import { RemoveDownloadedCollectionDrawer, RemoveDownloadedFavoritesDrawer, UnfavoriteDownloadedCollectionDrawer, - DeleteTrackConfirmationDrawer + DeleteTrackConfirmationDrawer, + OfflineListeningDrawer } from '../components/drawers' import { ShareToStoryProgressDrawer } from '../components/share-drawer/useShareToStory' import { VipDiscordDrawer } from '../components/vip-discord-drawer' @@ -116,6 +117,7 @@ const commonDrawersMap: { [Modal in Modals]?: ComponentType } = { const nativeDrawersMap: { [DrawerName in Drawer]?: ComponentType } = { EnablePushNotifications: EnablePushNotificationsDrawer, + OfflineListening: OfflineListeningDrawer, DownloadTrackProgress: DownloadTrackProgressDrawer, ForgotPassword: ForgotPasswordDrawer, DeleteTrackConfirmation: DeleteTrackConfirmationDrawer, diff --git a/packages/mobile/src/assets/images/iconLibrary.svg b/packages/mobile/src/assets/images/iconLibrary.svg new file mode 100644 index 0000000000..e178b6edbb --- /dev/null +++ b/packages/mobile/src/assets/images/iconLibrary.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/mobile/src/components/bottom-tab-bar/bottom-tab-bar-buttons/FavoritesButton.tsx b/packages/mobile/src/components/bottom-tab-bar/bottom-tab-bar-buttons/FavoritesButton.tsx deleted file mode 100644 index ba4f80efda..0000000000 --- a/packages/mobile/src/components/bottom-tab-bar/bottom-tab-bar-buttons/FavoritesButton.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import type { BaseBottomTabBarButtonProps } from './BottomTabBarButton' -import { BottomTabBarButton } from './BottomTabBarButton' - -type FavoritesButtonProps = BaseBottomTabBarButtonProps - -export const FavoritesButton = (props: FavoritesButtonProps) => { - return -} diff --git a/packages/mobile/src/components/bottom-tab-bar/bottom-tab-bar-buttons/LibraryButton.tsx b/packages/mobile/src/components/bottom-tab-bar/bottom-tab-bar-buttons/LibraryButton.tsx new file mode 100644 index 0000000000..b97bfcd69e --- /dev/null +++ b/packages/mobile/src/components/bottom-tab-bar/bottom-tab-bar-buttons/LibraryButton.tsx @@ -0,0 +1,8 @@ +import type { BaseBottomTabBarButtonProps } from './BottomTabBarButton' +import { BottomTabBarButton } from './BottomTabBarButton' + +type LibraryButtonProps = BaseBottomTabBarButtonProps + +export const LibraryButton = (props: LibraryButtonProps) => { + return +} diff --git a/packages/mobile/src/components/bottom-tab-bar/bottom-tab-bar-buttons/index.ts b/packages/mobile/src/components/bottom-tab-bar/bottom-tab-bar-buttons/index.ts index 72c8cdea1c..97588d6162 100644 --- a/packages/mobile/src/components/bottom-tab-bar/bottom-tab-bar-buttons/index.ts +++ b/packages/mobile/src/components/bottom-tab-bar/bottom-tab-bar-buttons/index.ts @@ -1,6 +1,6 @@ import { ExploreButton } from './ExploreButton' -import { FavoritesButton } from './FavoritesButton' import { FeedButton } from './FeedButton' +import { LibraryButton } from './LibraryButton' import { NotificationsButton } from './NotificationsButton' import { TrendingButton } from './TrendingButton' @@ -8,12 +8,12 @@ export const bottomTabBarButtons = { feed: FeedButton, trending: TrendingButton, explore: ExploreButton, - favorites: FavoritesButton, + library: LibraryButton, notifications: NotificationsButton } export * from './ExploreButton' -export * from './FavoritesButton' +export * from './LibraryButton' export * from './FeedButton' export * from './TrendingButton' export * from './NotificationsButton' diff --git a/packages/mobile/src/components/collection-list/AddCollectionCard.tsx b/packages/mobile/src/components/collection-list/AddCollectionCard.tsx index 0a2dc53ac1..6aba004914 100644 --- a/packages/mobile/src/components/collection-list/AddCollectionCard.tsx +++ b/packages/mobile/src/components/collection-list/AddCollectionCard.tsx @@ -41,7 +41,7 @@ const useStyles = makeStyles(({ spacing }) => ({ export const AddCollectionCard = ({ onCreate, - source = CreatePlaylistSource.FAVORITES_PAGE, + source = CreatePlaylistSource.LIBRARY_PAGE, sourceTrackId = null }: AddCollectionCardProps) => { const styles = useStyles() diff --git a/packages/mobile/src/components/collection-list/CollectionList.tsx b/packages/mobile/src/components/collection-list/CollectionList.tsx index 131a756a11..e2f1fe9f9d 100644 --- a/packages/mobile/src/components/collection-list/CollectionList.tsx +++ b/packages/mobile/src/components/collection-list/CollectionList.tsx @@ -46,7 +46,7 @@ const FullCollectionList = (props: FullCollectionListProps) => { collection, collectionIdsToNumTracks, showCreatePlaylistTile = false, - createPlaylistSource = CreatePlaylistSource.FAVORITES_PAGE, + createPlaylistSource = CreatePlaylistSource.LIBRARY_PAGE, createPlaylistTrackId, createPlaylistCallback, renderItem, @@ -99,7 +99,7 @@ const CollectionIDList = (props: CollectionIdListProps) => { const { collectionIds, showCreatePlaylistTile = false, - createPlaylistSource = CreatePlaylistSource.FAVORITES_PAGE, + createPlaylistSource = CreatePlaylistSource.LIBRARY_PAGE, createPlaylistTrackId, createPlaylistCallback, ...other diff --git a/packages/mobile/src/components/core/HarmonyModalHeader.tsx b/packages/mobile/src/components/core/HarmonyModalHeader.tsx new file mode 100644 index 0000000000..d62fdab5f7 --- /dev/null +++ b/packages/mobile/src/components/core/HarmonyModalHeader.tsx @@ -0,0 +1,55 @@ +import type { ComponentType } from 'react' + +import { View } from 'react-native' +import type { SvgProps } from 'react-native-svg' + +import { Text } from 'app/components/core' +import { makeStyles } from 'app/styles' +import { useThemeColors } from 'app/utils/theme' + +const useStyles = makeStyles(({ spacing, palette }) => ({ + container: { + width: '100%', + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + paddingBottom: spacing(4), + borderBottomColor: palette.neutralLight8, + borderBottomWidth: 1 + }, + titleIcon: { + marginRight: spacing(2) + } +})) + +type HarmonyModalHeaderProps = { + icon: ComponentType + title: string +} + +export const HarmonyModalHeader = ({ + icon: Icon, + title +}: HarmonyModalHeaderProps) => { + const styles = useStyles() + const { neutralLight2 } = useThemeColors() + + return ( + + + + {title} + + + ) +} diff --git a/packages/mobile/src/components/drawers/OfflineListeningDrawer.tsx b/packages/mobile/src/components/drawers/OfflineListeningDrawer.tsx new file mode 100644 index 0000000000..a8446fd8b8 --- /dev/null +++ b/packages/mobile/src/components/drawers/OfflineListeningDrawer.tsx @@ -0,0 +1,185 @@ +import type { ComponentType } from 'react' +import { useCallback, useState } from 'react' + +import { View } from 'react-native' +import type { SvgProps } from 'react-native-svg' +import { useDispatch } from 'react-redux' + +import IconCart from 'app/assets/images/iconCart.svg' +import IconDownload from 'app/assets/images/iconDownloadQueued.svg' +import IconFavorite from 'app/assets/images/iconFavorite.svg' +import IconRepost from 'app/assets/images/iconRepost.svg' +import { Button, Switch, Text } from 'app/components/core' +import { useDrawer } from 'app/hooks/useDrawer' +import { useIsUSDCEnabled } from 'app/hooks/useIsUSDCEnabled' +import { setVisibility } from 'app/store/drawers/slice' +import { requestDownloadAllFavorites } from 'app/store/offline-downloads/slice' +import { makeStyles } from 'app/styles' +import { useThemeColors } from 'app/utils/theme' + +import { HarmonyModalHeader } from '../core/HarmonyModalHeader' +import { NativeDrawer } from '../drawer' + +const useDrawerStyles = makeStyles(({ spacing, palette, typography }) => ({ + container: { + paddingVertical: spacing(6), + flexDirection: 'column', + paddingHorizontal: spacing(4), + rowGap: spacing(6), + alignItems: 'center' + }, + descriptionText: { + textAlign: 'center', + lineHeight: typography.fontSize.large * 1.3 + }, + titleIcon: { + position: 'relative', + top: 7, + color: palette.neutral, + marginRight: spacing(3) + } +})) + +const useToggleStyles = makeStyles(({ spacing, palette }) => ({ + toggleContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + width: '100%', + alignItems: 'center' + }, + titleContainer: { + columnGap: spacing(2), + flexDirection: 'row' + } +})) + +const messages = { + offlineListeningTitle: 'Offline Listening', + offlineListeningDescription: + 'Use the toggles to select what you’d like synced for offline streaming.', + comingSoonToggleSuffix: '(coming soon...)', + favorites: 'Favorites', + reposts: 'Reposts', + purchased: 'Purchased', + saveChanges: 'Save Changes' +} + +type OfflineListeningOptionToggleProps = { + title: string + icon: ComponentType + value: boolean + onValueChange?: (value: boolean) => void | Promise + disabled?: boolean +} + +const OfflineListeningOptionToggle = ({ + title, + icon: Icon, + value, + onValueChange, + disabled +}: OfflineListeningOptionToggleProps) => { + const styles = useToggleStyles() + const { neutral, neutralLight4 } = useThemeColors() + + return ( + + + + + {title} + + + + + ) +} + +export const OfflineListeningDrawer = () => { + const styles = useDrawerStyles() + const dispatch = useDispatch() + const { data, onClose } = useDrawer('OfflineListening') + const { isFavoritesMarkedForDownload, onSaveChanges } = data + + const [isFavoritesOn, setIsFavoritesOn] = useState( + isFavoritesMarkedForDownload + ) + + const handleSaveChanges = useCallback(() => { + if (isFavoritesMarkedForDownload && !isFavoritesOn) { + dispatch( + setVisibility({ + drawer: 'RemoveDownloadedFavorites', + visible: true + }) + ) + onSaveChanges(isFavoritesOn) + } else if (!isFavoritesMarkedForDownload && isFavoritesOn) { + dispatch(requestDownloadAllFavorites()) + onSaveChanges(isFavoritesOn) + } + + onClose() + }, [ + dispatch, + isFavoritesMarkedForDownload, + isFavoritesOn, + onClose, + onSaveChanges + ]) + + const handleToggleFavorites = useCallback((value: boolean) => { + setIsFavoritesOn(value) + }, []) + + const isUSDCPurchasesEnabled = useIsUSDCEnabled() + + return ( + + + + + {messages.offlineListeningDescription} + + + + {isUSDCPurchasesEnabled ? ( + + ) : null} +