Merge pull request #2045 from Bilb/fix-bug-release-blob-decrypted

Fix bug release blob decrypted
This commit is contained in:
Audric Ackermann 2021-11-26 10:28:49 +11:00 committed by GitHub
commit 1c27ceb2d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 205 additions and 476 deletions

View File

@ -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();

View File

@ -2,9 +2,10 @@ 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';
import { useAvatarPath, useConversationUsername } from '../hooks/useParamSelector';
export enum AvatarSize {
XS = 28,
@ -16,19 +17,18 @@ 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
memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails
onAvatarClick?: () => void;
dataTestId?: string;
};
const Identicon = (props: Props) => {
const { size, name, pubkey } = props;
const userName = name || '0';
const { size, forcedName, pubkey } = props;
const userName = forcedName || '0';
return (
<AvatarPlaceHolder
@ -41,26 +41,20 @@ const Identicon = (props: Props) => {
);
};
const NoImage = (props: {
memberAvatars?: Array<ConversationAvatar>;
name?: string;
pubkey?: string;
size: AvatarSize;
onAvatarClick?: () => void;
}) => {
const { name, memberAvatars, size, pubkey } = props;
const NoImage = (
props: Pick<Props, 'forcedName' | 'size' | 'pubkey' | 'onAvatarClick'> & {
isClosedGroup: boolean;
}
) => {
const { forcedName, 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 (
<ClosedGroupAvatar
size={size}
memberAvatars={memberAvatars}
onAvatarClick={props.onAvatarClick}
/>
<ClosedGroupAvatar size={size} closedGroupId={pubkey} onAvatarClick={props.onAvatarClick} />
);
}
return <Identicon size={size} name={name} pubkey={pubkey} />;
return <Identicon size={size} forcedName={forcedName} pubkey={pubkey} />;
};
const AvatarImage = (props: {
@ -93,20 +87,26 @@ const AvatarImage = (props: {
};
const AvatarInner = (props: Props) => {
const { avatarPath, base64Data, size, memberAvatars, 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);
};
const isClosedGroupAvatar = Boolean(memberAvatars?.length);
const isClosedGroupAvatar = Boolean(closedGroupMembers?.length);
const hasImage = (base64Data || urlToLoad) && !imageBroken && !isClosedGroupAvatar;
const isClickable = !!props.onAvatarClick;
@ -130,11 +130,11 @@ const AvatarInner = (props: Props) => {
avatarPath={urlToLoad}
base64Data={base64Data}
imageBroken={imageBroken}
name={name}
name={forcedName || name}
handleImageError={handleImageError}
/>
) : (
<NoImage {...props} />
<NoImage {...props} isClosedGroup={isClosedGroupAvatar} />
)}
</div>
);

View File

@ -1,59 +1,43 @@
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<ConversationAvatar>; // this is added by usingClosedConversationDetails
closedGroupId: string;
onAvatarClick?: () => void;
}
};
export class ClosedGroupAvatar extends React.PureComponent<Props> {
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 (
<div className="module-avatar__icon-closed">
<Avatar
avatarPath={conv1?.avatarPath}
name={name1}
size={avatarsDiameter}
pubkey={conv1?.id}
onAvatarClick={onAvatarClick}
/>
<Avatar
avatarPath={conv2?.avatarPath}
name={name2}
size={avatarsDiameter}
pubkey={conv2?.id}
onAvatarClick={onAvatarClick}
/>
</div>
);
function 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}`);
}
}
export const ClosedGroupAvatar = (props: Props) => {
const { closedGroupId, size, onAvatarClick } = props;
const memberAvatars = useMembersAvatars(closedGroupId);
const avatarsDiameter = getClosedGroupAvatarsSize(size);
const firstMemberId = memberAvatars?.[0];
const secondMemberID = memberAvatars?.[1];
return (
<div className="module-avatar__icon-closed">
<Avatar size={avatarsDiameter} pubkey={firstMemberId} onAvatarClick={onAvatarClick} />
<Avatar size={avatarsDiameter} pubkey={secondMemberID} onAvatarClick={onAvatarClick} />
</div>
);
};

View File

@ -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<Props> {
public renderAvatar() {
const { avatarPath, name, pubkey, profileName } = this.props;
const AvatarItem = (props: { pubkey: string }) => {
const { pubkey } = props;
const userName = name || profileName || pubkey;
return <Avatar size={AvatarSize.S} pubkey={pubkey} />;
};
return <Avatar avatarPath={avatarPath} name={userName} size={AvatarSize.S} pubkey={pubkey} />;
}
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 ? (
<span className="module-contact-list-item__text__profile-name">
~
<Emojify text={profileName} key={`emojify-list-item-${pubkey}`} />
</span>
) : null;
return (
<div
role="button"
onClick={onClick}
className={classNames(
'module-contact-list-item',
onClick ? 'module-contact-list-item--with-click-handler' : null
)}
>
{this.renderAvatar()}
<div className="module-contact-list-item__text">
<div className="module-contact-list-item__text__name">
<Emojify text={displayName} /> {profileElement}
</div>
return (
<div
role="button"
onClick={onClick}
className={classNames(
'module-contact-list-item',
onClick ? 'module-contact-list-item--with-click-handler' : null
)}
>
<AvatarItem pubkey={pubkey} />
<div className="module-contact-list-item__text">
<div className="module-contact-list-item__text__name">
<Emojify text={displayName} />
</div>
</div>
);
}
}
</div>
);
};

View File

@ -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,13 +20,13 @@ 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';
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 {}
@ -54,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;
@ -67,11 +63,8 @@ const HeaderItem = (props: {
unreadCount,
mentionedUs,
activeAt,
isMe,
isPinned,
conversationId,
profileName,
name,
currentNotificationSetting,
} = props;
@ -118,12 +111,7 @@ const HeaderItem = (props: {
unreadCount > 0 ? 'module-conversation-list-item__header__name--with-unread' : null
)}
>
<UserItem
isMe={isMe}
conversationId={conversationId}
name={name}
profileName={profileName}
/>
<UserItem conversationId={conversationId} />
</div>
<StyledConversationListItemIconWrapper>
@ -145,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;
}
@ -167,7 +152,7 @@ const UserItem = (props: {
<div className="module-conversation__user">
<ContactName
pubkey={displayedPubkey}
name={name}
name={username}
profileName={displayName}
module="module-conversation__user"
boldProfileName={true}
@ -214,33 +199,23 @@ const MessageItem = (props: {
);
};
const AvatarItem = (props: {
avatarPath: string | null;
conversationId: string;
memberAvatars?: Array<ConversationAvatar>;
name?: string;
profileName?: string;
isPrivate: boolean;
}) => {
const { avatarPath, name, isPrivate, conversationId, profileName, memberAvatars } = 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 (
<div className="module-conversation-list-item__avatar-container">
<Avatar
avatarPath={avatarPath}
name={userName}
size={AvatarSize.S}
memberAvatars={memberAvatars}
pubkey={conversationId}
onAvatarClick={() => {
if (isPrivate) {
dispatch(
updateUserDetailsModal({
conversationId: conversationId,
userName,
userName: userName || '',
authorAvatarPath: avatarPath,
})
);
@ -261,9 +236,7 @@ const ConversationListItem = (props: Props) => {
style,
mentionedUs,
isMe,
name,
isPinned,
profileName,
isTyping,
lastMessage,
hasNickname,
@ -278,8 +251,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<HTMLDivElement>) => {
// mousedown is invoked sooner than onClick, but for both right and left click
@ -316,24 +287,14 @@ const ConversationListItem = (props: Props) => {
isBlocked ? 'module-conversation-list-item--is-blocked' : null
)}
>
<AvatarItem
conversationId={conversationId}
avatarPath={avatarPath || null}
memberAvatars={membersAvatar}
profileName={profileName}
name={name}
isPrivate={isPrivate || false}
/>
<AvatarItem conversationId={conversationId} isPrivate={isPrivate || false} />
<div className="module-conversation-list-item__content">
<HeaderItem
mentionedUs={!!mentionedUs}
unreadCount={unreadCount || 0}
activeAt={activeAt}
isMe={!!isMe}
isPinned={!!isPinned}
conversationId={conversationId}
name={name}
profileName={profileName}
currentNotificationSetting={currentNotificationSetting || 'all'}
/>
<MessageItem
@ -357,8 +318,6 @@ const ConversationListItem = (props: Props) => {
type={type}
currentNotificationSetting={currentNotificationSetting || 'all'}
avatarPath={avatarPath || null}
name={name}
profileName={profileName}
/>
</Portal>
</div>

View File

@ -5,7 +5,6 @@ import { Avatar, AvatarSize } from '../Avatar';
import { SessionIconButton } from '../session/icon';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton';
import { ConversationAvatar } from '../session/usingClosedConversationDetails';
import { MemoConversationHeaderMenu } from '../session/menu/ConversationHeaderMenu';
import { contextMenu } from 'react-contexify';
import styled from 'styled-components';
@ -16,7 +15,6 @@ import {
getCurrentNotificationSettingText,
getIsSelectedNoteToSelf,
getIsSelectedPrivate,
getSelectedConversation,
getSelectedConversationIsPublic,
getSelectedConversationKey,
getSelectedMessageIds,
@ -25,7 +23,6 @@ import {
isRightPanelShowing,
} from '../../state/selectors/conversations';
import { useDispatch, useSelector } from 'react-redux';
import { useMembersAvatars } from '../../hooks/useMembersAvatar';
import {
deleteMessagesById,
@ -165,30 +162,22 @@ const ExpirationLength = (props: { expirationSettingName?: string }) => {
};
const AvatarHeader = (props: {
avatarPath: string | null;
memberAvatars?: Array<ConversationAvatar>;
name?: string;
pubkey: string;
profileName?: string;
showBackButton: boolean;
onAvatarClick?: (pubkey: string) => void;
}) => {
const { avatarPath, memberAvatars, name, pubkey, profileName } = props;
const userName = name || profileName || pubkey;
const { pubkey, onAvatarClick, showBackButton } = props;
return (
<span className="module-conversation-header__avatar">
<Avatar
avatarPath={avatarPath}
name={userName}
size={AvatarSize.S}
onAvatarClick={() => {
// 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);
}
}}
memberAvatars={memberAvatars}
pubkey={pubkey}
/>
</span>
@ -344,8 +333,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();
@ -400,10 +387,6 @@ export const ConversationHeaderWithDetails = () => {
}}
pubkey={conversationKey}
showBackButton={isMessageDetailOpened}
avatarPath={avatarPath}
memberAvatars={memberDetails}
name={name}
profileName={profileName}
/>
</>
)}

View File

@ -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 <Avatar avatarPath={avatarPath} name={userName} size={AvatarSize.S} pubkey={pubkey} />;
return <Avatar size={AvatarSize.S} pubkey={pubkey} />;
};
const DeleteButtonItem = (props: { messageId: string; convoId: string; isDeletable: boolean }) => {
@ -68,7 +67,7 @@ const ContactItem = (props: { contact: ContactPropsMessageDetail }) => {
return (
<div key={contact.pubkey} className="module-message-detail__contact">
<AvatarItem contact={contact} />
<AvatarItem pubkey={contact.pubkey} />
<div className="module-message-detail__contact__text">
<div className="module-message-detail__contact__name">
<ContactName

View File

@ -62,13 +62,7 @@ export const MessageAvatar = (props: Props) => {
return (
<div className="module-message__author-avatar" key={`msg-avatar-${authorPhoneNumber}`}>
<Avatar
avatarPath={authorAvatarPath}
name={userName}
size={AvatarSize.S}
onAvatarClick={onMessageAvatarClick}
pubkey={authorPhoneNumber}
/>
<Avatar size={AvatarSize.S} onAvatarClick={onMessageAvatarClick} pubkey={authorPhoneNumber} />
{isPublic && isSenderAdmin && (
<div className="module-avatar__icon--crown-wrapper">
<div className="module-avatar__icon--crown" />

View File

@ -246,7 +246,12 @@ export class EditProfileDialog extends React.Component<{}, State> {
const userName = profileName || this.convo.id;
return (
<Avatar avatarPath={avatar} name={userName} size={AvatarSize.XL} pubkey={this.convo.id} />
<Avatar
forcedAvatarPath={avatar}
forcedName={userName}
size={AvatarSize.XL}
pubkey={this.convo.id}
/>
);
}

View File

@ -191,7 +191,7 @@ export class UpdateGroupNameDialog extends React.Component<Props, State> {
return (
<div className="avatar-center">
<div className="avatar-center-inner">
<Avatar avatarPath={this.state.avatar || ''} size={AvatarSize.XL} pubkey={pubkey} />
<Avatar forcedAvatarPath={this.state.avatar || ''} size={AvatarSize.XL} pubkey={pubkey} />
<div
className="image-upload-section"
role="button"

View File

@ -25,7 +25,6 @@ export const UserDetailsDialog = (props: Props) => {
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) => {
<div className="avatar-center">
<div className="avatar-center-inner">
<Avatar
avatarPath={props.authorAvatarPath}
name={userName}
size={size}
onAvatarClick={() => {
setIsEnlargedImageShown(!isEnlargedImageShown);

View File

@ -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 (
<Avatar
avatarPath={avatarPath}
size={AvatarSize.XS}
onAvatarClick={handleClick}
name={userName}
pubkey={ourNumber}
dataTestId="leftpane-primary-avatar"
/>
@ -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"
>
<Section type={SectionType.Profile} avatarPath={ourPrimaryConversation.avatarPath} />
<Section type={SectionType.Profile} />
<Section type={SectionType.Message} />
<Section type={SectionType.Contact} />
<Section type={SectionType.Settings} />

View File

@ -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 <Avatar size={AvatarSize.XS} pubkey={props.memberPubkey} />;
};
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 (
<Avatar
avatarPath={authorAvatarPath}
name={userName}
size={AvatarSize.XS}
pubkey={authorPhoneNumber}
/>
);
};
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"
>
<div className="session-member-item__info">
<span className="session-member-item__avatar">{renderAvatar()}</span>
<span className="session-member-item__avatar">
<AvatarItem memberPubkey={member.id} />
</span>
<span className="session-member-item__name">{name}</span>
</div>
<span className={classNames('session-member-item__checkmark', isSelected && 'selected')}>

View File

@ -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<HTMLVideoElement>(null);
function onWindowResize() {
@ -140,12 +137,7 @@ export const DraggableCallContainer = () => {
/>
{remoteStreamVideoIsMuted && (
<CenteredAvatarInDraggable>
<Avatar
size={AvatarSize.XL}
avatarPath={avatarPath}
name={ongoingCallUsername}
pubkey={ongoingCallPubkey}
/>
<Avatar size={AvatarSize.XL} pubkey={ongoingCallPubkey} />
</CenteredAvatarInDraggable>
)}
</DraggableCallWindowInner>

View File

@ -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<HTMLVideoElement>(null);
const videoRefLocal = useRef<HTMLVideoElement>(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 && (
<CenteredAvatarInConversation>
<Avatar
size={AvatarSize.XL}
avatarPath={remoteAvatarPath}
name={ongoingCallUsername}
pubkey={ongoingCallPubkey}
/>
<Avatar size={AvatarSize.XL} pubkey={ongoingCallPubkey} />
</CenteredAvatarInConversation>
)}
</VideoContainer>
@ -208,12 +189,7 @@ export const InConversationCallContainer = () => {
/>
{localStreamVideoIsMuted && (
<CenteredAvatarInConversation>
<Avatar
size={AvatarSize.XL}
avatarPath={ourAvatarPath}
name={ourUsername}
pubkey={ourPubkey}
/>
<Avatar size={AvatarSize.XL} pubkey={ourPubkey} />
</CenteredAvatarInConversation>
)}
</VideoContainer>

View File

@ -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 (
<SessionWrapperModal title={window.i18n('incomingCallFrom', from)}>
<IncomingCallAvatatContainer>
<Avatar
size={AvatarSize.XL}
avatarPath={incomingAvatar}
name={from}
pubkey={incomingCallFromPubkey}
/>
<Avatar size={AvatarSize.XL} pubkey={incomingCallFromPubkey} />
</IncomingCallAvatatContainer>
<div className="session-modal__button-group">
<SessionButton

View File

@ -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,24 +109,13 @@ async function getMediaGalleryProps(
const HeaderItem = () => {
const selectedConversation = useSelector(getSelectedConversation);
const dispatch = useDispatch();
const memberDetails = useMembersAvatars(selectedConversation);
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 (
<div className="group-settings-header">
@ -139,13 +127,7 @@ const HeaderItem = () => {
dispatch(closeRightPanel());
}}
/>
<Avatar
avatarPath={avatarPath || ''}
name={userName}
size={AvatarSize.XL}
memberAvatars={memberDetails}
pubkey={id}
/>
<Avatar size={AvatarSize.XL} pubkey={id} />
<div className="invite-friends-container">
{showInviteContacts && (
<SessionIconButton

View File

@ -1,6 +1,7 @@
import React from 'react';
import { animation, Menu } from 'react-contexify';
import _ from 'underscore';
import { useAvatarPath, useConversationUsername } from '../../../hooks/useParamSelector';
import {
ConversationNotificationSettingType,
ConversationTypeEnum,
@ -34,8 +35,6 @@ export type PropsContextConversationItem = {
left: boolean;
theme?: any;
currentNotificationSetting: ConversationNotificationSettingType;
name: string | undefined;
profileName: string | undefined;
avatarPath: string | null;
};
@ -52,13 +51,12 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) =>
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 (
<Menu id={triggerId} animation={animation.fade}>
@ -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 || '')}
</Menu>
);
};

View File

@ -1,69 +0,0 @@
import { GroupUtils, UserUtils } from '../../session/utils';
import { PubKey } from '../../session/types';
import React from 'react';
import * as _ from 'lodash';
import { getConversationController } from '../../session/conversations';
export type ConversationAvatar = {
avatarPath?: string;
id?: string; // member's pubkey
name?: string;
};
type State = {
memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails
};
export function usingClosedConversationDetails(WrappedComponent: any) {
return class extends React.Component<any, State> {
constructor(props: any) {
super(props);
this.state = {
memberAvatars: undefined,
};
}
public componentDidMount() {
this.fetchClosedConversationDetails();
}
public componentWillReceiveProps() {
this.fetchClosedConversationDetails();
}
public render() {
return <WrappedComponent memberAvatars={this.state.memberAvatars} {...this.props} />;
}
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 });
}
}
};
}

View File

@ -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;
}

View File

@ -0,0 +1,40 @@
import { UserUtils } from '../session/utils';
import * as _ from 'lodash';
import { useSelector } from 'react-redux';
import { StateType } from '../state/reducer';
export function useMembersAvatars(closedGroupPubkey: string | undefined) {
const ourPrimary = UserUtils.getOurPubKeyStrFromCache();
return useSelector((state: StateType): Array<string> | 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 => {
return m?.id || undefined;
})
);
return memberConvos && memberConvos.length ? memberConvos : undefined;
});
}

View File

@ -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();
}

View File

@ -32,8 +32,6 @@ export const cleanUpOldDecryptedMedias = () => {
countKept++;
}
}
urlToDecryptedBlobMap.clear();
urlToDecryptingPromise.clear();
window?.log?.info(`Clean medias blobs: cleaned/kept: ${countCleaned}:${countKept}`);
};

View File

@ -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