diff --git a/packages/common/src/store/ui/modals/parentSlice.ts b/packages/common/src/store/ui/modals/parentSlice.ts index c9edbd4b658..952be569027 100644 --- a/packages/common/src/store/ui/modals/parentSlice.ts +++ b/packages/common/src/store/ui/modals/parentSlice.ts @@ -26,6 +26,7 @@ export const initialState: BasicModalsState = { TrendingCategory: { isOpen: false }, TrendingTimeRange: { isOpen: false }, TrendingFilter: { isOpen: false }, + FeedFilter: { isOpen: false }, TrendingRewardsExplainer: { isOpen: false }, SocialProof: { isOpen: false }, EditTrack: { isOpen: false }, diff --git a/packages/common/src/store/ui/modals/types.ts b/packages/common/src/store/ui/modals/types.ts index 7a5076fb250..307360dfe97 100644 --- a/packages/common/src/store/ui/modals/types.ts +++ b/packages/common/src/store/ui/modals/types.ts @@ -61,6 +61,7 @@ export type Modals = | 'TrendingCategory' | 'TrendingTimeRange' | 'TrendingFilter' + | 'FeedFilter' | 'TrendingRewardsExplainer' | 'SocialProof' | 'EditTrack' diff --git a/packages/mobile/src/app/Drawers.tsx b/packages/mobile/src/app/Drawers.tsx index 4e407ca93ff..6f7d0405362 100644 --- a/packages/mobile/src/app/Drawers.tsx +++ b/packages/mobile/src/app/Drawers.tsx @@ -52,6 +52,7 @@ import { ReplaceTrackProgressDrawer } from 'app/screens/edit-track-screen/compon import { EarlyReleaseConfirmationDrawer } from 'app/screens/edit-track-screen/components/EarlyReleaseConfirmationDrawer' import { PublishConfirmationDrawer } from 'app/screens/edit-track-screen/components/PublishConfirmationDrawer' import { ConnectNewWalletDrawer } from 'app/screens/external-wallets/components/ConnectNewWalletDrawer' +import { FeedFilterDrawer } from 'app/screens/feed-screen' import { WelcomeDrawer } from 'app/screens/sign-on-screen/components/WelcomeDrawer' import { PickWinnersDrawer } from 'app/screens/track-screen/PickWinnersDrawer' import { @@ -115,6 +116,7 @@ const commonDrawersMap: { [Modal in Modals]?: ComponentType } = { DeactivateAccountConfirmation: DeactivateAccountConfirmationDrawer, TrendingGenreSelection: TrendingFilterDrawer, TrendingFilter: TrendingCombinedFilterDrawer, + FeedFilter: FeedFilterDrawer, Overflow: OverflowMenuDrawer, SignOutConfirmation: SignOutConfirmationDrawer, AddToCollection: AddToCollectionDrawer, diff --git a/packages/mobile/src/screens/feed-screen/FeedFilterButton.tsx b/packages/mobile/src/screens/feed-screen/FeedFilterButton.tsx new file mode 100644 index 00000000000..f92bdc4b442 --- /dev/null +++ b/packages/mobile/src/screens/feed-screen/FeedFilterButton.tsx @@ -0,0 +1,37 @@ +import { FeedFilter } from '@audius/common/models' +import { feedPageSelectors, modalsActions } from '@audius/common/store' +import { useDispatch, useSelector } from 'react-redux' + +import { Flex, IconLeading, SelectablePill } from '@audius/harmony-native' + +import { FEED_FILTER_MODAL } from './FeedFilterDrawer' + +export const FeedFilterButton = () => { + const dispatch = useDispatch() + const feedFilter = useSelector(feedPageSelectors.getFeedFilter) + + const hasActiveFilters = (feedFilter ?? FeedFilter.ALL) !== FeedFilter.ALL + + const handleOpenFilter = () => { + dispatch( + modalsActions.setVisibility({ + modal: FEED_FILTER_MODAL, + visible: true + }) + ) + } + + return ( + + + + ) +} diff --git a/packages/mobile/src/screens/feed-screen/FeedFilterDrawer.tsx b/packages/mobile/src/screens/feed-screen/FeedFilterDrawer.tsx new file mode 100644 index 00000000000..6b7d7aad311 --- /dev/null +++ b/packages/mobile/src/screens/feed-screen/FeedFilterDrawer.tsx @@ -0,0 +1,132 @@ +import { useCallback } from 'react' + +import { FeedFilter } from '@audius/common/models' +import { feedPageActions, feedPageSelectors } from '@audius/common/store' +import { Pressable, View } from 'react-native' +import { useDispatch, useSelector } from 'react-redux' + +import { Flex, Text } from '@audius/harmony-native' +import { RadioButton, Text as CoreText } from 'app/components/core' +import { AppDrawer, useDrawerState } from 'app/components/drawer' +import { makeStyles } from 'app/styles' + +export const FEED_FILTER_MODAL = 'FeedFilter' as const + +const filterLabels: Record = { + [FeedFilter.ALL]: 'All Posts', + [FeedFilter.ORIGINAL]: 'Original Posts', + [FeedFilter.REPOST]: 'Reposts' +} + +const filterOptions: FeedFilter[] = [ + FeedFilter.ALL, + FeedFilter.ORIGINAL, + FeedFilter.REPOST +] + +const messages = { + title: 'Filter Feed', + sectionTitle: 'Post Type', + selected: 'Selected' +} + +const useStyles = makeStyles(({ palette, spacing }) => ({ + grabBar: { + width: 36, + height: 4, + borderRadius: 2, + backgroundColor: palette.neutralLight6 + }, + grabBarContainer: { + paddingTop: spacing(2), + paddingBottom: spacing(1), + alignItems: 'center' + }, + content: { + paddingHorizontal: spacing(4), + paddingTop: spacing(4), + paddingBottom: spacing(6) + }, + sectionTitle: { + marginBottom: spacing(2) + }, + optionRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingVertical: spacing(4), + borderBottomWidth: 1, + borderBottomColor: palette.neutralLight8 + } +})) + +export const FeedFilterDrawer = () => { + const styles = useStyles() + const dispatch = useDispatch() + const currentFilter = + useSelector(feedPageSelectors.getFeedFilter) ?? FeedFilter.ALL + + useDrawerState(FEED_FILTER_MODAL) + + const handleSelectFilter = useCallback( + (filter: FeedFilter) => { + dispatch(feedPageActions.setFeedFilter(filter)) + }, + [dispatch] + ) + + const drawerHeader = useCallback( + (_props: { onClose: () => void }) => ( + + + + + + + {messages.title} + + + + ), + [styles] + ) + + return ( + + + + + {messages.sectionTitle} + + + {filterOptions.map((filter) => ( + handleSelectFilter(filter)} + style={styles.optionRow} + > + + + + {filterLabels[filter]} + + + {currentFilter === filter ? ( + + {messages.selected} + + ) : null} + + ))} + + + ) +} diff --git a/packages/mobile/src/screens/feed-screen/FeedFilters.tsx b/packages/mobile/src/screens/feed-screen/FeedFilters.tsx deleted file mode 100644 index b12b33f8c7b..00000000000 --- a/packages/mobile/src/screens/feed-screen/FeedFilters.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useCallback } from 'react' - -import { FeedFilter } from '@audius/common/models' -import { FilterButton, useTheme } from '@audius/harmony-native' -import { View } from 'react-native' - -const messages = { - allPosts: 'All Posts', - originalPosts: 'Original Posts', - reposts: 'Reposts' -} - -const filterOptions = [ - { label: messages.allPosts, value: FeedFilter.ALL }, - { label: messages.originalPosts, value: FeedFilter.ORIGINAL }, - { label: messages.reposts, value: FeedFilter.REPOST } -] - -type FeedFiltersProps = { - currentFilter: FeedFilter - onSelectFilter: (filter: FeedFilter) => void -} - -export const FeedFilters = ({ - currentFilter, - onSelectFilter -}: FeedFiltersProps) => { - const { spacing, color } = useTheme() - - const handleChange = useCallback( - (value: string | undefined) => { - if (!value) return - onSelectFilter(value as FeedFilter) - }, - [onSelectFilter] - ) - - return ( - - - - ) -} diff --git a/packages/mobile/src/screens/feed-screen/FeedScreen.tsx b/packages/mobile/src/screens/feed-screen/FeedScreen.tsx index dd178d3b74d..26a22c0e014 100644 --- a/packages/mobile/src/screens/feed-screen/FeedScreen.tsx +++ b/packages/mobile/src/screens/feed-screen/FeedScreen.tsx @@ -10,7 +10,7 @@ import { FOR_YOU_INITIAL_PAGE_SIZE, FOR_YOU_LOAD_MORE_PAGE_SIZE } from '@audius/common/api' -import { Name, FeedTab, type FeedFilter } from '@audius/common/models' +import { Name, FeedTab } from '@audius/common/models' import { feedPageActions, feedPageSelectors } from '@audius/common/store' import { useDispatch, useSelector } from 'react-redux' @@ -21,11 +21,11 @@ import { SuggestedFollows } from 'app/components/suggested-follows' import { MobileRootHeader } from 'app/screens/app-screen/MobileRootHeader' import { make, track } from 'app/services/analytics' -import { FeedFilters } from './FeedFilters' +import { FeedFilterButton } from './FeedFilterButton' import { FeedTabs } from './FeedTabs' const { getFeedTab, getFeedFilter } = feedPageSelectors -const { setFeedTab, setFeedFilter } = feedPageActions +const { setFeedTab } = feedPageActions const messages = { header: 'Your Feed', @@ -80,12 +80,17 @@ export const FeedScreen = () => { [dispatch] ) - const handleSelectFilter = useCallback( - (filter: FeedFilter) => { - dispatch(setFeedFilter(filter)) - track(make({ eventName: Name.FEED_CHANGE_VIEW, view: filter })) - }, - [dispatch] + // Memoized so the header isn't a new function reference on every render — + // otherwise Screen's setOptions runs each parent re-render and React + // Navigation rebuilds the header, remounting AccountPictureHeader and + // re-firing the profile-picture image-fetch path. + const renderHeader = useCallback( + () => ( + + {isForYou ? null : } + + ), + [isForYou] ) const lineupProps = isForYou @@ -115,20 +120,9 @@ export const FeedScreen = () => { } return ( - ( - - )} - > + - {isForYou ? null : ( - - )}