Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at cope-settings-sync 253 lines 7.9 kB view raw
1import {useMemo, useState} from 'react' 2import {type TextStyle, View, type ViewStyle} from 'react-native' 3import {msg} from '@lingui/core/macro' 4import {useLingui} from '@lingui/react' 5import {Trans} from '@lingui/react/macro' 6import {type NativeStackScreenProps} from '@react-navigation/native-stack' 7import {useQueryClient} from '@tanstack/react-query' 8import debounce from 'lodash.debounce' 9 10import { 11 type Interest, 12 interests as allInterests, 13 useInterestsDisplayNames, 14} from '#/lib/interests' 15import {type CommonNavigatorParams} from '#/lib/routes/types' 16import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 17import { 18 preferencesQueryKey, 19 usePreferencesQuery, 20} from '#/state/queries/preferences' 21import {type UsePreferencesQueryResponse} from '#/state/queries/preferences/types' 22import {createGetSuggestedFeedsQueryKey} from '#/state/queries/trending/useGetSuggestedFeedsQuery' 23import {createGetSuggestedUsersForDiscoverQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForDiscoverQuery' 24import {createGetSuggestedUsersForExploreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForExploreQuery' 25import {createGetSuggestedUsersForSeeMoreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery' 26import {createSuggestedStarterPacksQueryKey} from '#/state/queries/useSuggestedStarterPacksQuery' 27import {useAgent} from '#/state/session' 28import {pdsAgent} from '#/state/session/agent' 29import {atoms as a, useGutters, useTheme} from '#/alf' 30import {Admonition} from '#/components/Admonition' 31import {Divider} from '#/components/Divider' 32import * as Toggle from '#/components/forms/Toggle' 33import * as Layout from '#/components/Layout' 34import {Loader} from '#/components/Loader' 35import * as Toast from '#/components/Toast' 36import {Text} from '#/components/Typography' 37 38type Props = NativeStackScreenProps<CommonNavigatorParams, 'InterestsSettings'> 39export function InterestsSettingsScreen({}: Props) { 40 const t = useTheme() 41 const gutters = useGutters(['base']) 42 const {data: preferences} = usePreferencesQuery() 43 const [isSaving, setIsSaving] = useState(false) 44 45 return ( 46 <Layout.Screen> 47 <Layout.Header.Outer> 48 <Layout.Header.BackButton /> 49 <Layout.Header.Content> 50 <Layout.Header.TitleText> 51 <Trans>Your interests</Trans> 52 </Layout.Header.TitleText> 53 </Layout.Header.Content> 54 <Layout.Header.Slot>{isSaving && <Loader />}</Layout.Header.Slot> 55 </Layout.Header.Outer> 56 <Layout.Content> 57 <View style={[gutters, a.gap_lg]}> 58 <Text 59 style={[ 60 a.flex_1, 61 a.text_sm, 62 a.leading_snug, 63 t.atoms.text_contrast_medium, 64 ]}> 65 <Trans> 66 Your selected interests help us serve you content you care about. 67 </Trans> 68 </Text> 69 70 <Divider /> 71 72 {preferences ? ( 73 <Inner preferences={preferences} setIsSaving={setIsSaving} /> 74 ) : ( 75 <View style={[a.flex_row, a.justify_center, a.p_lg]}> 76 <Loader size="xl" /> 77 </View> 78 )} 79 </View> 80 </Layout.Content> 81 </Layout.Screen> 82 ) 83} 84 85function Inner({ 86 preferences, 87 setIsSaving, 88}: { 89 preferences: UsePreferencesQueryResponse 90 setIsSaving: (isSaving: boolean) => void 91}) { 92 const {_} = useLingui() 93 const agent = useAgent() 94 const qc = useQueryClient() 95 const interestsDisplayNames = useInterestsDisplayNames() 96 const preselectedInterests = useMemo( 97 () => preferences.interests.tags || [], 98 [preferences.interests.tags], 99 ) 100 const [interests, setInterests] = useState<string[]>(preselectedInterests) 101 102 const saveInterests = useMemo(() => { 103 return debounce(async (interests: string[]) => { 104 const noEdits = 105 interests.length === preselectedInterests.length && 106 preselectedInterests.every(pre => { 107 return interests.find(int => int === pre) 108 }) 109 110 if (noEdits) return 111 112 setIsSaving(true) 113 114 try { 115 await pdsAgent(agent).setInterestsPref({tags: interests}) 116 qc.setQueriesData( 117 {queryKey: preferencesQueryKey}, 118 (old?: UsePreferencesQueryResponse) => { 119 if (!old) return old 120 old.interests.tags = interests 121 return old 122 }, 123 ) 124 await Promise.all([ 125 qc.resetQueries({queryKey: createSuggestedStarterPacksQueryKey()}), 126 qc.resetQueries({queryKey: createGetSuggestedFeedsQueryKey()}), 127 qc.resetQueries({ 128 queryKey: createGetSuggestedUsersForDiscoverQueryKey({}), 129 }), 130 qc.resetQueries({ 131 queryKey: createGetSuggestedUsersForExploreQueryKey({}), 132 }), 133 qc.resetQueries({ 134 queryKey: createGetSuggestedUsersForSeeMoreQueryKey({}), 135 }), 136 ]) 137 138 Toast.show( 139 _( 140 msg({ 141 message: 'Your interests have been updated!', 142 context: 'toast', 143 }), 144 ), 145 ) 146 } catch (error) { 147 Toast.show( 148 _( 149 msg({ 150 message: 'Failed to save your interests.', 151 context: 'toast', 152 }), 153 ), 154 { 155 type: 'error', 156 }, 157 ) 158 } finally { 159 setIsSaving(false) 160 } 161 }, 1500) 162 }, [_, agent, setIsSaving, qc, preselectedInterests]) 163 164 const onChangeInterests = async (interests: string[]) => { 165 setInterests(interests) 166 saveInterests(interests) 167 } 168 169 return ( 170 <> 171 {interests.length === 0 && ( 172 <Admonition type="tip"> 173 <Trans>We recommend selecting at least two interests.</Trans> 174 </Admonition> 175 )} 176 177 <Toggle.Group 178 values={interests} 179 onChange={onChangeInterests} 180 label={_(msg`Select your interests from the options below`)}> 181 <View style={[a.flex_row, a.flex_wrap, a.gap_sm]}> 182 {allInterests.map(interest => { 183 const name = interestsDisplayNames[interest] 184 if (!name) return null 185 return ( 186 <Toggle.Item 187 key={interest} 188 name={interest} 189 label={interestsDisplayNames[interest]}> 190 <InterestButton interest={interest} /> 191 </Toggle.Item> 192 ) 193 })} 194 </View> 195 </Toggle.Group> 196 </> 197 ) 198} 199 200export function InterestButton({interest}: {interest: Interest}) { 201 const t = useTheme() 202 const interestsDisplayNames = useInterestsDisplayNames() 203 const ctx = Toggle.useItemContext() 204 205 const enableSquareButtons = useEnableSquareButtons() 206 207 const styles = useMemo(() => { 208 const hovered: ViewStyle[] = [t.atoms.bg_contrast_100] 209 const focused: ViewStyle[] = [] 210 const pressed: ViewStyle[] = [] 211 const selected: ViewStyle[] = [t.atoms.bg_contrast_900] 212 const selectedHover: ViewStyle[] = [t.atoms.bg_contrast_975] 213 const textSelected: TextStyle[] = [t.atoms.text_inverted] 214 215 return { 216 hovered, 217 focused, 218 pressed, 219 selected, 220 selectedHover, 221 textSelected, 222 } 223 }, [t]) 224 225 return ( 226 <View 227 style={[ 228 enableSquareButtons ? a.rounded_sm : a.rounded_full, 229 a.py_md, 230 a.px_xl, 231 t.atoms.bg_contrast_50, 232 ctx.hovered ? styles.hovered : {}, 233 ctx.focused ? styles.hovered : {}, 234 ctx.pressed ? styles.hovered : {}, 235 ctx.selected ? styles.selected : {}, 236 ctx.selected && (ctx.hovered || ctx.focused || ctx.pressed) 237 ? styles.selectedHover 238 : {}, 239 ]}> 240 <Text 241 selectable={false} 242 style={[ 243 { 244 color: t.palette.contrast_900, 245 }, 246 a.font_semi_bold, 247 ctx.selected ? styles.textSelected : {}, 248 ]}> 249 {interestsDisplayNames[interest]} 250 </Text> 251 </View> 252 ) 253}