Skip to content

Commit 0f9d73a

Browse files
authored
feat(mobile): chronological feed filter button uses drawer pattern (#14335)
1 parent b5817fc commit 0f9d73a

8 files changed

Lines changed: 189 additions & 79 deletions

File tree

packages/common/src/store/ui/modals/parentSlice.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const initialState: BasicModalsState = {
2626
TrendingCategory: { isOpen: false },
2727
TrendingTimeRange: { isOpen: false },
2828
TrendingFilter: { isOpen: false },
29+
FeedFilter: { isOpen: false },
2930
TrendingRewardsExplainer: { isOpen: false },
3031
SocialProof: { isOpen: false },
3132
EditTrack: { isOpen: false },

packages/common/src/store/ui/modals/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export type Modals =
6161
| 'TrendingCategory'
6262
| 'TrendingTimeRange'
6363
| 'TrendingFilter'
64+
| 'FeedFilter'
6465
| 'TrendingRewardsExplainer'
6566
| 'SocialProof'
6667
| 'EditTrack'

packages/mobile/src/app/Drawers.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import { ReplaceTrackProgressDrawer } from 'app/screens/edit-track-screen/compon
5252
import { EarlyReleaseConfirmationDrawer } from 'app/screens/edit-track-screen/components/EarlyReleaseConfirmationDrawer'
5353
import { PublishConfirmationDrawer } from 'app/screens/edit-track-screen/components/PublishConfirmationDrawer'
5454
import { ConnectNewWalletDrawer } from 'app/screens/external-wallets/components/ConnectNewWalletDrawer'
55+
import { FeedFilterDrawer } from 'app/screens/feed-screen'
5556
import { WelcomeDrawer } from 'app/screens/sign-on-screen/components/WelcomeDrawer'
5657
import { PickWinnersDrawer } from 'app/screens/track-screen/PickWinnersDrawer'
5758
import {
@@ -115,6 +116,7 @@ const commonDrawersMap: { [Modal in Modals]?: ComponentType } = {
115116
DeactivateAccountConfirmation: DeactivateAccountConfirmationDrawer,
116117
TrendingGenreSelection: TrendingFilterDrawer,
117118
TrendingFilter: TrendingCombinedFilterDrawer,
119+
FeedFilter: FeedFilterDrawer,
118120
Overflow: OverflowMenuDrawer,
119121
SignOutConfirmation: SignOutConfirmationDrawer,
120122
AddToCollection: AddToCollectionDrawer,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { FeedFilter } from '@audius/common/models'
2+
import { feedPageSelectors, modalsActions } from '@audius/common/store'
3+
import { useDispatch, useSelector } from 'react-redux'
4+
5+
import { Flex, IconLeading, SelectablePill } from '@audius/harmony-native'
6+
7+
import { FEED_FILTER_MODAL } from './FeedFilterDrawer'
8+
9+
export const FeedFilterButton = () => {
10+
const dispatch = useDispatch()
11+
const feedFilter = useSelector(feedPageSelectors.getFeedFilter)
12+
13+
const hasActiveFilters = (feedFilter ?? FeedFilter.ALL) !== FeedFilter.ALL
14+
15+
const handleOpenFilter = () => {
16+
dispatch(
17+
modalsActions.setVisibility({
18+
modal: FEED_FILTER_MODAL,
19+
visible: true
20+
})
21+
)
22+
}
23+
24+
return (
25+
<Flex>
26+
<SelectablePill
27+
type='button'
28+
icon={IconLeading}
29+
size='large'
30+
isSelected={hasActiveFilters}
31+
isControlled
32+
onPress={handleOpenFilter}
33+
accessibilityLabel='Open filter'
34+
/>
35+
</Flex>
36+
)
37+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { useCallback } from 'react'
2+
3+
import { FeedFilter } from '@audius/common/models'
4+
import { feedPageActions, feedPageSelectors } from '@audius/common/store'
5+
import { Pressable, View } from 'react-native'
6+
import { useDispatch, useSelector } from 'react-redux'
7+
8+
import { Flex, Text } from '@audius/harmony-native'
9+
import { RadioButton, Text as CoreText } from 'app/components/core'
10+
import { AppDrawer, useDrawerState } from 'app/components/drawer'
11+
import { makeStyles } from 'app/styles'
12+
13+
export const FEED_FILTER_MODAL = 'FeedFilter' as const
14+
15+
const filterLabels: Record<FeedFilter, string> = {
16+
[FeedFilter.ALL]: 'All Posts',
17+
[FeedFilter.ORIGINAL]: 'Original Posts',
18+
[FeedFilter.REPOST]: 'Reposts'
19+
}
20+
21+
const filterOptions: FeedFilter[] = [
22+
FeedFilter.ALL,
23+
FeedFilter.ORIGINAL,
24+
FeedFilter.REPOST
25+
]
26+
27+
const messages = {
28+
title: 'Filter Feed',
29+
sectionTitle: 'Post Type',
30+
selected: 'Selected'
31+
}
32+
33+
const useStyles = makeStyles(({ palette, spacing }) => ({
34+
grabBar: {
35+
width: 36,
36+
height: 4,
37+
borderRadius: 2,
38+
backgroundColor: palette.neutralLight6
39+
},
40+
grabBarContainer: {
41+
paddingTop: spacing(2),
42+
paddingBottom: spacing(1),
43+
alignItems: 'center'
44+
},
45+
content: {
46+
paddingHorizontal: spacing(4),
47+
paddingTop: spacing(4),
48+
paddingBottom: spacing(6)
49+
},
50+
sectionTitle: {
51+
marginBottom: spacing(2)
52+
},
53+
optionRow: {
54+
flexDirection: 'row',
55+
alignItems: 'center',
56+
justifyContent: 'space-between',
57+
paddingVertical: spacing(4),
58+
borderBottomWidth: 1,
59+
borderBottomColor: palette.neutralLight8
60+
}
61+
}))
62+
63+
export const FeedFilterDrawer = () => {
64+
const styles = useStyles()
65+
const dispatch = useDispatch()
66+
const currentFilter =
67+
useSelector(feedPageSelectors.getFeedFilter) ?? FeedFilter.ALL
68+
69+
useDrawerState(FEED_FILTER_MODAL)
70+
71+
const handleSelectFilter = useCallback(
72+
(filter: FeedFilter) => {
73+
dispatch(feedPageActions.setFeedFilter(filter))
74+
},
75+
[dispatch]
76+
)
77+
78+
const drawerHeader = useCallback(
79+
(_props: { onClose: () => void }) => (
80+
<Flex>
81+
<View style={[styles.grabBarContainer]}>
82+
<View style={[styles.grabBar]} />
83+
</View>
84+
<Flex ph='l' pb='l' pt='xs' justifyContent='center' alignItems='center'>
85+
<Text variant='title' size='l' textAlign='center'>
86+
{messages.title}
87+
</Text>
88+
</Flex>
89+
</Flex>
90+
),
91+
[styles]
92+
)
93+
94+
return (
95+
<AppDrawer
96+
modalName={FEED_FILTER_MODAL}
97+
drawerHeader={drawerHeader}
98+
isGestureSupported
99+
>
100+
<View style={styles.content}>
101+
<View style={styles.sectionTitle}>
102+
<CoreText fontSize='large' weight='demiBold'>
103+
{messages.sectionTitle}
104+
</CoreText>
105+
</View>
106+
{filterOptions.map((filter) => (
107+
<Pressable
108+
key={filter}
109+
onPress={() => handleSelectFilter(filter)}
110+
style={styles.optionRow}
111+
>
112+
<Flex direction='row' alignItems='center' gap='m'>
113+
<RadioButton checked={currentFilter === filter} />
114+
<Text
115+
variant='body'
116+
size='l'
117+
color={currentFilter === filter ? 'accent' : 'default'}
118+
>
119+
{filterLabels[filter]}
120+
</Text>
121+
</Flex>
122+
{currentFilter === filter ? (
123+
<Text variant='body' size='s' color='accent'>
124+
{messages.selected}
125+
</Text>
126+
) : null}
127+
</Pressable>
128+
))}
129+
</View>
130+
</AppDrawer>
131+
)
132+
}

packages/mobile/src/screens/feed-screen/FeedFilters.tsx

Lines changed: 0 additions & 58 deletions
This file was deleted.

packages/mobile/src/screens/feed-screen/FeedScreen.tsx

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
FOR_YOU_INITIAL_PAGE_SIZE,
1111
FOR_YOU_LOAD_MORE_PAGE_SIZE
1212
} from '@audius/common/api'
13-
import { Name, FeedTab, type FeedFilter } from '@audius/common/models'
13+
import { Name, FeedTab } from '@audius/common/models'
1414
import { feedPageActions, feedPageSelectors } from '@audius/common/store'
1515
import { useDispatch, useSelector } from 'react-redux'
1616

@@ -21,11 +21,11 @@ import { SuggestedFollows } from 'app/components/suggested-follows'
2121
import { MobileRootHeader } from 'app/screens/app-screen/MobileRootHeader'
2222
import { make, track } from 'app/services/analytics'
2323

24-
import { FeedFilters } from './FeedFilters'
24+
import { FeedFilterButton } from './FeedFilterButton'
2525
import { FeedTabs } from './FeedTabs'
2626

2727
const { getFeedTab, getFeedFilter } = feedPageSelectors
28-
const { setFeedTab, setFeedFilter } = feedPageActions
28+
const { setFeedTab } = feedPageActions
2929

3030
const messages = {
3131
header: 'Your Feed',
@@ -80,12 +80,17 @@ export const FeedScreen = () => {
8080
[dispatch]
8181
)
8282

83-
const handleSelectFilter = useCallback(
84-
(filter: FeedFilter) => {
85-
dispatch(setFeedFilter(filter))
86-
track(make({ eventName: Name.FEED_CHANGE_VIEW, view: filter }))
87-
},
88-
[dispatch]
83+
// Memoized so the header isn't a new function reference on every render —
84+
// otherwise Screen's setOptions runs each parent re-render and React
85+
// Navigation rebuilds the header, remounting AccountPictureHeader and
86+
// re-firing the profile-picture image-fetch path.
87+
const renderHeader = useCallback(
88+
() => (
89+
<MobileRootHeader title={messages.header} showDivider={false}>
90+
{isForYou ? null : <FeedFilterButton />}
91+
</MobileRootHeader>
92+
),
93+
[isForYou]
8994
)
9095

9196
const lineupProps = isForYou
@@ -115,20 +120,9 @@ export const FeedScreen = () => {
115120
}
116121

117122
return (
118-
<Screen
119-
url='Feed'
120-
header={() => (
121-
<MobileRootHeader title={messages.header} showDivider={false} />
122-
)}
123-
>
123+
<Screen url='Feed' header={renderHeader}>
124124
<ScreenContent>
125125
<FeedTabs currentTab={feedTab} onSelectTab={handleSelectTab} />
126-
{isForYou ? null : (
127-
<FeedFilters
128-
currentFilter={feedFilter}
129-
onSelectFilter={handleSelectFilter}
130-
/>
131-
)}
132126
<TrackLineup
133127
key={`feed-${feedTab}`}
134128
source='DISCOVER_FEED'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { FeedScreen } from './FeedScreen'
2+
export { FeedFilterDrawer, FEED_FILTER_MODAL } from './FeedFilterDrawer'

0 commit comments

Comments
 (0)