diff options
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/Button.tsx | 2 | ||||
-rw-r--r-- | src/components/Menu/index.tsx | 24 | ||||
-rw-r--r-- | src/components/dms/ConvoMenu.tsx | 177 | ||||
-rw-r--r-- | src/components/icons/ArrowBoxLeft.tsx | 5 |
4 files changed, 196 insertions, 12 deletions
diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 33d777971..dc319eb5c 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -64,7 +64,7 @@ type NonTextElements = export type ButtonProps = Pick< PressableProps, - 'disabled' | 'onPress' | 'testID' + 'disabled' | 'onPress' | 'testID' | 'onLongPress' > & AccessibilityProps & VariantProps & { diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx index 051e95b95..3be69b348 100644 --- a/src/components/Menu/index.tsx +++ b/src/components/Menu/index.tsx @@ -1,27 +1,29 @@ import React from 'react' -import {View, Pressable, ViewStyle, StyleProp} from 'react-native' +import {Pressable, StyleProp, View, ViewStyle} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' import flattenReactChildren from 'react-keyed-flatten-children' +import {isNative} from 'platform/detection' import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {useInteractionState} from '#/components/hooks/useInteractionState' -import {Text} from '#/components/Typography' - import {Context} from '#/components/Menu/context' import { ContextType, - TriggerProps, - ItemProps, GroupProps, - ItemTextProps, ItemIconProps, + ItemProps, + ItemTextProps, + TriggerProps, } from '#/components/Menu/types' -import {Button, ButtonText} from '#/components/Button' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {isNative} from 'platform/detection' +import {Text} from '#/components/Typography' -export {useDialogControl as useMenuControl} from '#/components/Dialog' +export { + type DialogControlProps as MenuControlProps, + useDialogControl as useMenuControl, +} from '#/components/Dialog' export function useMemoControlContext() { return React.useContext(Context) diff --git a/src/components/dms/ConvoMenu.tsx b/src/components/dms/ConvoMenu.tsx new file mode 100644 index 000000000..777d6c086 --- /dev/null +++ b/src/components/dms/ConvoMenu.tsx @@ -0,0 +1,177 @@ +import React, {useCallback} from 'react' +import {Pressable} from 'react-native' +import {AppBskyActorDefs} from '@atproto/api' +import {ChatBskyConvoDefs} from '@atproto-labs/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' + +import {NavigationProp} from '#/lib/routes/types' +import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' +import { + useMuteConvo, + useUnmuteConvo, +} from '#/state/queries/messages/mute-conversation' +import * as Toast from '#/view/com/util/Toast' +import {atoms as a, useTheme} from '#/alf' +import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeft} from '#/components/icons/ArrowBoxLeft' +import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid' +import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' +import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' +import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person' +import {PersonCheck_Stroke2_Corner0_Rounded as PersonCheck} from '#/components/icons/PersonCheck' +import {PersonX_Stroke2_Corner0_Rounded as PersonX} from '#/components/icons/PersonX' +import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' +import * as Menu from '#/components/Menu' +import * as Prompt from '#/components/Prompt' + +let ConvoMenu = ({ + convo, + profile, + onUpdateConvo, + control, + hideTrigger, + currentScreen, +}: { + convo: ChatBskyConvoDefs.ConvoView + profile: AppBskyActorDefs.ProfileViewBasic + onUpdateConvo?: (convo: ChatBskyConvoDefs.ConvoView) => void + control?: Menu.MenuControlProps + hideTrigger?: boolean + currentScreen: 'list' | 'conversation' +}): React.ReactNode => { + const navigation = useNavigation<NavigationProp>() + const {_} = useLingui() + const t = useTheme() + const leaveConvoControl = Prompt.usePromptControl() + + const onNavigateToProfile = useCallback(() => { + navigation.navigate('Profile', {name: profile.did}) + }, [navigation, profile.did]) + + const {mutate: muteConvo} = useMuteConvo(convo.id, { + onSuccess: data => { + onUpdateConvo?.(data.convo) + Toast.show(_(msg`Chat muted`)) + }, + onError: () => { + Toast.show(_(msg`Could not mute chat`)) + }, + }) + + const {mutate: unmuteConvo} = useUnmuteConvo(convo.id, { + onSuccess: data => { + onUpdateConvo?.(data.convo) + Toast.show(_(msg`Chat unmuted`)) + }, + onError: () => { + Toast.show(_(msg`Could not unmute chat`)) + }, + }) + + const {mutate: leaveConvo} = useLeaveConvo(convo.id, { + onSuccess: () => { + if (currentScreen === 'conversation') { + navigation.replace('MessagesList') + } + }, + onError: () => { + Toast.show(_(msg`Could not leave chat`)) + }, + }) + + return ( + <> + <Menu.Root control={control}> + {!hideTrigger && ( + <Menu.Trigger label={_(msg`Chat settings`)}> + {({props, state}) => ( + <Pressable + {...props} + style={[ + a.p_sm, + a.rounded_sm, + (state.hovered || state.pressed) && t.atoms.bg_contrast_25, + // make sure pfp is in the middle + {marginLeft: -10}, + ]}> + <DotsHorizontal size="lg" style={t.atoms.text} /> + </Pressable> + )} + </Menu.Trigger> + )} + <Menu.Outer> + <Menu.Group> + <Menu.Item + label={_(msg`Go to user's profile`)} + onPress={onNavigateToProfile}> + <Menu.ItemText> + <Trans>Go to profile</Trans> + </Menu.ItemText> + <Menu.ItemIcon icon={Person} /> + </Menu.Item> + <Menu.Item + label={_(msg`Mute notifications`)} + onPress={() => (convo?.muted ? unmuteConvo() : muteConvo())}> + <Menu.ItemText> + {convo?.muted ? ( + <Trans>Unmute notifications</Trans> + ) : ( + <Trans>Mute notifications</Trans> + )} + </Menu.ItemText> + <Menu.ItemIcon icon={convo?.muted ? Unmute : Mute} /> + </Menu.Item> + </Menu.Group> + {/* TODO(samuel): implement these */} + <Menu.Group> + <Menu.Item + label={_(msg`Block account`)} + onPress={() => {}} + disabled> + <Menu.ItemText> + <Trans>Block account</Trans> + </Menu.ItemText> + <Menu.ItemIcon + icon={profile.viewer?.blocking ? PersonCheck : PersonX} + /> + </Menu.Item> + <Menu.Item + label={_(msg`Report account`)} + onPress={() => {}} + disabled> + <Menu.ItemText> + <Trans>Report account</Trans> + </Menu.ItemText> + <Menu.ItemIcon icon={Flag} /> + </Menu.Item> + </Menu.Group> + <Menu.Group> + <Menu.Item + label={_(msg`Leave conversation`)} + onPress={leaveConvoControl.open}> + <Menu.ItemText> + <Trans>Leave conversation</Trans> + </Menu.ItemText> + <Menu.ItemIcon icon={ArrowBoxLeft} /> + </Menu.Item> + </Menu.Group> + </Menu.Outer> + </Menu.Root> + + <Prompt.Basic + control={leaveConvoControl} + title={_(msg`Leave conversation`)} + description={_( + msg`Are you sure you want to leave this conversation? Your messages will be deleted for you, but not for other participants.`, + )} + confirmButtonCta={_(msg`Leave`)} + confirmButtonColor="negative" + onConfirm={() => leaveConvo()} + /> + </> + ) +} +ConvoMenu = React.memo(ConvoMenu) + +export {ConvoMenu} diff --git a/src/components/icons/ArrowBoxLeft.tsx b/src/components/icons/ArrowBoxLeft.tsx new file mode 100644 index 000000000..011bf6afa --- /dev/null +++ b/src/components/icons/ArrowBoxLeft.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const ArrowBoxLeft_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M3.293 3.293A1 1 0 0 1 4 3h7.25a1 1 0 1 1 0 2H5v14h6.25a1 1 0 1 1 0 2H4a1 1 0 0 1-1-1V4a1 1 0 0 1 .293-.707Zm11.5 3.5a1 1 0 0 1 1.414 0l4.5 4.5a1 1 0 0 1 0 1.414l-4.5 4.5a1 1 0 0 1-1.414-1.414L17.586 13H8.75a1 1 0 1 1 0-2h8.836l-2.793-2.793a1 1 0 0 1 0-1.414Z', +}) |