From 5ba7f20162270a7644414259faec744fdef18108 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 23 Nov 2021 15:18:46 +1100 Subject: [PATCH 1/3] speed up fetching closed group's members avatar --- js/background.js | 21 ---- ts/components/Avatar.tsx | 23 ++-- .../AvatarPlaceHolder/ClosedGroupAvatar.tsx | 100 +++++++++--------- ts/components/ConversationListItem.tsx | 10 +- .../conversation/ConversationHeader.tsx | 10 +- .../conversation/SessionRightPanel.tsx | 10 +- .../usingClosedConversationDetails.tsx | 69 ------------ ts/hooks/useMembersAvatar.tsx | 55 ---------- ts/hooks/useMembersAvatars.tsx | 55 ++++++++++ ts/state/selectors/conversations.ts | 1 + 10 files changed, 118 insertions(+), 236 deletions(-) delete mode 100644 ts/components/session/usingClosedConversationDetails.tsx delete mode 100644 ts/hooks/useMembersAvatar.tsx create mode 100644 ts/hooks/useMembersAvatars.tsx diff --git a/js/background.js b/js/background.js index 092edf848..bbf003ee3 100644 --- a/js/background.js +++ b/js/background.js @@ -192,27 +192,6 @@ } }); - Whisper.events.on('deleteLocalPublicMessages', async ({ messageServerIds, conversationId }) => { - if (!Array.isArray(messageServerIds)) { - return; - } - const messageIds = await window.Signal.Data.getMessageIdsFromServerIds( - messageServerIds, - conversationId - ); - if (messageIds.length === 0) { - return; - } - - const conversation = window.getConversationController().get(conversationId); - messageIds.forEach(id => { - if (conversation) { - conversation.removeMessage(id); - } - window.Signal.Data.removeMessage(id); - }); - }); - function manageExpiringData() { window.Signal.Data.cleanSeenMessages(); window.Signal.Data.cleanLastHashes(); diff --git a/ts/components/Avatar.tsx b/ts/components/Avatar.tsx index 0c347cce8..8597d6850 100644 --- a/ts/components/Avatar.tsx +++ b/ts/components/Avatar.tsx @@ -2,9 +2,9 @@ import React, { useCallback, useState } from 'react'; import classNames from 'classnames'; import { AvatarPlaceHolder, ClosedGroupAvatar } from './AvatarPlaceHolder'; -import { ConversationAvatar } from './session/usingClosedConversationDetails'; import { useEncryptedFileFetch } from '../hooks/useEncryptedFileFetch'; import _ from 'underscore'; +import { useMembersAvatars } from '../hooks/useMembersAvatars'; export enum AvatarSize { XS = 28, @@ -21,7 +21,6 @@ type Props = { pubkey?: string; size: AvatarSize; base64Data?: string; // if this is not empty, it will be used to render the avatar with base64 encoded data - memberAvatars?: Array; // this is added by usingClosedConversationDetails onAvatarClick?: () => void; dataTestId?: string; }; @@ -42,21 +41,17 @@ const Identicon = (props: Props) => { }; const NoImage = (props: { - memberAvatars?: Array; name?: string; pubkey?: string; size: AvatarSize; + isClosedGroup: boolean; onAvatarClick?: () => void; }) => { - const { name, memberAvatars, size, pubkey } = props; + const { name, size, pubkey, isClosedGroup } = props; // if no image but we have conversations set for the group, renders group members avatars - if (memberAvatars) { + if (pubkey && isClosedGroup) { return ( - + ); } @@ -93,8 +88,10 @@ const AvatarImage = (props: { }; const AvatarInner = (props: Props) => { - const { avatarPath, base64Data, size, memberAvatars, name, dataTestId } = props; + const { avatarPath, base64Data, size, pubkey, name, dataTestId } = props; const [imageBroken, setImageBroken] = useState(false); + + const closedGroupMembers = useMembersAvatars(pubkey); // contentType is not important const { urlToLoad } = useEncryptedFileFetch(avatarPath || '', ''); const handleImageError = () => { @@ -106,7 +103,7 @@ const AvatarInner = (props: Props) => { setImageBroken(true); }; - const isClosedGroupAvatar = Boolean(memberAvatars?.length); + const isClosedGroupAvatar = Boolean(closedGroupMembers?.length); const hasImage = (base64Data || urlToLoad) && !imageBroken && !isClosedGroupAvatar; const isClickable = !!props.onAvatarClick; @@ -134,7 +131,7 @@ const AvatarInner = (props: Props) => { handleImageError={handleImageError} /> ) : ( - + )} ); diff --git a/ts/components/AvatarPlaceHolder/ClosedGroupAvatar.tsx b/ts/components/AvatarPlaceHolder/ClosedGroupAvatar.tsx index 4dc344564..be3ef30d1 100644 --- a/ts/components/AvatarPlaceHolder/ClosedGroupAvatar.tsx +++ b/ts/components/AvatarPlaceHolder/ClosedGroupAvatar.tsx @@ -1,59 +1,57 @@ import React from 'react'; +import { useMembersAvatars } from '../../hooks/useMembersAvatars'; import { Avatar, AvatarSize } from '../Avatar'; -import { ConversationAvatar } from '../session/usingClosedConversationDetails'; -interface Props { +type Props = { size: number; - memberAvatars: Array; // this is added by usingClosedConversationDetails + closedGroupId: string; onAvatarClick?: () => void; -} +}; -export class ClosedGroupAvatar extends React.PureComponent { - public getClosedGroupAvatarsSize(size: AvatarSize): AvatarSize { - // Always use the size directly under the one requested - switch (size) { - case AvatarSize.S: - return AvatarSize.XS; - case AvatarSize.M: - return AvatarSize.S; - case AvatarSize.L: - return AvatarSize.M; - case AvatarSize.XL: - return AvatarSize.L; - case AvatarSize.HUGE: - return AvatarSize.XL; - default: - throw new Error(`Invalid size request for closed group avatar: ${size}`); - } - } - - public render() { - const { memberAvatars, size, onAvatarClick } = this.props; - const avatarsDiameter = this.getClosedGroupAvatarsSize(size); - - const conv1 = memberAvatars.length > 0 ? memberAvatars[0] : undefined; - const conv2 = memberAvatars.length > 1 ? memberAvatars[1] : undefined; - const name1 = conv1?.name || conv1?.id || undefined; - const name2 = conv2?.name || conv2?.id || undefined; - - // use the 2 first members as group avatars - return ( -
- - -
- ); +function getClosedGroupAvatarsSize(size: AvatarSize): AvatarSize { + // Always use the size directly under the one requested + switch (size) { + case AvatarSize.XS: + return AvatarSize.XS; + case AvatarSize.S: + return AvatarSize.XS; + case AvatarSize.M: + return AvatarSize.S; + case AvatarSize.L: + return AvatarSize.M; + case AvatarSize.XL: + return AvatarSize.L; + case AvatarSize.HUGE: + return AvatarSize.XL; + default: + throw new Error(`Invalid size request for closed group avatar: ${size}`); } } + +export const ClosedGroupAvatar = (props: Props) => { + const { closedGroupId, size, onAvatarClick } = props; + + const memberAvatars = useMembersAvatars(closedGroupId); + const avatarsDiameter = getClosedGroupAvatarsSize(size); + const firstMember = memberAvatars?.[0]; + const secondMember = memberAvatars?.[1]; + + return ( +
+ + +
+ ); +}; diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx index 58c7401ae..f8b8a37d1 100644 --- a/ts/components/ConversationListItem.tsx +++ b/ts/components/ConversationListItem.tsx @@ -9,7 +9,6 @@ import { Timestamp } from './conversation/Timestamp'; import { ContactName } from './conversation/ContactName'; import { TypingAnimation } from './conversation/TypingAnimation'; -import { ConversationAvatar } from './session/usingClosedConversationDetails'; import { MemoConversationListItemContextMenu } from './session/menu/ConversationListItemContextMenu'; import { createPortal } from 'react-dom'; import { OutgoingMessageStatus } from './conversation/message/OutgoingMessageStatus'; @@ -21,7 +20,6 @@ import { ReduxConversationType, } from '../state/ducks/conversations'; import _ from 'underscore'; -import { useMembersAvatars } from '../hooks/useMembersAvatar'; import { SessionIcon } from './session/icon'; import { useDispatch, useSelector } from 'react-redux'; import { SectionType } from '../state/ducks/section'; @@ -217,13 +215,11 @@ const MessageItem = (props: { const AvatarItem = (props: { avatarPath: string | null; conversationId: string; - memberAvatars?: Array; name?: string; profileName?: string; isPrivate: boolean; }) => { - const { avatarPath, name, isPrivate, conversationId, profileName, memberAvatars } = props; - + const { avatarPath, name, isPrivate, conversationId, profileName } = props; const userName = name || profileName || conversationId; const dispatch = useDispatch(); @@ -233,7 +229,6 @@ const AvatarItem = (props: { avatarPath={avatarPath} name={userName} size={AvatarSize.S} - memberAvatars={memberAvatars} pubkey={conversationId} onAvatarClick={() => { if (isPrivate) { @@ -278,8 +273,6 @@ const ConversationListItem = (props: Props) => { const triggerId = `conversation-item-${conversationId}-ctxmenu`; const key = `conversation-item-${conversationId}`; - const membersAvatar = useMembersAvatars(props); - const openConvo = useCallback( async (e: React.MouseEvent) => { // mousedown is invoked sooner than onClick, but for both right and left click @@ -319,7 +312,6 @@ const ConversationListItem = (props: Props) => { { const AvatarHeader = (props: { avatarPath: string | null; - memberAvatars?: Array; name?: string; pubkey: string; profileName?: string; showBackButton: boolean; onAvatarClick?: (pubkey: string) => void; }) => { - const { avatarPath, memberAvatars, name, pubkey, profileName } = props; + const { avatarPath, name, pubkey, profileName } = props; const userName = name || profileName || pubkey; return ( @@ -188,7 +184,6 @@ const AvatarHeader = (props: { props.onAvatarClick(pubkey); } }} - memberAvatars={memberAvatars} pubkey={pubkey} /> @@ -344,8 +339,6 @@ export const ConversationHeaderWithDetails = () => { const headerProps = useSelector(getConversationHeaderProps); const isSelectionMode = useSelector(isMessageSelectionMode); - const selectedConversation = useSelector(getSelectedConversation); - const memberDetails = useMembersAvatars(selectedConversation); const isMessageDetailOpened = useSelector(isMessageDetailView); const dispatch = useDispatch(); @@ -401,7 +394,6 @@ export const ConversationHeaderWithDetails = () => { pubkey={conversationKey} showBackButton={isMessageDetailOpened} avatarPath={avatarPath} - memberAvatars={memberDetails} name={name} profileName={profileName} /> diff --git a/ts/components/session/conversation/SessionRightPanel.tsx b/ts/components/session/conversation/SessionRightPanel.tsx index a0897b3ee..a2eb5d61c 100644 --- a/ts/components/session/conversation/SessionRightPanel.tsx +++ b/ts/components/session/conversation/SessionRightPanel.tsx @@ -31,7 +31,6 @@ import { getSelectedConversation, isRightPanelShowing, } from '../../../state/selectors/conversations'; -import { useMembersAvatars } from '../../../hooks/useMembersAvatar'; import { closeRightPanel } from '../../../state/ducks/conversations'; async function getMediaGalleryProps( @@ -110,7 +109,6 @@ async function getMediaGalleryProps( const HeaderItem = () => { const selectedConversation = useSelector(getSelectedConversation); const dispatch = useDispatch(); - const memberDetails = useMembersAvatars(selectedConversation); if (!selectedConversation) { return null; @@ -139,13 +137,7 @@ const HeaderItem = () => { dispatch(closeRightPanel()); }} /> - +
{showInviteContacts && ( ; // this is added by usingClosedConversationDetails -}; - -export function usingClosedConversationDetails(WrappedComponent: any) { - return class extends React.Component { - constructor(props: any) { - super(props); - this.state = { - memberAvatars: undefined, - }; - } - - public componentDidMount() { - this.fetchClosedConversationDetails(); - } - - public componentWillReceiveProps() { - this.fetchClosedConversationDetails(); - } - - public render() { - return ; - } - - private fetchClosedConversationDetails() { - const { isPublic, type, conversationType, isGroup, id } = this.props; - - if (!isPublic && (conversationType === 'group' || type === 'group' || isGroup)) { - const groupId = id; - const ourPrimary = UserUtils.getOurPubKeyFromCache(); - let members = GroupUtils.getGroupMembers(PubKey.cast(groupId)); - - const ourself = members.find(m => m.key !== ourPrimary.key); - // add ourself back at the back, so it's shown only if only 1 member and we are still a member - members = members.filter(m => m.key !== ourPrimary.key); - members.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0)); - if (ourself) { - members.push(ourPrimary); - } - // no need to forward more than 2 conversations for rendering the group avatar - members = members.slice(0, 2); - const memberConvos = _.compact(members.map(m => getConversationController().get(m.key))); - const memberAvatars = memberConvos.map(m => { - return { - avatarPath: m.getAvatarPath() || undefined, - id: m.id, - name: m.get('name') || m.get('profileName') || m.id, - }; - }); - this.setState({ memberAvatars }); - } else { - this.setState({ memberAvatars: undefined }); - } - } - }; -} diff --git a/ts/hooks/useMembersAvatar.tsx b/ts/hooks/useMembersAvatar.tsx deleted file mode 100644 index 76386fe83..000000000 --- a/ts/hooks/useMembersAvatar.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import _ from 'lodash'; -import { useEffect, useState } from 'react'; -import { getConversationController } from '../session/conversations'; -import { UserUtils } from '../session/utils'; -import { ReduxConversationType } from '../state/ducks/conversations'; - -export function useMembersAvatars(conversation: ReduxConversationType | undefined) { - const [membersAvatars, setMembersAvatars] = useState< - | Array<{ - avatarPath: string | undefined; - id: string; - name: string; - }> - | undefined - >(undefined); - - useEffect( - () => { - if (!conversation) { - setMembersAvatars(undefined); - return; - } - const { isPublic, isGroup, members: convoMembers } = conversation; - if (!isPublic && isGroup) { - const ourPrimary = UserUtils.getOurPubKeyStrFromCache(); - - const ourself = convoMembers?.find(m => m !== ourPrimary) || undefined; - // add ourself back at the back, so it's shown only if only 1 member and we are still a member - let membersFiltered = convoMembers?.filter(m => m !== ourPrimary) || []; - membersFiltered.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); - if (ourself) { - membersFiltered.push(ourPrimary); - } - // no need to forward more than 2 conversations for rendering the group avatar - membersFiltered = membersFiltered.slice(0, 2); - const memberConvos = _.compact( - membersFiltered.map(m => getConversationController().get(m)) - ); - const memberAvatars = memberConvos.map(m => { - return { - avatarPath: m.getAvatarPath() || undefined, - id: m.id as string, - name: (m.get('name') || m.get('profileName') || m.id) as string, - }; - }); - setMembersAvatars(memberAvatars); - } else { - setMembersAvatars(undefined); - } - }, - conversation ? [conversation.members, conversation.id] : [] - ); - - return membersAvatars; -} diff --git a/ts/hooks/useMembersAvatars.tsx b/ts/hooks/useMembersAvatars.tsx new file mode 100644 index 000000000..39e972a21 --- /dev/null +++ b/ts/hooks/useMembersAvatars.tsx @@ -0,0 +1,55 @@ +import { UserUtils } from '../session/utils'; +import * as _ from 'lodash'; +import { useSelector } from 'react-redux'; +import { StateType } from '../state/reducer'; + +export type ConversationAvatar = { + avatarPath?: string; + id: string; // member's pubkey + name: string; +}; + +export function useMembersAvatars(closedGroupPubkey: string | undefined) { + const ourPrimary = UserUtils.getOurPubKeyStrFromCache(); + + return useSelector((state: StateType): Array | undefined => { + if (!closedGroupPubkey) { + return undefined; + } + const groupConvo = state.conversations.conversationLookup[closedGroupPubkey]; + + if (groupConvo.isPrivate || groupConvo.isPublic || !groupConvo.isGroup) { + return undefined; + } + // this must be a closed group + const originalMembers = groupConvo.members; + if (!originalMembers || originalMembers.length === 0) { + return undefined; + } + const allMembersSorted = originalMembers.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); + + // no need to forward more than 2 conversations for rendering the group avatar + const usAtTheEndMaxTwo = _.sortBy(allMembersSorted, a => (a === ourPrimary ? 1 : 0)).slice( + 0, + 2 + ); + const memberConvos = _.compact( + usAtTheEndMaxTwo + .map(m => state.conversations.conversationLookup[m]) + .map(m => { + if (!m) { + return undefined; + } + const userName = m.name || m.profileName || m.id; + + return { + avatarPath: m.avatarPath || undefined, + id: m.id, + name: userName, + }; + }) + ); + + return memberConvos && memberConvos.length ? memberConvos : undefined; + }); +} diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index de9fc35a4..16016868a 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -325,6 +325,7 @@ export const _getConversationComparator = (testingi18n?: LocalizerType) => { return collator.compare(leftTitle, rightTitle); }; }; + export const getConversationComparator = createSelector(getIntl, _getConversationComparator); // export only because we use it in some of our tests From 894349e710d0e93ad10b988011a62c2cfc3017e5 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 23 Nov 2021 16:18:27 +1100 Subject: [PATCH 2/3] cleanup props passing of avatar and name with a custom hook --- ts/components/Avatar.tsx | 37 +++++----- .../AvatarPlaceHolder/ClosedGroupAvatar.tsx | 22 ++---- ts/components/ContactListItem.tsx | 70 ++++++++----------- ts/components/ConversationListItem.tsx | 65 +++++------------ .../conversation/ConversationHeader.tsx | 15 +--- ts/components/conversation/MessageDetail.tsx | 9 ++- .../conversation/message/MessageAvatar.tsx | 8 +-- ts/components/dialog/EditProfileDialog.tsx | 7 +- .../dialog/UpdateGroupNameDialog.tsx | 2 +- ts/components/dialog/UserDetailsDialog.tsx | 3 - ts/components/session/ActionsPanel.tsx | 19 ++--- .../session/SessionMemberListItem.tsx | 40 ++++------- .../calling/DraggableCallContainer.tsx | 10 +-- .../calling/InConversationCallContainer.tsx | 28 +------- .../session/calling/IncomingCallDialog.tsx | 10 +-- .../conversation/SessionRightPanel.tsx | 14 +--- .../menu/ConversationListItemContextMenu.tsx | 12 ++-- ts/hooks/useMembersAvatars.tsx | 19 +---- ts/hooks/useParamSelector.ts | 17 ++++- 19 files changed, 128 insertions(+), 279 deletions(-) diff --git a/ts/components/Avatar.tsx b/ts/components/Avatar.tsx index 8597d6850..6b3537155 100644 --- a/ts/components/Avatar.tsx +++ b/ts/components/Avatar.tsx @@ -5,6 +5,7 @@ import { AvatarPlaceHolder, ClosedGroupAvatar } from './AvatarPlaceHolder'; import { useEncryptedFileFetch } from '../hooks/useEncryptedFileFetch'; import _ from 'underscore'; import { useMembersAvatars } from '../hooks/useMembersAvatars'; +import { useAvatarPath, useConversationUsername } from '../hooks/useParamSelector'; export enum AvatarSize { XS = 28, @@ -16,8 +17,8 @@ export enum AvatarSize { } type Props = { - avatarPath?: string | null; - name?: string; // display name, profileName or pubkey, whatever is set first + forcedAvatarPath?: string | null; + forcedName?: string; pubkey?: string; size: AvatarSize; base64Data?: string; // if this is not empty, it will be used to render the avatar with base64 encoded data @@ -26,8 +27,8 @@ type Props = { }; const Identicon = (props: Props) => { - const { size, name, pubkey } = props; - const userName = name || '0'; + const { size, forcedName, pubkey } = props; + const userName = forcedName || '0'; return ( { ); }; -const NoImage = (props: { - name?: string; - pubkey?: string; - size: AvatarSize; - isClosedGroup: boolean; - onAvatarClick?: () => void; -}) => { - const { name, size, pubkey, isClosedGroup } = props; +const NoImage = ( + props: Pick & { + isClosedGroup: boolean; + } +) => { + const { forcedName, size, pubkey, isClosedGroup } = props; // if no image but we have conversations set for the group, renders group members avatars if (pubkey && isClosedGroup) { return ( @@ -55,7 +54,7 @@ const NoImage = (props: { ); } - return ; + return ; }; const AvatarImage = (props: { @@ -88,17 +87,21 @@ const AvatarImage = (props: { }; const AvatarInner = (props: Props) => { - const { avatarPath, base64Data, size, pubkey, name, dataTestId } = props; + const { base64Data, size, pubkey, forcedAvatarPath, forcedName, dataTestId } = props; const [imageBroken, setImageBroken] = useState(false); const closedGroupMembers = useMembersAvatars(pubkey); + + const avatarPath = useAvatarPath(pubkey); + const name = useConversationUsername(pubkey); + // contentType is not important - const { urlToLoad } = useEncryptedFileFetch(avatarPath || '', ''); + const { urlToLoad } = useEncryptedFileFetch(forcedAvatarPath || avatarPath || '', ''); const handleImageError = () => { window.log.warn( 'Avatar: Image failed to load; failing over to placeholder', urlToLoad, - avatarPath + forcedAvatarPath || avatarPath ); setImageBroken(true); }; @@ -127,7 +130,7 @@ const AvatarInner = (props: Props) => { avatarPath={urlToLoad} base64Data={base64Data} imageBroken={imageBroken} - name={name} + name={forcedName || name} handleImageError={handleImageError} /> ) : ( diff --git a/ts/components/AvatarPlaceHolder/ClosedGroupAvatar.tsx b/ts/components/AvatarPlaceHolder/ClosedGroupAvatar.tsx index be3ef30d1..ac6fa155c 100644 --- a/ts/components/AvatarPlaceHolder/ClosedGroupAvatar.tsx +++ b/ts/components/AvatarPlaceHolder/ClosedGroupAvatar.tsx @@ -11,8 +11,6 @@ type Props = { function getClosedGroupAvatarsSize(size: AvatarSize): AvatarSize { // Always use the size directly under the one requested switch (size) { - case AvatarSize.XS: - return AvatarSize.XS; case AvatarSize.S: return AvatarSize.XS; case AvatarSize.M: @@ -33,25 +31,13 @@ export const ClosedGroupAvatar = (props: Props) => { const memberAvatars = useMembersAvatars(closedGroupId); const avatarsDiameter = getClosedGroupAvatarsSize(size); - const firstMember = memberAvatars?.[0]; - const secondMember = memberAvatars?.[1]; + const firstMemberId = memberAvatars?.[0]; + const secondMemberID = memberAvatars?.[1]; return (
- - + +
); }; diff --git a/ts/components/ContactListItem.tsx b/ts/components/ContactListItem.tsx index c7deceba9..f16122dd5 100644 --- a/ts/components/ContactListItem.tsx +++ b/ts/components/ContactListItem.tsx @@ -3,55 +3,43 @@ import classNames from 'classnames'; import { Avatar, AvatarSize } from './Avatar'; import { Emojify } from './conversation/Emojify'; +import { useConversationUsername, useIsMe } from '../hooks/useParamSelector'; -interface Props { +type Props = { pubkey: string; - isMe?: boolean; - name?: string; - profileName?: string; - avatarPath?: string; onClick?: () => void; -} +}; -export class ContactListItem extends React.Component { - public renderAvatar() { - const { avatarPath, name, pubkey, profileName } = this.props; +const AvatarItem = (props: { pubkey: string }) => { + const { pubkey } = props; - const userName = name || profileName || pubkey; + return ; +}; - return ; - } +export const ContactListItem = (props: Props) => { + const { onClick, pubkey } = props; - public render() { - const { name, onClick, isMe, pubkey, profileName } = this.props; + const name = useConversationUsername(pubkey); + const isMe = useIsMe(pubkey); - const title = name ? name : pubkey; - const displayName = isMe ? window.i18n('me') : title; + const title = name ? name : pubkey; + const displayName = isMe ? window.i18n('me') : title; - const profileElement = - !isMe && profileName && !name ? ( - - ~ - - - ) : null; - - return ( -
- {this.renderAvatar()} -
-
- {profileElement} -
+ return ( +
+ +
+
+
- ); - } -} +
+ ); +}; diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx index f8b8a37d1..cce539e08 100644 --- a/ts/components/ConversationListItem.tsx +++ b/ts/components/ConversationListItem.tsx @@ -26,6 +26,7 @@ import { SectionType } from '../state/ducks/section'; import { getFocusedSection } from '../state/selectors/section'; import { ConversationNotificationSettingType } from '../models/conversation'; import { updateUserDetailsModal } from '../state/ducks/modalDialog'; +import { useAvatarPath, useConversationUsername, useIsMe } from '../hooks/useParamSelector'; // tslint:disable-next-line: no-empty-interface export interface ConversationListItemProps extends ReduxConversationType {} @@ -52,11 +53,8 @@ const Portal = ({ children }: { children: any }) => { const HeaderItem = (props: { unreadCount: number; - isMe: boolean; mentionedUs: boolean; activeAt?: number; - name?: string; - profileName?: string; conversationId: string; isPinned: boolean; currentNotificationSetting: ConversationNotificationSettingType; @@ -65,11 +63,8 @@ const HeaderItem = (props: { unreadCount, mentionedUs, activeAt, - isMe, isPinned, conversationId, - profileName, - name, currentNotificationSetting, } = props; @@ -116,12 +111,7 @@ const HeaderItem = (props: { unreadCount > 0 ? 'module-conversation-list-item__header__name--with-unread' : null )} > - +
@@ -143,21 +133,18 @@ const HeaderItem = (props: { ); }; -const UserItem = (props: { - name?: string; - profileName?: string; - isMe: boolean; - conversationId: string; -}) => { - const { name, conversationId, profileName, isMe } = props; +const UserItem = (props: { conversationId: string }) => { + const { conversationId } = props; const shortenedPubkey = PubKey.shorten(conversationId); + const isMe = useIsMe(conversationId); + const username = useConversationUsername(conversationId); - const displayedPubkey = profileName ? shortenedPubkey : conversationId; - const displayName = isMe ? window.i18n('noteToSelf') : profileName; + const displayedPubkey = username ? shortenedPubkey : conversationId; + const displayName = isMe ? window.i18n('noteToSelf') : username; let shouldShowPubkey = false; - if ((!name || name.length === 0) && (!displayName || displayName.length === 0)) { + if ((!username || username.length === 0) && (!displayName || displayName.length === 0)) { shouldShowPubkey = true; } @@ -165,7 +152,7 @@ const UserItem = (props: {
{ - const { avatarPath, name, isPrivate, conversationId, profileName } = props; - const userName = name || profileName || conversationId; +const AvatarItem = (props: { conversationId: string; isPrivate: boolean }) => { + const { isPrivate, conversationId } = props; + const userName = useConversationUsername(conversationId); + const avatarPath = useAvatarPath(conversationId); const dispatch = useDispatch(); return (
{ @@ -235,7 +215,7 @@ const AvatarItem = (props: { dispatch( updateUserDetailsModal({ conversationId: conversationId, - userName, + userName: userName || '', authorAvatarPath: avatarPath, }) ); @@ -256,9 +236,7 @@ const ConversationListItem = (props: Props) => { style, mentionedUs, isMe, - name, isPinned, - profileName, isTyping, lastMessage, hasNickname, @@ -309,23 +287,14 @@ const ConversationListItem = (props: Props) => { isBlocked ? 'module-conversation-list-item--is-blocked' : null )} > - +
{ type={type} currentNotificationSetting={currentNotificationSetting || 'all'} avatarPath={avatarPath || null} - name={name} - profileName={profileName} />
diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index 5c2c3f1d1..d9a470820 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -162,26 +162,20 @@ const ExpirationLength = (props: { expirationSettingName?: string }) => { }; const AvatarHeader = (props: { - avatarPath: string | null; - name?: string; pubkey: string; - profileName?: string; showBackButton: boolean; onAvatarClick?: (pubkey: string) => void; }) => { - const { avatarPath, name, pubkey, profileName } = props; - const userName = name || profileName || pubkey; + const { pubkey, onAvatarClick, showBackButton } = props; return ( { // do not allow right panel to appear if another button is shown on the SessionConversation - if (props.onAvatarClick && !props.showBackButton) { - props.onAvatarClick(pubkey); + if (onAvatarClick && !showBackButton) { + onAvatarClick(pubkey); } }} pubkey={pubkey} @@ -393,9 +387,6 @@ export const ConversationHeaderWithDetails = () => { }} pubkey={conversationKey} showBackButton={isMessageDetailOpened} - avatarPath={avatarPath} - name={name} - profileName={profileName} /> )} diff --git a/ts/components/conversation/MessageDetail.tsx b/ts/components/conversation/MessageDetail.tsx index 15e94b201..7080a6736 100644 --- a/ts/components/conversation/MessageDetail.tsx +++ b/ts/components/conversation/MessageDetail.tsx @@ -13,11 +13,10 @@ import { } from '../../state/selectors/conversations'; import { deleteMessagesById } from '../../interactions/conversations/unsendingInteractions'; -const AvatarItem = (props: { contact: ContactPropsMessageDetail }) => { - const { avatarPath, pubkey, name, profileName } = props.contact; - const userName = name || profileName || pubkey; +const AvatarItem = (props: { pubkey: string | undefined }) => { + const { pubkey } = props; - return ; + return ; }; const DeleteButtonItem = (props: { messageId: string; convoId: string; isDeletable: boolean }) => { @@ -68,7 +67,7 @@ const ContactItem = (props: { contact: ContactPropsMessageDetail }) => { return (
- +
{ return (
- + {isPublic && isSenderAdmin && (
diff --git a/ts/components/dialog/EditProfileDialog.tsx b/ts/components/dialog/EditProfileDialog.tsx index 4c84a81ea..045e506cb 100644 --- a/ts/components/dialog/EditProfileDialog.tsx +++ b/ts/components/dialog/EditProfileDialog.tsx @@ -246,7 +246,12 @@ export class EditProfileDialog extends React.Component<{}, State> { const userName = profileName || this.convo.id; return ( - + ); } diff --git a/ts/components/dialog/UpdateGroupNameDialog.tsx b/ts/components/dialog/UpdateGroupNameDialog.tsx index 7409ef923..e96b2e612 100644 --- a/ts/components/dialog/UpdateGroupNameDialog.tsx +++ b/ts/components/dialog/UpdateGroupNameDialog.tsx @@ -191,7 +191,7 @@ export class UpdateGroupNameDialog extends React.Component { return (
- +
{ const convo = getConversationController().get(props.conversationId); const size = isEnlargedImageShown ? AvatarSize.HUGE : AvatarSize.XL; - const userName = props.userName || props.conversationId; const [_, copyToClipboard] = useCopyToClipboard(); @@ -57,8 +56,6 @@ export const UserDetailsDialog = (props: Props) => {
{ setIsEnlargedImageShown(!isEnlargedImageShown); diff --git a/ts/components/session/ActionsPanel.tsx b/ts/components/session/ActionsPanel.tsx index 8e09ca7f9..7ad2f323b 100644 --- a/ts/components/session/ActionsPanel.tsx +++ b/ts/components/session/ActionsPanel.tsx @@ -50,11 +50,11 @@ import { DraggableCallContainer } from './calling/DraggableCallContainer'; import { IncomingCallDialog } from './calling/IncomingCallDialog'; import { CallInFullScreenContainer } from './calling/CallInFullScreenContainer'; -const Section = (props: { type: SectionType; avatarPath?: string | null }) => { +const Section = (props: { type: SectionType }) => { const ourNumber = useSelector(getOurNumber); const unreadMessageCount = useSelector(getUnreadMessageCount); const dispatch = useDispatch(); - const { type, avatarPath } = props; + const { type } = props; const focusedSection = useSelector(getFocusedSection); const isSelected = focusedSection === props.type; @@ -85,16 +85,10 @@ const Section = (props: { type: SectionType; avatarPath?: string | null }) => { }; if (type === SectionType.Profile) { - const conversation = getConversationController().get(ourNumber); - - const profile = conversation?.getLokiProfile(); - const userName = (profile && profile.displayName) || ourNumber; return ( @@ -287,12 +281,7 @@ export const ActionsPanel = () => { return () => clearTimeout(timeout); }, []); - useInterval( - () => { - cleanUpOldDecryptedMedias(); - }, - startCleanUpMedia ? cleanUpMediasInterval : null - ); + useInterval(cleanUpOldDecryptedMedias, startCleanUpMedia ? cleanUpMediasInterval : null); if (!ourPrimaryConversation) { window?.log?.warn('ActionsPanel: ourPrimaryConversation is not set'); @@ -328,7 +317,7 @@ export const ActionsPanel = () => { className="module-left-pane__sections-container" data-testid="leftpane-section-container" > -
+
diff --git a/ts/components/session/SessionMemberListItem.tsx b/ts/components/session/SessionMemberListItem.tsx index 9cf9fd0b6..f90a03f19 100644 --- a/ts/components/session/SessionMemberListItem.tsx +++ b/ts/components/session/SessionMemberListItem.tsx @@ -23,37 +23,17 @@ type Props = { isSelected: boolean; // this bool is used to make a zombie appear with less opacity than a normal member isZombie?: boolean; - onSelect?: any; - onUnselect?: any; + onSelect?: (selectedMember: ContactType) => void; + onUnselect?: (selectedMember: ContactType) => void; +}; + +const AvatarItem = (props: { memberPubkey?: string }) => { + return ; }; export const SessionMemberListItem = (props: Props) => { const { isSelected, member, isZombie, onSelect, onUnselect } = props; - const renderAvatar = () => { - const { authorAvatarPath, authorName, authorPhoneNumber, authorProfileName } = member; - const userName = authorName || authorProfileName || authorPhoneNumber; - return ( - - ); - }; - - const selectMember = () => { - onSelect?.(member); - }; - const unselectMember = () => { - onUnselect?.(member); - }; - - const handleSelectionAction = () => { - isSelected ? unselectMember() : selectMember(); - }; - const name = member.authorProfileName || PubKey.shorten(member.authorPhoneNumber); return ( @@ -64,11 +44,15 @@ export const SessionMemberListItem = (props: Props) => { isSelected && 'selected', isZombie && 'zombie' )} - onClick={handleSelectionAction} + onClick={() => { + isSelected ? onUnselect?.(member) : onSelect?.(member); + }} role="button" >
- {renderAvatar()} + + + {name}
diff --git a/ts/components/session/calling/DraggableCallContainer.tsx b/ts/components/session/calling/DraggableCallContainer.tsx index 7455da648..04b0e9194 100644 --- a/ts/components/session/calling/DraggableCallContainer.tsx +++ b/ts/components/session/calling/DraggableCallContainer.tsx @@ -9,7 +9,6 @@ import { getHasOngoingCall, getHasOngoingCallWith } from '../../../state/selecto import { openConversationWithMessages } from '../../../state/ducks/conversations'; import { Avatar, AvatarSize } from '../../Avatar'; import { useVideoCallEventsListener } from '../../../hooks/useVideoEventListener'; -import { useAvatarPath, useConversationUsername } from '../../../hooks/useParamSelector'; import { VideoLoadingSpinner } from './InConversationCallContainer'; export const DraggableCallWindow = styled.div` @@ -77,8 +76,6 @@ export const DraggableCallContainer = () => { 'DraggableCallContainer', false ); - const ongoingCallUsername = useConversationUsername(ongoingCallPubkey); - const avatarPath = useAvatarPath(ongoingCallPubkey); const videoRefRemote = useRef(null); function onWindowResize() { @@ -140,12 +137,7 @@ export const DraggableCallContainer = () => { /> {remoteStreamVideoIsMuted && ( - + )} diff --git a/ts/components/session/calling/InConversationCallContainer.tsx b/ts/components/session/calling/InConversationCallContainer.tsx index 85d0384f3..87fa01175 100644 --- a/ts/components/session/calling/InConversationCallContainer.tsx +++ b/ts/components/session/calling/InConversationCallContainer.tsx @@ -6,7 +6,6 @@ import _ from 'underscore'; import { UserUtils } from '../../../session/utils'; import { getCallIsInFullScreen, - getHasOngoingCallWith, getHasOngoingCallWithFocusedConvo, getHasOngoingCallWithFocusedConvoIsOffering, getHasOngoingCallWithFocusedConvosIsConnecting, @@ -16,11 +15,6 @@ import { StyledVideoElement } from './DraggableCallContainer'; import { Avatar, AvatarSize } from '../../Avatar'; import { useVideoCallEventsListener } from '../../../hooks/useVideoEventListener'; -import { - useAvatarPath, - useOurAvatarPath, - useOurConversationUsername, -} from '../../../hooks/useParamSelector'; import { useModuloWithTripleDots } from '../../../hooks/useModuloWithTripleDots'; import { CallWindowControls } from './CallButtons'; import { SessionSpinner } from '../SessionSpinner'; @@ -118,23 +112,15 @@ export const VideoLoadingSpinner = (props: { fullWidth: boolean }) => { // tslint:disable-next-line: max-func-body-length export const InConversationCallContainer = () => { - const ongoingCallProps = useSelector(getHasOngoingCallWith); - const isInFullScreen = useSelector(getCallIsInFullScreen); const ongoingCallPubkey = useSelector(getHasOngoingCallWithPubkey); const ongoingCallWithFocused = useSelector(getHasOngoingCallWithFocusedConvo); - const ongoingCallUsername = ongoingCallProps?.profileName || ongoingCallProps?.name; const videoRefRemote = useRef(null); const videoRefLocal = useRef(null); const ourPubkey = UserUtils.getOurPubKeyStrFromCache(); - const remoteAvatarPath = useAvatarPath(ongoingCallPubkey); - const ourAvatarPath = useOurAvatarPath(); - - const ourUsername = useOurConversationUsername(); - const { currentConnectedAudioInputs, currentConnectedCameras, @@ -190,12 +176,7 @@ export const InConversationCallContainer = () => { /> {remoteStreamVideoIsMuted && ( - + )} @@ -208,12 +189,7 @@ export const InConversationCallContainer = () => { /> {localStreamVideoIsMuted && ( - + )} diff --git a/ts/components/session/calling/IncomingCallDialog.tsx b/ts/components/session/calling/IncomingCallDialog.tsx index d022e2c26..3e6dd5500 100644 --- a/ts/components/session/calling/IncomingCallDialog.tsx +++ b/ts/components/session/calling/IncomingCallDialog.tsx @@ -3,7 +3,7 @@ import { useSelector } from 'react-redux'; import styled from 'styled-components'; import _ from 'underscore'; -import { useAvatarPath, useConversationUsername } from '../../../hooks/useParamSelector'; +import { useConversationUsername } from '../../../hooks/useParamSelector'; import { ed25519Str } from '../../../session/onions/onionPath'; import { CallManager } from '../../../session/utils'; import { getHasIncomingCall, getHasIncomingCallFrom } from '../../../state/selectors/call'; @@ -70,7 +70,6 @@ export const IncomingCallDialog = () => { } }; const from = useConversationUsername(incomingCallFromPubkey); - const incomingAvatar = useAvatarPath(incomingCallFromPubkey); if (!hasIncomingCall) { return null; } @@ -79,12 +78,7 @@ export const IncomingCallDialog = () => { return ( - +
{ if (!selectedConversation) { return null; } - const { - avatarPath, - id, - isGroup, - isKickedFromGroup, - profileName, - isBlocked, - left, - name, - } = selectedConversation; + const { id, isGroup, isKickedFromGroup, isBlocked, left } = selectedConversation; const showInviteContacts = isGroup && !isKickedFromGroup && !isBlocked && !left; - const userName = name || profileName || id; return (
@@ -137,7 +127,7 @@ const HeaderItem = () => { dispatch(closeRightPanel()); }} /> - +
{showInviteContacts && ( isKickedFromGroup, currentNotificationSetting, isPrivate, - name, - profileName, - avatarPath, } = props; const isGroup = type === 'group'; - const userName = name || profileName || conversationId; + + const userName = useConversationUsername(conversationId); + const avatarPath = useAvatarPath(conversationId); return ( @@ -80,7 +78,7 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) => {getInviteContactMenuItem(isGroup, isPublic, conversationId)} {getDeleteContactMenuItem(isGroup, isPublic, left, isKickedFromGroup, conversationId)} {getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, conversationId)} - {getShowUserDetailsMenuItem(isPrivate, conversationId, avatarPath, userName)} + {getShowUserDetailsMenuItem(isPrivate, conversationId, avatarPath, userName || '')} ); }; diff --git a/ts/hooks/useMembersAvatars.tsx b/ts/hooks/useMembersAvatars.tsx index 39e972a21..8f80c6616 100644 --- a/ts/hooks/useMembersAvatars.tsx +++ b/ts/hooks/useMembersAvatars.tsx @@ -3,16 +3,10 @@ import * as _ from 'lodash'; import { useSelector } from 'react-redux'; import { StateType } from '../state/reducer'; -export type ConversationAvatar = { - avatarPath?: string; - id: string; // member's pubkey - name: string; -}; - export function useMembersAvatars(closedGroupPubkey: string | undefined) { const ourPrimary = UserUtils.getOurPubKeyStrFromCache(); - return useSelector((state: StateType): Array | undefined => { + return useSelector((state: StateType): Array | undefined => { if (!closedGroupPubkey) { return undefined; } @@ -37,16 +31,7 @@ export function useMembersAvatars(closedGroupPubkey: string | undefined) { usAtTheEndMaxTwo .map(m => state.conversations.conversationLookup[m]) .map(m => { - if (!m) { - return undefined; - } - const userName = m.name || m.profileName || m.id; - - return { - avatarPath: m.avatarPath || undefined, - id: m.id, - name: userName, - }; + return m?.id || undefined; }) ); diff --git a/ts/hooks/useParamSelector.ts b/ts/hooks/useParamSelector.ts index 595727dc9..e9ab569d4 100644 --- a/ts/hooks/useParamSelector.ts +++ b/ts/hooks/useParamSelector.ts @@ -5,9 +5,9 @@ import { StateType } from '../state/reducer'; export function useAvatarPath(pubkey: string | undefined) { return useSelector((state: StateType) => { if (!pubkey) { - return undefined; + return null; } - return state.conversations.conversationLookup[pubkey]?.avatarPath; + return state.conversations.conversationLookup[pubkey]?.avatarPath || null; }); } @@ -15,12 +15,19 @@ export function useOurAvatarPath() { return useAvatarPath(UserUtils.getOurPubKeyStrFromCache()); } -export function useConversationUsername(pubkey: string | undefined) { +/** + * + * @returns convo.profileName || convo.name || convo.id or undefined if the convo is not found + */ +export function useConversationUsername(pubkey?: string) { return useSelector((state: StateType) => { if (!pubkey) { return undefined; } const convo = state.conversations.conversationLookup[pubkey]; + if (!convo) { + return pubkey; + } return convo?.profileName || convo?.name || convo.id; }); } @@ -28,3 +35,7 @@ export function useConversationUsername(pubkey: string | undefined) { export function useOurConversationUsername() { return useConversationUsername(UserUtils.getOurPubKeyStrFromCache()); } + +export function useIsMe(pubkey?: string) { + return pubkey && pubkey === UserUtils.getOurPubKeyStrFromCache(); +} From faeb6e206ad1274d711122aa8bcd5c1b0f5098f5 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 23 Nov 2021 16:18:52 +1100 Subject: [PATCH 3/3] fix a bug releasing the decrypted attachment blobs too early --- ts/session/crypto/DecryptedAttachmentsManager.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/ts/session/crypto/DecryptedAttachmentsManager.ts b/ts/session/crypto/DecryptedAttachmentsManager.ts index 5e578d291..54b22b390 100644 --- a/ts/session/crypto/DecryptedAttachmentsManager.ts +++ b/ts/session/crypto/DecryptedAttachmentsManager.ts @@ -32,8 +32,6 @@ export const cleanUpOldDecryptedMedias = () => { countKept++; } } - urlToDecryptedBlobMap.clear(); - urlToDecryptingPromise.clear(); window?.log?.info(`Clean medias blobs: cleaned/kept: ${countCleaned}:${countKept}`); };