diff options
-rw-r--r-- | src/components/dms/MessagesNUX.tsx | 172 | ||||
-rw-r--r-- | src/screens/Messages/List/index.tsx | 3 | ||||
-rw-r--r-- | src/screens/Messages/Settings.tsx | 4 | ||||
-rw-r--r-- | src/state/queries/messages/actor-declaration.ts | 22 | ||||
-rw-r--r-- | src/view/screens/Settings/index.tsx | 13 |
5 files changed, 210 insertions, 4 deletions
diff --git a/src/components/dms/MessagesNUX.tsx b/src/components/dms/MessagesNUX.tsx new file mode 100644 index 000000000..81d1cfff4 --- /dev/null +++ b/src/components/dms/MessagesNUX.tsx @@ -0,0 +1,172 @@ +import React, {useCallback, useEffect} from 'react' +import {View} from 'react-native' +import {ChatBskyActorDeclaration} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +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 {atoms as a, useTheme, web} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import * as Toggle from '#/components/forms/Toggle' +import {Message_Stroke2_Corner0_Rounded} from '#/components/icons/Message' +import {Text} from '#/components/Typography' + +export function MessagesNUX() { + const control = Dialog.useDialogControl() + + const {currentAccount} = useSession() + const {data: profile} = useProfileQuery({ + did: currentAccount!.did, + }) + + useEffect(() => { + if (profile && typeof profile.associated?.chat === 'undefined') { + const timeout = setTimeout(() => { + control.open() + }, 1000) + + return () => { + clearTimeout(timeout) + } + } + }, [profile, control]) + + if (!profile) return null + + return ( + <Dialog.Outer control={control}> + <Dialog.Handle /> + <DialogInner chatDeclation={profile.associated?.chat} /> + </Dialog.Outer> + ) +} + +function DialogInner({ + chatDeclation, +}: { + chatDeclation?: ChatBskyActorDeclaration.Record +}) { + const control = Dialog.useDialogContext() + const {_} = useLingui() + const t = useTheme() + const {mutate: updateDeclaration} = useUpdateActorDeclaration({ + onError: () => { + Toast.show(_(msg`Failed to update settings`)) + }, + }) + + const onSelectItem = useCallback( + (keys: string[]) => { + const key = keys[0] + if (!key) return + updateDeclaration(key as 'all' | 'none' | 'following') + }, + [updateDeclaration], + ) + + useEffect(() => { + if (!chatDeclation) { + updateDeclaration('following') + } + }, [chatDeclation, updateDeclaration]) + + return ( + <Dialog.ScrollableInner + label={_(msg`Introducing Direct Messages`)} + style={web({maxWidth: 440})}> + <View style={a.gap_xl}> + <View style={[a.align_center, a.pt_sm, a.pb_xs]}> + <Message_Stroke2_Corner0_Rounded width={64} /> + <Text style={[a.text_2xl, a.font_bold, a.text_center, a.mt_md]}> + <Trans>Direct messages are here!</Trans> + </Text> + <Text style={[a.text_md, a.text_center, a.mt_sm]}> + <Trans>Privately chat with other users.</Trans> + </Text> + </View> + <View + style={[ + a.gap_xs, + a.border, + a.overflow_hidden, + a.rounded_sm, + t.atoms.border_contrast_low, + ]}> + <View + style={[ + a.p_md, + a.border_b, + t.atoms.bg_contrast_25, + t.atoms.border_contrast_low, + ]}> + <Text style={[a.text_sm, a.font_bold]}> + <Trans>Who can message you?</Trans> + </Text> + <Text + style={[ + a.mt_xs, + a.text_sm, + a.italic, + t.atoms.text_contrast_medium, + ]}> + <Trans>You can change this at any time.</Trans> + </Text> + </View> + <View style={[a.px_md, a.py_xs]}> + <Toggle.Group + label={_(msg`Who can message you?`)} + type="radio" + values={[chatDeclation?.allowIncoming ?? 'following']} + onChange={onSelectItem}> + <View> + <Toggle.Item + name="all" + label={_(msg`Everyone`)} + style={[a.justify_between, a.py_sm, a.rounded_2xs]}> + <Toggle.LabelText> + <Trans>Everyone</Trans> + </Toggle.LabelText> + <Toggle.Radio /> + </Toggle.Item> + <Toggle.Item + name="following" + label={_(msg`Users I follow`)} + style={[a.justify_between, a.py_sm, a.rounded_2xs]}> + <Toggle.LabelText> + <Trans>Users I follow</Trans> + </Toggle.LabelText> + <Toggle.Radio /> + </Toggle.Item> + <Toggle.Item + name="none" + label={_(msg`No one`)} + style={[a.justify_between, a.py_sm, a.rounded_2xs]}> + <Toggle.LabelText> + <Trans>No one</Trans> + </Toggle.LabelText> + <Toggle.Radio /> + </Toggle.Item> + </View> + </Toggle.Group> + </View> + </View> + <Button + label={_(msg`Start chatting`)} + accessibilityHint={_(msg`Close modal`)} + size="medium" + color="primary" + variant="solid" + onPress={() => control.close()}> + <ButtonText> + <Trans>Get started</Trans> + </ButtonText> + </Button> + </View> + <Dialog.Close /> + </Dialog.ScrollableInner> + ) +} diff --git a/src/screens/Messages/List/index.tsx b/src/screens/Messages/List/index.tsx index 060dac630..e36d1edf2 100644 --- a/src/screens/Messages/List/index.tsx +++ b/src/screens/Messages/List/index.tsx @@ -17,6 +17,7 @@ import {CenteredView} from '#/view/com/util/Views' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {DialogControlProps, useDialogControl} from '#/components/Dialog' +import {MessagesNUX} from '#/components/dms/MessagesNUX' import {NewChat} from '#/components/dms/NewChat' import {useRefreshOnFocus} from '#/components/hooks/useRefreshOnFocus' import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' @@ -131,6 +132,7 @@ export function MessagesScreen({navigation, route}: Props) { if (conversations.length < 1) { return ( <View style={a.flex_1}> + <MessagesNUX /> {gtMobile ? ( <CenteredView sideBorders> <DesktopHeader @@ -165,6 +167,7 @@ export function MessagesScreen({navigation, route}: Props) { return ( <View style={a.flex_1}> + <MessagesNUX /> {!gtMobile && ( <ViewHeader title={_(msg`Messages`)} diff --git a/src/screens/Messages/Settings.tsx b/src/screens/Messages/Settings.tsx index a9c35dba7..7dbf027f9 100644 --- a/src/screens/Messages/Settings.tsx +++ b/src/screens/Messages/Settings.tsx @@ -1,10 +1,8 @@ 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' @@ -30,7 +28,7 @@ export function MessagesSettingsScreen({}: Props) { const {currentAccount} = useSession() const {data: profile} = useProfileQuery({ did: currentAccount!.did, - }) as UseQueryResult<AppBskyActorDefs.ProfileViewDetailed, Error> + }) const {preferences, setPref} = useBackgroundNotificationPreferences() const {mutate: updateDeclaration} = useUpdateActorDeclaration({ diff --git a/src/state/queries/messages/actor-declaration.ts b/src/state/queries/messages/actor-declaration.ts index c8cc4acbd..0886af382 100644 --- a/src/state/queries/messages/actor-declaration.ts +++ b/src/state/queries/messages/actor-declaration.ts @@ -21,9 +21,9 @@ export function useUpdateActorDeclaration({ 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({ + repo: currentAccount.did, collection: 'chat.bsky.actor.declaration', rkey: 'self', - repo: currentAccount.did, validate: false, record: { $type: 'chat.bsky.actor.declaration', @@ -62,3 +62,23 @@ export function useUpdateActorDeclaration({ }, }) } + +// for use in the settings screen for testing +export function useDeleteActorDeclaration() { + const {currentAccount} = useSession() + const {getAgent} = useAgent() + + return useMutation({ + mutationFn: async () => { + 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.deleteRecord({ + repo: currentAccount.did, + collection: 'chat.bsky.actor.declaration', + rkey: 'self', + validate: false, + }) + return result + }, + }) +} diff --git a/src/view/screens/Settings/index.tsx b/src/view/screens/Settings/index.tsx index c3864e5a9..b3b937c61 100644 --- a/src/view/screens/Settings/index.tsx +++ b/src/view/screens/Settings/index.tsx @@ -26,6 +26,7 @@ import { useInAppBrowser, useSetInAppBrowser, } from '#/state/preferences/in-app-browser' +import {useDeleteActorDeclaration} from '#/state/queries/messages/actor-declaration' import {useClearPreferencesMutation} from '#/state/queries/preferences' import {RQKEY as RQKEY_PROFILE} from '#/state/queries/profile' import {useProfileQuery} from '#/state/queries/profile' @@ -305,6 +306,8 @@ export function SettingsScreen({}: Props) { Toast.show(_(msg`Legacy storage cleared, you need to restart the app now.`)) }, [_]) + const {mutate: onPressDeleteChatDeclaration} = useDeleteActorDeclaration() + return ( <View style={s.hContentRegion} testID="settingsScreen"> <ExportCarDialog control={exportCarControl} /> @@ -828,6 +831,16 @@ export function SettingsScreen({}: Props) { </TouchableOpacity> <TouchableOpacity style={[pal.view, styles.linkCardNoIcon]} + onPress={() => onPressDeleteChatDeclaration()} + accessibilityRole="button" + accessibilityLabel={_(msg`Delete chat declaration record`)} + accessibilityHint={_(msg`Deletes the chat declaration record`)}> + <Text type="lg" style={pal.text}> + <Trans>Delete chat declaration record</Trans> + </Text> + </TouchableOpacity> + <TouchableOpacity + style={[pal.view, styles.linkCardNoIcon]} onPress={onPressResetOnboarding} accessibilityRole="button" accessibilityLabel={_(msg`Reset onboarding state`)} |