mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

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

[🐴] Settings screen (#3830)

* create settings screen + api

* update api package

* use putrecord API with validate false

* create new RadioGroup component

authored by samuel.fm and committed by

GitHub 5af61ca4 9861494e

+216 -28
+76
src/components/RadioGroup.tsx
··· 1 + import React from 'react' 2 + import {View, ViewProps} from 'react-native' 3 + 4 + import {atoms as a, useTheme} from '#/alf' 5 + import {Button} from './Button' 6 + import {Text} from './Typography' 7 + 8 + export function RadioGroup<T extends string | number>({ 9 + value, 10 + onSelect, 11 + items, 12 + ...props 13 + }: ViewProps & { 14 + value: T 15 + onSelect: (value: T) => void 16 + items: Array<{label: string; value: T}> 17 + }) { 18 + return ( 19 + <View {...props}> 20 + {items.map(item => ( 21 + <Button 22 + label={item.label} 23 + key={item.value} 24 + variant="ghost" 25 + color="secondary" 26 + size="small" 27 + onPress={() => onSelect(item.value)} 28 + style={[a.justify_between, a.px_sm]}> 29 + <Text style={a.text_md}>{item.label}</Text> 30 + <RadioIcon selected={value === item.value} /> 31 + </Button> 32 + ))} 33 + </View> 34 + ) 35 + } 36 + 37 + function RadioIcon({selected}: {selected: boolean}) { 38 + const t = useTheme() 39 + return ( 40 + <View 41 + style={[ 42 + { 43 + width: 30, 44 + height: 30, 45 + borderWidth: 2, 46 + borderColor: selected 47 + ? t.palette.primary_500 48 + : t.palette.contrast_200, 49 + }, 50 + selected 51 + ? { 52 + backgroundColor: 53 + t.name === 'light' 54 + ? t.palette.primary_100 55 + : t.palette.primary_900, 56 + } 57 + : t.atoms.bg, 58 + a.align_center, 59 + a.justify_center, 60 + a.rounded_full, 61 + ]}> 62 + {selected && ( 63 + <View 64 + style={[ 65 + { 66 + width: 18, 67 + height: 18, 68 + backgroundColor: t.palette.primary_500, 69 + }, 70 + a.rounded_full, 71 + ]} 72 + /> 73 + )} 74 + </View> 75 + ) 76 + }
+70
src/screens/Messages/Settings.tsx
··· 1 + import React, {useCallback} from 'react' 2 + import {View} from 'react-native' 3 + import {AppBskyActorDefs} from '@atproto/api' 4 + import {msg, Trans} from '@lingui/macro' 5 + import {useLingui} from '@lingui/react' 6 + import {NativeStackScreenProps} from '@react-navigation/native-stack' 7 + import {UseQueryResult} from '@tanstack/react-query' 8 + 9 + import {CommonNavigatorParams} from '#/lib/routes/types' 10 + import {useGate} from '#/lib/statsig/statsig' 11 + import {useUpdateActorDeclaration} from '#/state/queries/messages/actor-declaration' 12 + import {useProfileQuery} from '#/state/queries/profile' 13 + import {useSession} from '#/state/session' 14 + import * as Toast from '#/view/com/util/Toast' 15 + import {ViewHeader} from '#/view/com/util/ViewHeader' 16 + import {CenteredView} from '#/view/com/util/Views' 17 + import {atoms as a} from '#/alf' 18 + import {RadioGroup} from '#/components/RadioGroup' 19 + import {Text} from '#/components/Typography' 20 + import {ClipClopGate} from './gate' 21 + 22 + type AllowIncoming = 'all' | 'none' | 'following' 23 + 24 + type Props = NativeStackScreenProps<CommonNavigatorParams, 'MessagesSettings'> 25 + export function MessagesSettingsScreen({}: Props) { 26 + const {_} = useLingui() 27 + const {currentAccount} = useSession() 28 + const {data: profile} = useProfileQuery({ 29 + did: currentAccount!.did, 30 + }) as UseQueryResult<AppBskyActorDefs.ProfileViewDetailed, Error> 31 + 32 + const {mutate: updateDeclaration} = useUpdateActorDeclaration({ 33 + onError: () => { 34 + Toast.show(_(msg`Failed to update settings`)) 35 + }, 36 + }) 37 + 38 + const onSelectItem = useCallback( 39 + (key: string) => { 40 + updateDeclaration(key as AllowIncoming) 41 + }, 42 + [updateDeclaration], 43 + ) 44 + 45 + const gate = useGate() 46 + if (!gate('dms')) return <ClipClopGate /> 47 + 48 + return ( 49 + <CenteredView sideBorders> 50 + <ViewHeader title={_(msg`Settings`)} showOnDesktop showBorder /> 51 + <View style={[a.px_md, a.py_lg, a.gap_md]}> 52 + <Text style={[a.text_xl, a.font_bold, a.px_sm]}> 53 + <Trans>Allow messages from</Trans> 54 + </Text> 55 + <RadioGroup<AllowIncoming> 56 + value={ 57 + (profile?.associated?.chat?.allowIncoming as AllowIncoming) ?? 58 + 'following' 59 + } 60 + items={[ 61 + {label: _(msg`Everyone`), value: 'all'}, 62 + {label: _(msg`Follows only`), value: 'following'}, 63 + {label: _(msg`No one`), value: 'none'}, 64 + ]} 65 + onSelect={onSelectItem} 66 + /> 67 + </View> 68 + </CenteredView> 69 + ) 70 + }
-24
src/screens/Messages/Settings/index.tsx
··· 1 - import React from 'react' 2 - import {View} from 'react-native' 3 - import {msg} from '@lingui/macro' 4 - import {useLingui} from '@lingui/react' 5 - import {NativeStackScreenProps} from '@react-navigation/native-stack' 6 - 7 - import {CommonNavigatorParams} from '#/lib/routes/types' 8 - import {useGate} from '#/lib/statsig/statsig' 9 - import {ViewHeader} from '#/view/com/util/ViewHeader' 10 - import {ClipClopGate} from '../gate' 11 - 12 - type Props = NativeStackScreenProps<CommonNavigatorParams, 'MessagesSettings'> 13 - export function MessagesSettingsScreen({}: Props) { 14 - const {_} = useLingui() 15 - 16 - const gate = useGate() 17 - if (!gate('dms')) return <ClipClopGate /> 18 - 19 - return ( 20 - <View> 21 - <ViewHeader title={_(msg`Settings`)} showOnDesktop /> 22 - </View> 23 - ) 24 - }
+64
src/state/queries/messages/actor-declaration.ts
··· 1 + import {AppBskyActorDefs} from '@atproto/api' 2 + import {useMutation, useQueryClient} from '@tanstack/react-query' 3 + 4 + import {logger} from '#/logger' 5 + import {useAgent, useSession} from '#/state/session' 6 + import {RQKEY as PROFILE_RKEY} from '../profile' 7 + 8 + export function useUpdateActorDeclaration({ 9 + onSuccess, 10 + onError, 11 + }: { 12 + onSuccess?: () => void 13 + onError?: (error: Error) => void 14 + }) { 15 + const queryClient = useQueryClient() 16 + const {currentAccount} = useSession() 17 + const {getAgent} = useAgent() 18 + 19 + return useMutation({ 20 + mutationFn: async (allowIncoming: 'all' | 'none' | 'following') => { 21 + if (!currentAccount) throw new Error('Not logged in') 22 + // TODO(sam): remove validate: false once PDSes have the new lexicon 23 + const result = await getAgent().api.com.atproto.repo.putRecord({ 24 + collection: 'chat.bsky.actor.declaration', 25 + rkey: 'self', 26 + repo: currentAccount.did, 27 + validate: false, 28 + record: { 29 + $type: 'chat.bsky.actor.declaration', 30 + allowIncoming, 31 + }, 32 + }) 33 + return result 34 + }, 35 + onMutate: allowIncoming => { 36 + if (!currentAccount) return 37 + queryClient.setQueryData( 38 + PROFILE_RKEY(currentAccount?.did), 39 + (old?: AppBskyActorDefs.ProfileViewDetailed) => { 40 + if (!old) return old 41 + return { 42 + ...old, 43 + associated: { 44 + ...old.associated, 45 + chat: { 46 + allowIncoming, 47 + }, 48 + }, 49 + } satisfies AppBskyActorDefs.ProfileViewDetailed 50 + }, 51 + ) 52 + }, 53 + onSuccess, 54 + onError: error => { 55 + logger.error(error) 56 + if (currentAccount) { 57 + queryClient.invalidateQueries({ 58 + queryKey: PROFILE_RKEY(currentAccount.did), 59 + }) 60 + } 61 + onError?.(error) 62 + }, 63 + }) 64 + }
+3 -2
src/view/com/util/forms/RadioButton.tsx
··· 1 1 import React from 'react' 2 2 import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native' 3 + 4 + import {choose} from 'lib/functions' 5 + import {useTheme} from 'lib/ThemeContext' 3 6 import {Text} from '../text/Text' 4 7 import {Button, ButtonType} from './Button' 5 - import {useTheme} from 'lib/ThemeContext' 6 - import {choose} from 'lib/functions' 7 8 8 9 export function RadioButton({ 9 10 testID,
+3 -2
src/view/com/util/forms/RadioGroup.tsx
··· 1 1 import React, {useState} from 'react' 2 2 import {View} from 'react-native' 3 + 4 + import {s} from 'lib/styles' 5 + import {ButtonType} from './Button' 3 6 import {RadioButton} from './RadioButton' 4 - import {ButtonType} from './Button' 5 - import {s} from 'lib/styles' 6 7 7 8 export interface RadioGroupItem { 8 9 label: string | JSX.Element