diff --git a/src/components/RadioGroup.tsx b/src/components/RadioGroup.tsx new file mode 100644 index 00000000..010f65bc --- /dev/null +++ b/src/components/RadioGroup.tsx @@ -0,0 +1,76 @@ +import React from 'react' +import {View, ViewProps} from 'react-native' + +import {atoms as a, useTheme} from '#/alf' +import {Button} from './Button' +import {Text} from './Typography' + +export function RadioGroup({ + value, + onSelect, + items, + ...props +}: ViewProps & { + value: T + onSelect: (value: T) => void + items: Array<{label: string; value: T}> +}) { + return ( + + {items.map(item => ( + + ))} + + ) +} + +function RadioIcon({selected}: {selected: boolean}) { + const t = useTheme() + return ( + + {selected && ( + + )} + + ) +} diff --git a/src/screens/Messages/Settings.tsx b/src/screens/Messages/Settings.tsx new file mode 100644 index 00000000..9faab413 --- /dev/null +++ b/src/screens/Messages/Settings.tsx @@ -0,0 +1,70 @@ +import React, {useCallback} from 'react' +import {View} from 'react-native' +import {AppBskyActorDefs} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {NativeStackScreenProps} from '@react-navigation/native-stack' +import {UseQueryResult} from '@tanstack/react-query' + +import {CommonNavigatorParams} from '#/lib/routes/types' +import {useGate} from '#/lib/statsig/statsig' +import {useUpdateActorDeclaration} from '#/state/queries/messages/actor-declaration' +import {useProfileQuery} from '#/state/queries/profile' +import {useSession} from '#/state/session' +import * as Toast from '#/view/com/util/Toast' +import {ViewHeader} from '#/view/com/util/ViewHeader' +import {CenteredView} from '#/view/com/util/Views' +import {atoms as a} from '#/alf' +import {RadioGroup} from '#/components/RadioGroup' +import {Text} from '#/components/Typography' +import {ClipClopGate} from './gate' + +type AllowIncoming = 'all' | 'none' | 'following' + +type Props = NativeStackScreenProps +export function MessagesSettingsScreen({}: Props) { + const {_} = useLingui() + const {currentAccount} = useSession() + const {data: profile} = useProfileQuery({ + did: currentAccount!.did, + }) as UseQueryResult + + const {mutate: updateDeclaration} = useUpdateActorDeclaration({ + onError: () => { + Toast.show(_(msg`Failed to update settings`)) + }, + }) + + const onSelectItem = useCallback( + (key: string) => { + updateDeclaration(key as AllowIncoming) + }, + [updateDeclaration], + ) + + const gate = useGate() + if (!gate('dms')) return + + return ( + + + + + Allow messages from + + + value={ + (profile?.associated?.chat?.allowIncoming as AllowIncoming) ?? + 'following' + } + items={[ + {label: _(msg`Everyone`), value: 'all'}, + {label: _(msg`Follows only`), value: 'following'}, + {label: _(msg`No one`), value: 'none'}, + ]} + onSelect={onSelectItem} + /> + + + ) +} diff --git a/src/screens/Messages/Settings/index.tsx b/src/screens/Messages/Settings/index.tsx deleted file mode 100644 index bd093c79..00000000 --- a/src/screens/Messages/Settings/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react' -import {View} from 'react-native' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {NativeStackScreenProps} from '@react-navigation/native-stack' - -import {CommonNavigatorParams} from '#/lib/routes/types' -import {useGate} from '#/lib/statsig/statsig' -import {ViewHeader} from '#/view/com/util/ViewHeader' -import {ClipClopGate} from '../gate' - -type Props = NativeStackScreenProps -export function MessagesSettingsScreen({}: Props) { - const {_} = useLingui() - - const gate = useGate() - if (!gate('dms')) return - - return ( - - - - ) -} diff --git a/src/state/queries/messages/actor-declaration.ts b/src/state/queries/messages/actor-declaration.ts new file mode 100644 index 00000000..c8cc4acb --- /dev/null +++ b/src/state/queries/messages/actor-declaration.ts @@ -0,0 +1,64 @@ +import {AppBskyActorDefs} from '@atproto/api' +import {useMutation, useQueryClient} from '@tanstack/react-query' + +import {logger} from '#/logger' +import {useAgent, useSession} from '#/state/session' +import {RQKEY as PROFILE_RKEY} from '../profile' + +export function useUpdateActorDeclaration({ + onSuccess, + onError, +}: { + onSuccess?: () => void + onError?: (error: Error) => void +}) { + const queryClient = useQueryClient() + const {currentAccount} = useSession() + const {getAgent} = useAgent() + + return useMutation({ + mutationFn: async (allowIncoming: 'all' | 'none' | 'following') => { + if (!currentAccount) throw new Error('Not logged in') + // TODO(sam): remove validate: false once PDSes have the new lexicon + const result = await getAgent().api.com.atproto.repo.putRecord({ + collection: 'chat.bsky.actor.declaration', + rkey: 'self', + repo: currentAccount.did, + validate: false, + record: { + $type: 'chat.bsky.actor.declaration', + allowIncoming, + }, + }) + return result + }, + onMutate: allowIncoming => { + if (!currentAccount) return + queryClient.setQueryData( + PROFILE_RKEY(currentAccount?.did), + (old?: AppBskyActorDefs.ProfileViewDetailed) => { + if (!old) return old + return { + ...old, + associated: { + ...old.associated, + chat: { + allowIncoming, + }, + }, + } satisfies AppBskyActorDefs.ProfileViewDetailed + }, + ) + }, + onSuccess, + onError: error => { + logger.error(error) + if (currentAccount) { + queryClient.invalidateQueries({ + queryKey: PROFILE_RKEY(currentAccount.did), + }) + } + onError?.(error) + }, + }) +} diff --git a/src/view/com/util/forms/RadioButton.tsx b/src/view/com/util/forms/RadioButton.tsx index 9d1cb474..6cecd318 100644 --- a/src/view/com/util/forms/RadioButton.tsx +++ b/src/view/com/util/forms/RadioButton.tsx @@ -1,9 +1,10 @@ import React from 'react' import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native' + +import {choose} from 'lib/functions' +import {useTheme} from 'lib/ThemeContext' import {Text} from '../text/Text' import {Button, ButtonType} from './Button' -import {useTheme} from 'lib/ThemeContext' -import {choose} from 'lib/functions' export function RadioButton({ testID, diff --git a/src/view/com/util/forms/RadioGroup.tsx b/src/view/com/util/forms/RadioGroup.tsx index 14599e64..493c36a9 100644 --- a/src/view/com/util/forms/RadioGroup.tsx +++ b/src/view/com/util/forms/RadioGroup.tsx @@ -1,8 +1,9 @@ import React, {useState} from 'react' import {View} from 'react-native' -import {RadioButton} from './RadioButton' -import {ButtonType} from './Button' + import {s} from 'lib/styles' +import {ButtonType} from './Button' +import {RadioButton} from './RadioButton' export interface RadioGroupItem { label: string | JSX.Element