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

Configure Feed

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

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