Merge branch 'unstable' into userconfig_disappearingmessage

pull/2784/head
Audric Ackermann 2 years ago
commit 959c494cfb

@ -1026,6 +1026,9 @@
} }
// Module H5AudioPlayer // Module H5AudioPlayer
$rhap_background-color: var(--transparent-color) !default;
$rhap_font-family: inherit !default;
.module-message__container--outgoing { .module-message__container--outgoing {
.rhap_container { .rhap_container {
background-color: var(--message-bubbles-sent-background-color); background-color: var(--message-bubbles-sent-background-color);

@ -254,135 +254,3 @@
} }
} }
} }
/* ************ */
/* AUDIO PLAYER */
/* ************ */
$rhap_background-color: var(--transparent-color) !default;
$rhap_font-family: inherit !default;
.rhap_container,
.rhap_container button,
.rhap_progress-container {
outline: none;
}
.rhap_progress-container {
margin: 0 0 0 calc(10px + 1%);
}
.rhap_container {
min-width: 220px;
padding: 0px;
background-color: transparent;
box-shadow: none;
padding: var(--padding-message-content);
border-radius: var(--border-radius-message-box);
svg {
transition: fill var(--default-duration);
}
}
.rhap_total-time {
display: none;
}
.rhap_current-time {
margin: 0 5px 0 4px;
flex-shrink: 0;
}
.rhap_play-pause-button {
display: flex;
justify-content: center;
align-items: center;
}
.rhap_volume-bar {
display: none;
}
.rhap_volume-button {
.module-message__container--incoming & {
color: var(--message-bubbles-received-text-color);
}
.module-message__container--outgoing & {
color: var(--message-bubbles-sent-text-color);
}
}
.rhap_volume-container div[role='progressbar'] {
display: none;
}
.rhap_time {
.module-message__container--incoming & {
color: var(--message-bubbles-received-text-color);
}
.module-message__container--outgoing & {
color: var(--message-bubbles-sent-text-color);
}
font-size: 12px;
}
.rhap_progress-bar {
box-sizing: border-box;
position: relative;
z-index: 0;
width: 100%;
height: 5px;
border-radius: 2px;
}
.rhap_progress-filled {
padding-left: 5px;
}
.rhap_download-progress {
height: 100%;
position: absolute;
z-index: 1;
border-radius: 2px;
}
.rhap_progress-indicator {
z-index: 3;
width: 15px;
height: 15px;
top: -5px;
margin-left: -10px;
box-shadow: rgba(0, 0, 0, 0.5) 0 0 5px !important;
}
.rhap_controls-section {
display: flex;
justify-content: space-between;
align-items: center;
}
.rhap_additional-controls {
display: none;
}
.rhap_play-pause-button {
width: unset;
height: unset;
}
.rhap_controls-section {
flex: unset;
justify-content: flex-start;
}
.rhap_volume-button {
font-size: 20px;
width: 20px;
height: 20px;
margin-right: 0px;
}
/* **************** */
/* END AUDIO PLAYER */
/* **************** */

@ -66,21 +66,21 @@ export const CrownIcon = () => {
); );
}; };
const NoImage = ( const NoImage = React.memo(
props: Pick<Props, 'forcedName' | 'size' | 'pubkey' | 'onAvatarClick'> & { (
isClosedGroup: boolean; 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 const { forcedName, size, pubkey, isClosedGroup, onAvatarClick } = props;
if (pubkey && isClosedGroup) { // if no image but we have conversations set for the group, renders group members avatars
return ( if (pubkey && isClosedGroup) {
<ClosedGroupAvatar size={size} closedGroupId={pubkey} onAvatarClick={props.onAvatarClick} /> return <ClosedGroupAvatar size={size} convoId={pubkey} onAvatarClick={onAvatarClick} />;
); }
return <Identicon size={size} forcedName={forcedName} pubkey={pubkey} />;
} }
);
return <Identicon size={size} forcedName={forcedName} pubkey={pubkey} />;
};
const AvatarImage = ( const AvatarImage = (
props: Pick<Props, 'base64Data' | 'dataTestId'> & { props: Pick<Props, 'base64Data' | 'dataTestId'> & {
@ -111,12 +111,20 @@ const AvatarImage = (
}; };
const AvatarInner = (props: Props) => { const AvatarInner = (props: Props) => {
const { base64Data, size, pubkey, forcedAvatarPath, forcedName, dataTestId } = props; const {
base64Data,
size,
pubkey,
forcedAvatarPath,
forcedName,
dataTestId,
onAvatarClick,
} = props;
const [imageBroken, setImageBroken] = useState(false); const [imageBroken, setImageBroken] = useState(false);
const isSelectingMessages = useSelector(isMessageSelectionMode); const isSelectingMessages = useSelector(isMessageSelectionMode);
const isClosedGroupAvatar = useIsClosedGroup(pubkey); const isClosedGroup = useIsClosedGroup(pubkey);
const avatarPath = useAvatarPath(pubkey); const avatarPath = useAvatarPath(pubkey);
const name = useConversationUsername(pubkey); const name = useConversationUsername(pubkey);
// contentType is not important // contentType is not important
@ -130,9 +138,9 @@ const AvatarInner = (props: Props) => {
setImageBroken(true); setImageBroken(true);
}; };
const hasImage = (base64Data || urlToLoad) && !imageBroken && !isClosedGroupAvatar; const hasImage = (base64Data || urlToLoad) && !imageBroken && !isClosedGroup;
const isClickable = !!props.onAvatarClick; const isClickable = !!onAvatarClick;
return ( return (
<div <div
className={classNames( className={classNames(
@ -167,7 +175,13 @@ const AvatarInner = (props: Props) => {
dataTestId={dataTestId ? `img-${dataTestId}` : undefined} dataTestId={dataTestId ? `img-${dataTestId}` : undefined}
/> />
) : ( ) : (
<NoImage {...props} isClosedGroup={isClosedGroupAvatar} /> <NoImage
pubkey={pubkey}
isClosedGroup={isClosedGroup}
size={size}
forcedName={forcedName}
onAvatarClick={onAvatarClick}
/>
)} )}
</div> </div>
); );

@ -1,6 +1,8 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { COLORS } from '../../../themes/constants/colors'; import { COLORS } from '../../../themes/constants/colors';
import { getInitials } from '../../../util/getInitials'; import { getInitials } from '../../../util/getInitials';
import { allowOnlyOneAtATime } from '../../../session/utils/Promise';
import { MemberAvatarPlaceHolder } from '../../icon/MemberAvatarPlaceHolder';
type Props = { type Props = {
diameter: number; diameter: number;
@ -8,13 +10,15 @@ type Props = {
pubkey: string; pubkey: string;
}; };
const sha512FromPubkey = async (pubkey: string): Promise<string> => { const sha512FromPubkeyOneAtAtime = async (pubkey: string) => {
const buf = await crypto.subtle.digest('SHA-512', new TextEncoder().encode(pubkey)); return allowOnlyOneAtATime(`sha512FromPubkey-${pubkey}`, async () => {
const buf = await crypto.subtle.digest('SHA-512', new TextEncoder().encode(pubkey));
// tslint:disable: prefer-template restrict-plus-operands // tslint:disable: prefer-template restrict-plus-operands
return Array.prototype.map return Array.prototype.map
.call(new Uint8Array(buf), (x: any) => ('00' + x.toString(16)).slice(-2)) .call(new Uint8Array(buf), (x: any) => ('00' + x.toString(16)).slice(-2))
.join(''); .join('');
});
}; };
// do not do this on every avatar, just cache the values so we can reuse them across the app // do not do this on every avatar, just cache the values so we can reuse them across the app
@ -46,7 +50,8 @@ function useHashBasedOnPubkey(pubkey: string) {
} }
return; return;
} }
void sha512FromPubkey(pubkey).then(sha => {
void sha512FromPubkeyOneAtAtime(pubkey).then(sha => {
if (isInProgress) { if (isInProgress) {
setIsLoading(false); setIsLoading(false);
// Generate the seed simulate the .hashCode as Java // Generate the seed simulate the .hashCode as Java
@ -79,22 +84,8 @@ export const AvatarPlaceHolder = (props: Props) => {
const rWithoutBorder = diameterWithoutBorder / 2; const rWithoutBorder = diameterWithoutBorder / 2;
if (loading || !hash) { if (loading || !hash) {
// return grey circle // return avatar placeholder circle
return ( return <MemberAvatarPlaceHolder />;
<svg viewBox={viewBox}>
<g id="UrTavla">
<circle
cx={r}
cy={r}
r={rWithoutBorder}
fill="#d2d2d3"
shapeRendering="geometricPrecision"
stroke={'var(--avatar-border-color)'}
strokeWidth="1"
/>
</g>
</svg>
);
} }
const initials = getInitials(name); const initials = getInitials(name);

@ -1,13 +1,9 @@
import React from 'react'; import React from 'react';
import { useMembersAvatars } from '../../../hooks/useMembersAvatars';
import { assertUnreachable } from '../../../types/sqlSharedTypes'; import { assertUnreachable } from '../../../types/sqlSharedTypes';
import { Avatar, AvatarSize } from '../Avatar'; import { Avatar, AvatarSize } from '../Avatar';
import { isEmpty } from 'lodash';
type Props = { import { useIsClosedGroup, useSortedGroupMembers } from '../../../hooks/useParamSelector';
size: number; import { UserUtils } from '../../../session/utils';
closedGroupId: string;
onAvatarClick?: () => void;
};
function getClosedGroupAvatarsSize(size: AvatarSize): AvatarSize { function getClosedGroupAvatarsSize(size: AvatarSize): AvatarSize {
// Always use the size directly under the one requested // Always use the size directly under the one requested
@ -29,18 +25,60 @@ function getClosedGroupAvatarsSize(size: AvatarSize): AvatarSize {
} }
} }
export const ClosedGroupAvatar = (props: Props) => { /**
const { closedGroupId, size, onAvatarClick } = props; * Move our pubkey at the end of the list if we are in the list of members.
* We do this, as we want to
* - show 2 other members when there are enough of them,
* - show us as the 2nd member when there are only 2 members
* - show us first with a grey avatar as second when there are only us in the group.
*/
function moveUsAtTheEnd(members: Array<string>, us: string) {
const usAt = members.findIndex(val => val === us);
if (us && usAt > -1) {
// we need to move us at the end of the array
const updated = members.filter(m => m !== us);
updated.push(us);
return updated;
}
return members;
}
function sortAndSlice(sortedMembers: Array<string>, us: string) {
const usAtTheEndIfNeeded = moveUsAtTheEnd(sortedMembers, us); // make sure we are not one of the first 2 members if there is enough members
// we render at most 2 avatars for closed groups
return { firstMember: usAtTheEndIfNeeded?.[0], secondMember: usAtTheEndIfNeeded?.[1] };
}
const memberAvatars = useMembersAvatars(closedGroupId); function useGroupMembersAvatars(convoId: string | undefined) {
const us = UserUtils.getOurPubKeyStrFromCache();
const isClosedGroup = useIsClosedGroup(convoId);
const sortedMembers = useSortedGroupMembers(convoId);
if (!convoId || !isClosedGroup || isEmpty(sortedMembers)) {
return undefined;
}
return sortAndSlice(sortedMembers, us);
}
export const ClosedGroupAvatar = ({
convoId,
size,
onAvatarClick,
}: {
size: number;
convoId: string;
onAvatarClick?: () => void;
}) => {
const memberAvatars = useGroupMembersAvatars(convoId);
const avatarsDiameter = getClosedGroupAvatarsSize(size); const avatarsDiameter = getClosedGroupAvatarsSize(size);
const firstMemberId = memberAvatars?.[0]; const firstMemberId = memberAvatars?.firstMember || '';
const secondMemberID = memberAvatars?.[1]; const secondMemberID = memberAvatars?.secondMember || '';
return ( return (
<div className="module-avatar__icon-closed"> <div className="module-avatar__icon-closed">
<Avatar size={avatarsDiameter} pubkey={firstMemberId || ''} onAvatarClick={onAvatarClick} /> <Avatar size={avatarsDiameter} pubkey={firstMemberId} onAvatarClick={onAvatarClick} />
<Avatar size={avatarsDiameter} pubkey={secondMemberID || ''} onAvatarClick={onAvatarClick} /> <Avatar size={avatarsDiameter} pubkey={secondMemberID} onAvatarClick={onAvatarClick} />
</div> </div>
); );
}; };

@ -25,6 +25,128 @@ const StyledSpeedButton = styled.div`
} }
`; `;
export const StyledH5AudioPlayer = styled(H5AudioPlayer)`
&.rhap_container {
min-width: 220px;
padding: 0px;
outline: none;
padding: var(--padding-message-content);
border-radius: var(--border-radius-message-box);
svg {
transition: fill var(--default-duration);
}
button {
outline: none;
}
}
.rhap_progress-container {
margin: 0 0 0 calc(10px + 1%);
outline: none;
}
.rhap_total-time {
display: none;
}
.rhap_current-time {
margin: 0 5px 0 4px;
flex-shrink: 0;
}
.rhap_play-pause-button {
display: flex;
justify-content: center;
align-items: center;
}
.rhap_volume-bar {
display: none;
}
.rhap_volume-button {
.module-message__container--incoming & {
color: var(--message-bubbles-received-text-color);
}
.module-message__container--outgoing & {
color: var(--message-bubbles-sent-text-color);
}
}
.rhap_volume-container div[role='progressbar'] {
display: none;
}
.rhap_time {
.module-message__container--incoming & {
color: var(--message-bubbles-received-text-color);
}
.module-message__container--outgoing & {
color: var(--message-bubbles-sent-text-color);
}
font-size: 12px;
}
.rhap_progress-bar {
box-sizing: border-box;
position: relative;
z-index: 0;
width: 100%;
height: 5px;
border-radius: 2px;
}
.rhap_progress-filled {
padding-left: 5px;
}
.rhap_download-progress {
height: 100%;
position: absolute;
z-index: 1;
border-radius: 2px;
}
.rhap_progress-indicator {
z-index: 3;
width: 15px;
height: 15px;
top: -5px;
margin-left: -10px;
box-shadow: rgba(0, 0, 0, 0.5) 0 0 5px !important;
}
.rhap_controls-section {
display: flex;
justify-content: space-between;
align-items: center;
}
.rhap_additional-controls {
display: none;
}
.rhap_play-pause-button {
width: unset;
height: unset;
}
.rhap_controls-section {
flex: unset;
justify-content: flex-start;
}
.rhap_volume-button {
font-size: 20px;
width: 20px;
height: 20px;
margin-right: 0px;
}
`;
export const AudioPlayerWithEncryptedFile = (props: { export const AudioPlayerWithEncryptedFile = (props: {
src: string; src: string;
contentType: string; contentType: string;
@ -98,7 +220,7 @@ export const AudioPlayerWithEncryptedFile = (props: {
}; };
return ( return (
<H5AudioPlayer <StyledH5AudioPlayer
src={urlToLoad} src={urlToLoad}
preload="metadata" preload="metadata"
style={{ pointerEvents: multiSelectMode ? 'none' : 'inherit' }} style={{ pointerEvents: multiSelectMode ? 'none' : 'inherit' }}

@ -31,6 +31,7 @@ import { LightBoxOptions } from '../../SessionConversation';
import { ClickToTrustSender } from './ClickToTrustSender'; import { ClickToTrustSender } from './ClickToTrustSender';
import styled from 'styled-components'; import styled from 'styled-components';
import classNames from 'classnames'; import classNames from 'classnames';
import { StyledMessageHighlighter } from './MessageContent';
export type MessageAttachmentSelectorProps = Pick< export type MessageAttachmentSelectorProps = Pick<
MessageRenderingProps, MessageRenderingProps,
@ -48,10 +49,13 @@ type Props = {
messageId: string; messageId: string;
imageBroken: boolean; imageBroken: boolean;
handleImageError: () => void; handleImageError: () => void;
highlight?: boolean;
}; };
// tslint:disable: use-simple-attributes // tslint:disable: use-simple-attributes
const StyledAttachmentContainer = styled.div<{ messageDirection: MessageModelType }>` const StyledAttachmentContainer = styled.div<{
messageDirection: MessageModelType;
}>`
text-align: center; text-align: center;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
@ -61,10 +65,11 @@ const StyledAttachmentContainer = styled.div<{ messageDirection: MessageModelTyp
// tslint:disable-next-line max-func-body-length cyclomatic-complexity // tslint:disable-next-line max-func-body-length cyclomatic-complexity
export const MessageAttachment = (props: Props) => { export const MessageAttachment = (props: Props) => {
const { messageId, imageBroken, handleImageError } = props; const { messageId, imageBroken, handleImageError, highlight = false } = props;
const dispatch = useDispatch(); const dispatch = useDispatch();
const attachmentProps = useSelector(state => getMessageAttachmentProps(state as any, messageId)); const attachmentProps = useSelector(state => getMessageAttachmentProps(state as any, messageId));
const multiSelectMode = useSelector(isMessageSelectionMode); const multiSelectMode = useSelector(isMessageSelectionMode);
const onClickOnImageGrid = useCallback( const onClickOnImageGrid = useCallback(
(attachment: AttachmentTypeWithPath | AttachmentType) => { (attachment: AttachmentTypeWithPath | AttachmentType) => {
@ -116,6 +121,7 @@ export const MessageAttachment = (props: Props) => {
if (!attachments || !attachments[0]) { if (!attachments || !attachments[0]) {
return null; return null;
} }
const firstAttachment = attachments[0]; const firstAttachment = attachments[0];
const displayImage = canDisplayImage(attachments); const displayImage = canDisplayImage(attachments);
@ -130,18 +136,22 @@ export const MessageAttachment = (props: Props) => {
(isVideo(attachments) && hasVideoScreenshot(attachments))) (isVideo(attachments) && hasVideoScreenshot(attachments)))
) { ) {
return ( return (
<StyledAttachmentContainer messageDirection={direction}> <StyledMessageHighlighter highlight={highlight}>
<ImageGrid <StyledAttachmentContainer messageDirection={direction}>
attachments={attachments} <ImageGrid
onError={handleImageError} attachments={attachments}
onClickAttachment={onClickOnImageGrid} onError={handleImageError}
/> onClickAttachment={onClickOnImageGrid}
</StyledAttachmentContainer> />
</StyledAttachmentContainer>
</StyledMessageHighlighter>
); );
} }
if (!firstAttachment.pending && !firstAttachment.error && isAudio(attachments)) { if (!firstAttachment.pending && !firstAttachment.error && isAudio(attachments)) {
return ( return (
<div <StyledMessageHighlighter
highlight={highlight}
role="main" role="main"
onClick={(e: any) => { onClick={(e: any) => {
if (multiSelectMode) { if (multiSelectMode) {
@ -150,21 +160,20 @@ export const MessageAttachment = (props: Props) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
}} }}
style={{ padding: 'var(--margins-xs) 0px' }}
> >
<AudioPlayerWithEncryptedFile <AudioPlayerWithEncryptedFile
src={firstAttachment.url} src={firstAttachment.url}
contentType={firstAttachment.contentType} contentType={firstAttachment.contentType}
messageId={messageId} messageId={messageId}
/> />
</div> </StyledMessageHighlighter>
); );
} }
const { pending, fileName, fileSize, contentType } = firstAttachment; const { pending, fileName, fileSize, contentType } = firstAttachment;
const extension = getExtensionForDisplay({ contentType, fileName }); const extension = getExtensionForDisplay({ contentType, fileName });
return ( return (
<div className="module-message__generic-attachment"> <StyledMessageHighlighter highlight={highlight} className="module-message__generic-attachment">
{pending ? ( {pending ? (
<div className="module-message__generic-attachment__spinner-container"> <div className="module-message__generic-attachment__spinner-container">
<Spinner size="small" /> <Spinner size="small" />
@ -200,7 +209,7 @@ export const MessageAttachment = (props: Props) => {
{fileSize} {fileSize}
</div> </div>
</div> </div>
</div> </StyledMessageHighlighter>
); );
}; };

@ -62,7 +62,7 @@ const opacityAnimation = keyframes`
} }
`; `;
const StyledMessageHighlighter = styled.div<{ export const StyledMessageHighlighter = styled.div<{
highlight: boolean; highlight: boolean;
}>` }>`
${props => ${props =>
@ -187,13 +187,12 @@ export const MessageContent = (props: Props) => {
</StyledMessageOpaqueContent> </StyledMessageOpaqueContent>
)} )}
{!isDeleted && ( {!isDeleted && (
<StyledMessageHighlighter highlight={highlight}> <MessageAttachment
<MessageAttachment messageId={props.messageId}
messageId={props.messageId} imageBroken={imageBroken}
imageBroken={imageBroken} handleImageError={handleImageError}
handleImageError={handleImageError} highlight={highlight}
/> />
</StyledMessageHighlighter>
)} )}
</IsMessageVisibleContext.Provider> </IsMessageVisibleContext.Provider>
</InView> </InView>

@ -14,7 +14,11 @@ import { StyledPopupContainer } from '../reactions/ReactionPopup';
export const popupXDefault = -81; export const popupXDefault = -81;
export const popupYDefault = -90; export const popupYDefault = -90;
const StyledMessageReactionsContainer = styled(Flex)<{ x: number; y: number; noAvatar: boolean }>` export const StyledMessageReactionsContainer = styled(Flex)<{
x: number;
y: number;
noAvatar: boolean;
}>`
${StyledPopupContainer} { ${StyledPopupContainer} {
position: absolute; position: absolute;
top: ${props => `${props.y}px;`}; top: ${props => `${props.y}px;`};

@ -16,6 +16,7 @@ import { ReadableMessage } from './ReadableMessage';
import styled, { keyframes } from 'styled-components'; import styled, { keyframes } from 'styled-components';
import { isOpenOrClosedGroup } from '../../../../models/conversationAttributes'; import { isOpenOrClosedGroup } from '../../../../models/conversationAttributes';
import { ExpirableReadableMessage } from './ExpirableReadableMessage'; import { ExpirableReadableMessage } from './ExpirableReadableMessage';
import { StyledMessageReactionsContainer } from '../message-content/MessageReactions';
export type GenericReadableMessageSelectorProps = Pick< export type GenericReadableMessageSelectorProps = Pick<
MessageRenderingProps, MessageRenderingProps,
@ -50,11 +51,16 @@ const StyledReadableMessage = styled(ReadableMessage)<{
width: 100%; width: 100%;
letter-spacing: 0.03rem; letter-spacing: 0.03rem;
padding: var(--margins-xs) var(--margins-lg) 0; padding: var(--margins-xs) var(--margins-lg) 0;
margin: var(--margins-xxs) 0;
&.message-highlighted { &.message-highlighted {
animation: ${highlightedMessageAnimation} 1s ease-in-out; animation: ${highlightedMessageAnimation} 1s ease-in-out;
} }
${StyledMessageReactionsContainer} {
margin-top: var(--margins-xs);
}
${props => ${props =>
props.isRightClicked && props.isRightClicked &&
` `

@ -0,0 +1,15 @@
import React from 'react';
// tslint:disable: no-http-string
export const MemberAvatarPlaceHolder = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26">
<circle fill="var(--primary-color)" cx="13" cy="13" r="13" />
<path
fill="var(--white-color)"
d="M18.9 19.1c-1.5-.9-3-.8-3-.8h-3.6c-2.3 0-3.3 0-4.2.3-.9.3-1.8.8-2.8 2-.5.7-.8 1.4-1 2C6.6 24.7 9.7 26 13 26c3.3 0 6.4-1.3 8.7-3.3-.5-1.6-1.5-2.8-2.8-3.6z"
/>
<ellipse cx="13" cy="10.8" fill="var(--white-color)" rx="5.6" ry="6.1" />
</svg>
);
};

@ -6,6 +6,7 @@ import {
useAvatarPath, useAvatarPath,
useConversationUsername, useConversationUsername,
useHasNickname, useHasNickname,
useIsActive,
useIsBlinded, useIsBlinded,
useIsBlocked, useIsBlocked,
useIsIncomingRequest, useIsIncomingRequest,
@ -15,6 +16,7 @@ import {
useIsPrivate, useIsPrivate,
useIsPrivateAndFriend, useIsPrivateAndFriend,
useIsPublic, useIsPublic,
useNotificationSetting,
useWeAreAdmin, useWeAreAdmin,
} from '../../hooks/useParamSelector'; } from '../../hooks/useParamSelector';
import { import {
@ -35,6 +37,10 @@ import {
showUpdateGroupNameByConvoId, showUpdateGroupNameByConvoId,
unblockConvoById, unblockConvoById,
} from '../../interactions/conversationInteractions'; } from '../../interactions/conversationInteractions';
import {
ConversationNotificationSetting,
ConversationNotificationSettingType,
} from '../../models/conversationAttributes';
import { getConversationController } from '../../session/conversations'; import { getConversationController } from '../../session/conversations';
import { PubKey } from '../../session/types'; import { PubKey } from '../../session/types';
import { import {
@ -43,23 +49,10 @@ import {
updateUserDetailsModal, updateUserDetailsModal,
} from '../../state/ducks/modalDialog'; } from '../../state/ducks/modalDialog';
import { getIsMessageSection } from '../../state/selectors/section'; import { getIsMessageSection } from '../../state/selectors/section';
import { import { LocalizerKeys } from '../../types/LocalizerKeys';
useSelectedConversationKey,
useSelectedIsActive,
useSelectedIsBlocked,
useSelectedIsKickedFromGroup,
useSelectedIsLeft,
useSelectedIsPrivate,
useSelectedIsPrivateFriend,
useSelectedNotificationSetting,
} from '../../state/selectors/conversations';
import { SessionButtonColor } from '../basic/SessionButton'; import { SessionButtonColor } from '../basic/SessionButton';
import { useConvoIdFromContext } from '../leftpane/conversation-list-item/ConvoIdContext'; import { useConvoIdFromContext } from '../leftpane/conversation-list-item/ConvoIdContext';
import { import { useSelectedConversationKey } from '../../state/selectors/conversations';
ConversationNotificationSetting,
ConversationNotificationSettingType,
} from '../../models/conversationAttributes';
import { LocalizerKeys } from '../../types/LocalizerKeys';
/** Menu items standardized */ /** Menu items standardized */
@ -556,18 +549,20 @@ export const DeclineAndBlockMsgRequestMenuItem = () => {
}; };
export const NotificationForConvoMenuItem = (): JSX.Element | null => { export const NotificationForConvoMenuItem = (): JSX.Element | null => {
const selectedConvoId = useSelectedConversationKey(); // Note: this item is used in the header and in the list item, so we need to grab the details
// from the convoId from the context itself, not the redux selected state
const convoId = useConvoIdFromContext();
const currentNotificationSetting = useSelectedNotificationSetting(); const currentNotificationSetting = useNotificationSetting(convoId);
const isBlocked = useSelectedIsBlocked(); const isBlocked = useIsBlocked(convoId);
const isActive = useSelectedIsActive(); const isActive = useIsActive(convoId);
const isLeft = useSelectedIsLeft(); const isLeft = useIsLeft(convoId);
const isKickedFromGroup = useSelectedIsKickedFromGroup(); const isKickedFromGroup = useIsKickedFromGroup(convoId);
const isFriend = useSelectedIsPrivateFriend(); const isFriend = useIsPrivateAndFriend(convoId);
const isPrivate = useSelectedIsPrivate(); const isPrivate = useIsPrivate(convoId);
if ( if (
!selectedConvoId || !convoId ||
isLeft || isLeft ||
isKickedFromGroup || isKickedFromGroup ||
isBlocked || isBlocked ||
@ -606,7 +601,7 @@ export const NotificationForConvoMenuItem = (): JSX.Element | null => {
<Item <Item
key={item.value} key={item.value}
onClick={async () => { onClick={async () => {
await setNotificationForConvoId(selectedConvoId, item.value); await setNotificationForConvoId(convoId, item.value);
}} }}
disabled={disabled} disabled={disabled}
> >

@ -1,40 +0,0 @@
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) {
return undefined;
}
// this must be a closed group
const originalMembers = _.cloneDeep(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;
});
}

@ -1,4 +1,4 @@
import { isEmpty, isNumber } from 'lodash'; import { compact, isEmpty, isNumber } from 'lodash';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { import {
hasValidIncomingRequestValues, hasValidIncomingRequestValues,
@ -353,3 +353,16 @@ export function useQuoteAuthorName(
return { authorName, isMe }; return { authorName, isMe };
} }
/**
* Get the list of members of a closed group or []
* @param convoId the closed group id to extract members from
*/
export function useSortedGroupMembers(convoId: string | undefined): Array<string> {
const convoProps = useConversationPropsById(convoId);
if (!convoProps || convoProps.isPrivate || convoProps.isPublic) {
return [];
}
// we need to clone the array before being able to call sort() it
return compact(convoProps.members?.slice()?.sort()) || [];
}

@ -1131,7 +1131,7 @@ export const isClosedGroupConversation = (state: StateType): boolean => {
); );
}; };
const getGroupMembers = (state: StateType): Array<string> => { const getSelectedGroupMembers = (state: StateType): Array<string> => {
const selected = getSelectedConversation(state); const selected = getSelectedConversation(state);
if (!selected) { if (!selected) {
return []; return [];
@ -1211,7 +1211,7 @@ export function useSelectedIsNoteToSelf() {
} }
export function useSelectedMembers() { export function useSelectedMembers() {
return useSelector(getGroupMembers); return useSelector(getSelectedGroupMembers);
} }
export function useSelectedSubscriberCount() { export function useSelectedSubscriberCount() {

@ -17,6 +17,7 @@ export type ThemeGlobals = {
'--font-size-h4': string; '--font-size-h4': string;
/* Margins */ /* Margins */
'--margins-xxs': string;
'--margins-xs': string; '--margins-xs': string;
'--margins-sm': string; '--margins-sm': string;
'--margins-md': string; '--margins-md': string;
@ -98,6 +99,7 @@ export const THEME_GLOBALS: ThemeGlobals = {
'--font-size-h3': '20px', '--font-size-h3': '20px',
'--font-size-h4': '16px', '--font-size-h4': '16px',
'--margins-xxs': '2.5px',
'--margins-xs': '5px', '--margins-xs': '5px',
'--margins-sm': '10px', '--margins-sm': '10px',
'--margins-md': '15px', '--margins-md': '15px',

Loading…
Cancel
Save