diff options
author | Samuel Newman <mozzius@protonmail.com> | 2024-05-14 17:41:20 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-14 17:41:20 +0100 |
commit | bffb9b590672c1e636083bdf9873f5cd8ab97b57 (patch) | |
tree | f4818a5b5b0486d135ce9a3d91abbe5908243b71 /src | |
parent | 9173be686c1df9adc6cbb9cc2175f8909a868c35 (diff) | |
download | voidsky-bffb9b590672c1e636083bdf9873f5cd8ab97b57.tar.zst |
[🐴] Chat muted state (#3988)
* separate out chatlistitem and add muted icon * move bell icon to the right of the timeelapsed
Diffstat (limited to 'src')
-rw-r--r-- | src/components/icons/Bell2.tsx | 8 | ||||
-rw-r--r-- | src/screens/Messages/List/ChatListItem.tsx | 209 | ||||
-rw-r--r-- | src/screens/Messages/List/index.tsx | 187 |
3 files changed, 220 insertions, 184 deletions
diff --git a/src/components/icons/Bell2.tsx b/src/components/icons/Bell2.tsx index 084445b1d..f3d46d09d 100644 --- a/src/components/icons/Bell2.tsx +++ b/src/components/icons/Bell2.tsx @@ -7,3 +7,11 @@ export const Bell2_Stroke2_Corner0_Rounded = createSinglePathSVG({ export const Bell2_Filled_Corner0_Rounded = createSinglePathSVG({ path: 'M12 2a7.307 7.307 0 0 0-7.298 6.943l-.19 3.798-1.321 2.641A1.809 1.809 0 0 0 4.809 18H7.1a5.002 5.002 0 0 0 9.8 0h2.291a1.809 1.809 0 0 0 1.618-2.618l-1.32-2.641-.19-3.798A7.308 7.308 0 0 0 12 2Zm0 18a3.001 3.001 0 0 1-2.83-2h5.66A3.001 3.001 0 0 1 12 20Z', }) + +export const Bell2Off_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M2.293 2.293a1 1 0 0 1 1.414 0l18 18a1 1 0 0 1-1.414 1.414L17.586 19h-.94c-.904 1.748-2.607 3-4.646 3-2.039 0-3.742-1.252-4.646-3H4a1 1 0 0 1-.991-1.132l1.207-9.053c.116-.87.372-1.69.743-2.442L2.293 3.707a1 1 0 0 1 0-1.414Zm4.19 5.604c-.134.376-.23.772-.285 1.183L5.142 17h10.444L6.483 7.897ZM9.778 19c.61.637 1.399 1 2.222 1s1.613-.363 2.222-1H9.778ZM8.834 2.666a7.853 7.853 0 0 1 10.95 6.15l.645 4.832a1 1 0 0 1-1.983.265l-.644-4.833A5.853 5.853 0 0 0 9.64 4.495a1 1 0 0 1-.807-1.83Z', +}) + +export const Bell2Off_Filled_Corner0_Rounded = createSinglePathSVG({ + path: 'm19.785 8.815 1.034 7.761L7.595 3.352a7.853 7.853 0 0 1 12.19 5.463ZM4 19h3.354c.904 1.748 2.607 3 4.646 3 2.038 0 3.742-1.252 4.646-3h.94l2.707 2.707a1 1 0 0 0 1.414-1.414l-18-18a1 1 0 0 0-1.414 1.414l2.666 2.666a7.842 7.842 0 0 0-.743 2.442l-1.207 9.053A1 1 0 0 0 4 19Zm8 1c-.823 0-1.613-.363-2.222-1h4.443c-.608.637-1.398 1-2.221 1Z', +}) diff --git a/src/screens/Messages/List/ChatListItem.tsx b/src/screens/Messages/List/ChatListItem.tsx new file mode 100644 index 000000000..13706472f --- /dev/null +++ b/src/screens/Messages/List/ChatListItem.tsx @@ -0,0 +1,209 @@ +import React from 'react' +import {View} from 'react-native' +import {ChatBskyConvoDefs} from '@atproto-labs/api' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' + +import {NavigationProp} from '#/lib/routes/types' +import {isNative} from '#/platform/detection' +import {useSession} from '#/state/session' +import {TimeElapsed} from '#/view/com/util/TimeElapsed' +import {UserAvatar} from '#/view/com/util/UserAvatar' +import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' +import {Button} from '#/components/Button' +import {ConvoMenu} from '#/components/dms/ConvoMenu' +import {Bell2Off_Filled_Corner0_Rounded as BellStroke} from '#/components/icons/Bell2' +import {useMenuControl} from '#/components/Menu' +import {Text} from '#/components/Typography' + +export function ChatListItem({ + convo, + index, +}: { + convo: ChatBskyConvoDefs.ConvoView + index: number +}) { + const t = useTheme() + const {_} = useLingui() + const {currentAccount} = useSession() + const menuControl = useMenuControl() + const {gtMobile} = useBreakpoints() + + let lastMessage = _(msg`No messages yet`) + let lastMessageSentAt: string | null = null + if (ChatBskyConvoDefs.isMessageView(convo.lastMessage)) { + if (convo.lastMessage.sender?.did === currentAccount?.did) { + lastMessage = _(msg`You: ${convo.lastMessage.text}`) + } else { + lastMessage = convo.lastMessage.text + } + lastMessageSentAt = convo.lastMessage.sentAt + } + if (ChatBskyConvoDefs.isDeletedMessageView(convo.lastMessage)) { + lastMessage = _(msg`Message deleted`) + } + + const otherUser = convo.members.find( + member => member.did !== currentAccount?.did, + ) + + const navigation = useNavigation<NavigationProp>() + const [showActions, setShowActions] = React.useState(false) + + const onMouseEnter = React.useCallback(() => { + setShowActions(true) + }, []) + + const onMouseLeave = React.useCallback(() => { + setShowActions(false) + }, []) + + const onFocus = React.useCallback<React.FocusEventHandler>(e => { + if (e.nativeEvent.relatedTarget == null) return + setShowActions(true) + }, []) + + const onPress = React.useCallback(() => { + navigation.push('MessagesConversation', { + conversation: convo.id, + }) + }, [convo.id, navigation]) + + if (!otherUser) { + return null + } + + return ( + <View + // @ts-expect-error web only + onMouseEnter={onMouseEnter} + onMouseLeave={onMouseLeave} + onFocus={onFocus} + onBlur={onMouseLeave}> + <Button + label={otherUser.displayName || otherUser.handle} + onPress={onPress} + style={a.flex_1} + onLongPress={isNative ? menuControl.open : undefined}> + {({hovered, pressed}) => ( + <View + style={[ + a.flex_row, + a.flex_1, + a.px_lg, + a.py_md, + a.gap_md, + (hovered || pressed) && t.atoms.bg_contrast_25, + index === 0 && [a.border_t, a.pt_lg], + t.atoms.border_contrast_low, + ]}> + <UserAvatar avatar={otherUser?.avatar} size={52} /> + <View style={[a.flex_1, a.flex_row, a.align_center]}> + <View style={[a.flex_1]}> + <View + style={[ + a.flex_1, + a.flex_row, + a.align_end, + a.pb_2xs, + web([{marginTop: -2}]), + ]}> + <Text + numberOfLines={1} + style={[{maxWidth: '85%'}, web([a.leading_normal])]}> + <Text style={[a.text_md, t.atoms.text, a.font_bold]}> + {otherUser.displayName || otherUser.handle} + </Text> + </Text> + {lastMessageSentAt && ( + <TimeElapsed timestamp={lastMessageSentAt}> + {({timeElapsed}) => ( + <Text + style={[ + a.text_sm, + web([a.leading_normal, {whiteSpace: 'pre'}]), + t.atoms.text_contrast_medium, + ]}> + {' '} + · {timeElapsed} + </Text> + )} + </TimeElapsed> + )} + {convo.muted && ( + <Text + style={[ + a.text_sm, + web([a.leading_normal, {whiteSpace: 'pre'}]), + t.atoms.text_contrast_medium, + ]}> + {' '} + ·{' '} + <BellStroke + size="xs" + style={t.atoms.text_contrast_medium} + /> + </Text> + )} + </View> + <Text + numberOfLines={1} + style={[a.text_sm, t.atoms.text_contrast_medium, a.pb_xs]}> + @{otherUser.handle} + </Text> + <Text + numberOfLines={2} + style={[ + a.text_sm, + a.leading_snug, + convo.unreadCount > 0 + ? a.font_bold + : t.atoms.text_contrast_high, + convo.muted && t.atoms.text_contrast_medium, + ]}> + {lastMessage} + </Text> + </View> + {convo.unreadCount > 0 && ( + <View + style={[ + a.absolute, + a.rounded_full, + { + backgroundColor: convo.muted + ? t.palette.contrast_200 + : t.palette.primary_500, + height: 7, + width: 7, + }, + isNative + ? { + top: 15, + right: 12, + } + : { + top: 0, + right: 0, + }, + ]} + /> + )} + <ConvoMenu + convo={convo} + profile={otherUser} + control={menuControl} + currentScreen="list" + showMarkAsRead={convo.unreadCount > 0} + hideTrigger={isNative} + triggerOpacity={ + !gtMobile || showActions || menuControl.isOpen ? 1 : 0 + } + /> + </View> + </View> + )} + </Button> + </View> + ) +} diff --git a/src/screens/Messages/List/index.tsx b/src/screens/Messages/List/index.tsx index b9ce8f251..05559b7d1 100644 --- a/src/screens/Messages/List/index.tsx +++ b/src/screens/Messages/List/index.tsx @@ -3,28 +3,22 @@ import {View} from 'react-native' import {ChatBskyConvoDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useNavigation} from '@react-navigation/native' import {NativeStackScreenProps} from '@react-navigation/native-stack' import {sha256} from 'js-sha256' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' -import {MessagesTabNavigatorParams, NavigationProp} from '#/lib/routes/types' +import {MessagesTabNavigatorParams} from '#/lib/routes/types' import {useGate} from '#/lib/statsig/statsig' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' -import {isNative} from '#/platform/detection' import {useListConvos} from '#/state/queries/messages/list-converations' -import {useSession} from '#/state/session' import {List} from '#/view/com/util/List' -import {TimeElapsed} from '#/view/com/util/TimeElapsed' -import {UserAvatar} from '#/view/com/util/UserAvatar' import {ViewHeader} from '#/view/com/util/ViewHeader' import {CenteredView} from '#/view/com/util/Views' import {ScrollView} from '#/view/com/util/Views' -import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {DialogControlProps, useDialogControl} from '#/components/Dialog' -import {ConvoMenu} from '#/components/dms/ConvoMenu' import {NewChat} from '#/components/dms/NewChat' import * as TextField from '#/components/forms/TextField' import {useRefreshOnFocus} from '#/components/hooks/useRefreshOnFocus' @@ -32,10 +26,10 @@ import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '#/components/icons/SettingsSlider' import {Link} from '#/components/Link' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' -import {useMenuControl} from '#/components/Menu' import {Text} from '#/components/Typography' import {ClipClopGate} from '../gate' import {useDmServiceUrlStorage} from '../Temp/useDmServiceUrlStorage' +import {ChatListItem} from './ChatListItem' type Props = NativeStackScreenProps<MessagesTabNavigatorParams, 'Messages'> @@ -255,181 +249,6 @@ export function MessagesScreen({navigation, route}: Props) { ) } -function ChatListItem({ - convo, - index, -}: { - convo: ChatBskyConvoDefs.ConvoView - index: number -}) { - const t = useTheme() - const {_} = useLingui() - const {currentAccount} = useSession() - const menuControl = useMenuControl() - const {gtMobile} = useBreakpoints() - - let lastMessage = _(msg`No messages yet`) - let lastMessageSentAt: string | null = null - if (ChatBskyConvoDefs.isMessageView(convo.lastMessage)) { - if (convo.lastMessage.sender?.did === currentAccount?.did) { - lastMessage = _(msg`You: ${convo.lastMessage.text}`) - } else { - lastMessage = convo.lastMessage.text - } - lastMessageSentAt = convo.lastMessage.sentAt - } - if (ChatBskyConvoDefs.isDeletedMessageView(convo.lastMessage)) { - lastMessage = _(msg`Message deleted`) - } - - const otherUser = convo.members.find( - member => member.did !== currentAccount?.did, - ) - - const navigation = useNavigation<NavigationProp>() - const [showActions, setShowActions] = React.useState(false) - - const onMouseEnter = React.useCallback(() => { - setShowActions(true) - }, []) - - const onMouseLeave = React.useCallback(() => { - setShowActions(false) - }, []) - - const onFocus = React.useCallback<React.FocusEventHandler>(e => { - if (e.nativeEvent.relatedTarget == null) return - setShowActions(true) - }, []) - - const onPress = React.useCallback(() => { - navigation.push('MessagesConversation', { - conversation: convo.id, - }) - }, [convo.id, navigation]) - - if (!otherUser) { - return null - } - - return ( - <View - // @ts-expect-error web only - onMouseEnter={onMouseEnter} - onMouseLeave={onMouseLeave} - onFocus={onFocus} - onBlur={onMouseLeave}> - <Button - label={otherUser.displayName || otherUser.handle} - onPress={onPress} - style={a.flex_1} - onLongPress={isNative ? menuControl.open : undefined}> - {({hovered, pressed}) => ( - <View - style={[ - a.flex_row, - a.flex_1, - a.px_lg, - a.py_md, - a.gap_md, - (hovered || pressed) && t.atoms.bg_contrast_25, - index === 0 && [a.border_t, a.pt_lg], - t.atoms.border_contrast_low, - ]}> - <UserAvatar avatar={otherUser?.avatar} size={52} /> - <View style={[a.flex_1, a.flex_row, a.align_center]}> - <View style={[a.flex_1]}> - <View - style={[ - a.flex_1, - a.flex_row, - a.align_end, - a.pb_2xs, - web([{marginTop: -2}]), - ]}> - <Text - numberOfLines={1} - style={[{maxWidth: '85%'}, web([a.leading_normal])]}> - <Text style={[a.text_md, t.atoms.text, a.font_bold]}> - {otherUser.displayName || otherUser.handle} - </Text> - </Text> - {lastMessageSentAt && ( - <TimeElapsed timestamp={lastMessageSentAt}> - {({timeElapsed}) => ( - <Text - style={[ - a.text_sm, - web([a.leading_normal]), - t.atoms.text_contrast_medium, - ]}> - {' '} - · {timeElapsed} - </Text> - )} - </TimeElapsed> - )} - </View> - <Text - numberOfLines={1} - style={[a.text_sm, t.atoms.text_contrast_medium, a.pb_xs]}> - @{otherUser.handle} - </Text> - <Text - numberOfLines={2} - style={[ - a.text_sm, - a.leading_snug, - convo.unreadCount > 0 - ? a.font_bold - : t.atoms.text_contrast_high, - ]}> - {lastMessage} - </Text> - </View> - {convo.unreadCount > 0 && ( - <View - style={[ - a.absolute, - a.rounded_full, - { - backgroundColor: convo.muted - ? t.palette.contrast_200 - : t.palette.primary_500, - height: 7, - width: 7, - }, - isNative - ? { - top: 15, - right: 12, - } - : { - top: 0, - right: 0, - }, - ]} - /> - )} - <ConvoMenu - convo={convo} - profile={otherUser} - control={menuControl} - currentScreen="list" - showMarkAsRead={convo.unreadCount > 0} - hideTrigger={isNative} - triggerOpacity={ - !gtMobile || showActions || menuControl.isOpen ? 1 : 0 - } - /> - </View> - </View> - )} - </Button> - </View> - ) -} - function DesktopHeader({ newChatControl, onNavigateToSettings, |