Merge branch 'unstable' into userconfig_disappearingmessage

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

@ -1026,6 +1026,9 @@
}
// Module H5AudioPlayer
$rhap_background-color: var(--transparent-color) !default;
$rhap_font-family: inherit !default;
.module-message__container--outgoing {
.rhap_container {
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 = (
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 (pubkey && isClosedGroup) {
return (
<ClosedGroupAvatar size={size} closedGroupId={pubkey} onAvatarClick={props.onAvatarClick} />
);
const NoImage = React.memo(
(
props: Pick<Props, 'forcedName' | 'size' | 'pubkey' | 'onAvatarClick'> & {
isClosedGroup: boolean;
}
) => {
const { forcedName, size, pubkey, isClosedGroup, onAvatarClick } = props;
// if no image but we have conversations set for the group, renders group members avatars
if (pubkey && isClosedGroup) {
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 = (
props: Pick<Props, 'base64Data' | 'dataTestId'> & {
@ -111,12 +111,20 @@ const AvatarImage = (
};
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 isSelectingMessages = useSelector(isMessageSelectionMode);
const isClosedGroupAvatar = useIsClosedGroup(pubkey);
const isClosedGroup = useIsClosedGroup(pubkey);
const avatarPath = useAvatarPath(pubkey);
const name = useConversationUsername(pubkey);
// contentType is not important
@ -130,9 +138,9 @@ const AvatarInner = (props: Props) => {
setImageBroken(true);
};
const hasImage = (base64Data || urlToLoad) && !imageBroken && !isClosedGroupAvatar;
const hasImage = (base64Data || urlToLoad) && !imageBroken && !isClosedGroup;
const isClickable = !!props.onAvatarClick;
const isClickable = !!onAvatarClick;
return (
<div
className={classNames(
@ -167,7 +175,13 @@ const AvatarInner = (props: Props) => {
dataTestId={dataTestId ? `img-${dataTestId}` : undefined}
/>
) : (
<NoImage {...props} isClosedGroup={isClosedGroupAvatar} />
<NoImage
pubkey={pubkey}
isClosedGroup={isClosedGroup}
size={size}
forcedName={forcedName}
onAvatarClick={onAvatarClick}
/>
)}
</div>
);

@ -1,6 +1,8 @@
import React, { useEffect, useState } from 'react';
import { COLORS } from '../../../themes/constants/colors';
import { getInitials } from '../../../util/getInitials';
import { allowOnlyOneAtATime } from '../../../session/utils/Promise';
import { MemberAvatarPlaceHolder } from '../../icon/MemberAvatarPlaceHolder';
type Props = {
diameter: number;
@ -8,13 +10,15 @@ type Props = {
pubkey: string;
};
const sha512FromPubkey = async (pubkey: string): Promise<string> => {
const buf = await crypto.subtle.digest('SHA-512', new TextEncoder().encode(pubkey));
const sha512FromPubkeyOneAtAtime = async (pubkey: string) => {
return allowOnlyOneAtATime(`sha512FromPubkey-${pubkey}`, async () => {
const buf = await crypto.subtle.digest('SHA-512', new TextEncoder().encode(pubkey));
// tslint:disable: prefer-template restrict-plus-operands
return Array.prototype.map
.call(new Uint8Array(buf), (x: any) => ('00' + x.toString(16)).slice(-2))
.join('');
// tslint:disable: prefer-template restrict-plus-operands
return Array.prototype.map
.call(new Uint8Array(buf), (x: any) => ('00' + x.toString(16)).slice(-2))
.join('');
});
};
// 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;
}
void sha512FromPubkey(pubkey).then(sha => {
void sha512FromPubkeyOneAtAtime(pubkey).then(sha => {
if (isInProgress) {
setIsLoading(false);
// Generate the seed simulate the .hashCode as Java
@ -79,22 +84,8 @@ export const AvatarPlaceHolder = (props: Props) => {
const rWithoutBorder = diameterWithoutBorder / 2;
if (loading || !hash) {
// return grey circle
return (
<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>
);
// return avatar placeholder circle
return <MemberAvatarPlaceHolder />;
}
const initials = getInitials(name);

@ -1,13 +1,9 @@
import React from 'react';
import { useMembersAvatars } from '../../../hooks/useMembersAvatars';
import { assertUnreachable } from '../../../types/sqlSharedTypes';
import { Avatar, AvatarSize } from '../Avatar';
type Props = {
size: number;
closedGroupId: string;
onAvatarClick?: () => void;
};
import { isEmpty } from 'lodash';
import { useIsClosedGroup, useSortedGroupMembers } from '../../../hooks/useParamSelector';
import { UserUtils } from '../../../session/utils';
function getClosedGroupAvatarsSize(size: AvatarSize): AvatarSize {
// 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 firstMemberId = memberAvatars?.[0];
const secondMemberID = memberAvatars?.[1];
const firstMemberId = memberAvatars?.firstMember || '';
const secondMemberID = memberAvatars?.secondMember || '';
return (
<div className="module-avatar__icon-closed">
<Avatar size={avatarsDiameter} pubkey={firstMemberId || ''} onAvatarClick={onAvatarClick} />
<Avatar size={avatarsDiameter} pubkey={secondMemberID || ''} onAvatarClick={onAvatarClick} />
<Avatar size={avatarsDiameter} pubkey={firstMemberId} onAvatarClick={onAvatarClick} />
<Avatar size={avatarsDiameter} pubkey={secondMemberID} onAvatarClick={onAvatarClick} />
</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: {
src: string;
contentType: string;
@ -98,7 +220,7 @@ export const AudioPlayerWithEncryptedFile = (props: {
};
return (
<H5AudioPlayer
<StyledH5AudioPlayer
src={urlToLoad}
preload="metadata"
style={{ pointerEvents: multiSelectMode ? 'none' : 'inherit' }}

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

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

@ -14,7 +14,11 @@ import { StyledPopupContainer } from '../reactions/ReactionPopup';
export const popupXDefault = -81;
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} {
position: absolute;
top: ${props => `${props.y}px;`};

@ -16,6 +16,7 @@ import { ReadableMessage } from './ReadableMessage';
import styled, { keyframes } from 'styled-components';
import { isOpenOrClosedGroup } from '../../../../models/conversationAttributes';
import { ExpirableReadableMessage } from './ExpirableReadableMessage';
import { StyledMessageReactionsContainer } from '../message-content/MessageReactions';
export type GenericReadableMessageSelectorProps = Pick<
MessageRenderingProps,
@ -50,11 +51,16 @@ const StyledReadableMessage = styled(ReadableMessage)<{
width: 100%;
letter-spacing: 0.03rem;
padding: var(--margins-xs) var(--margins-lg) 0;
margin: var(--margins-xxs) 0;
&.message-highlighted {
animation: ${highlightedMessageAnimation} 1s ease-in-out;
}
${StyledMessageReactionsContainer} {
margin-top: var(--margins-xs);
}
${props =>
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,
useConversationUsername,
useHasNickname,
useIsActive,
useIsBlinded,
useIsBlocked,
useIsIncomingRequest,
@ -15,6 +16,7 @@ import {
useIsPrivate,
useIsPrivateAndFriend,
useIsPublic,
useNotificationSetting,
useWeAreAdmin,
} from '../../hooks/useParamSelector';
import {
@ -35,6 +37,10 @@ import {
showUpdateGroupNameByConvoId,
unblockConvoById,
} from '../../interactions/conversationInteractions';
import {
ConversationNotificationSetting,
ConversationNotificationSettingType,
} from '../../models/conversationAttributes';
import { getConversationController } from '../../session/conversations';
import { PubKey } from '../../session/types';
import {
@ -43,23 +49,10 @@ import {
updateUserDetailsModal,
} from '../../state/ducks/modalDialog';
import { getIsMessageSection } from '../../state/selectors/section';
import {
useSelectedConversationKey,
useSelectedIsActive,
useSelectedIsBlocked,
useSelectedIsKickedFromGroup,
useSelectedIsLeft,
useSelectedIsPrivate,
useSelectedIsPrivateFriend,
useSelectedNotificationSetting,
} from '../../state/selectors/conversations';
import { LocalizerKeys } from '../../types/LocalizerKeys';
import { SessionButtonColor } from '../basic/SessionButton';
import { useConvoIdFromContext } from '../leftpane/conversation-list-item/ConvoIdContext';
import {
ConversationNotificationSetting,
ConversationNotificationSettingType,
} from '../../models/conversationAttributes';
import { LocalizerKeys } from '../../types/LocalizerKeys';
import { useSelectedConversationKey } from '../../state/selectors/conversations';
/** Menu items standardized */
@ -556,18 +549,20 @@ export const DeclineAndBlockMsgRequestMenuItem = () => {
};
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 isBlocked = useSelectedIsBlocked();
const isActive = useSelectedIsActive();
const isLeft = useSelectedIsLeft();
const isKickedFromGroup = useSelectedIsKickedFromGroup();
const isFriend = useSelectedIsPrivateFriend();
const isPrivate = useSelectedIsPrivate();
const currentNotificationSetting = useNotificationSetting(convoId);
const isBlocked = useIsBlocked(convoId);
const isActive = useIsActive(convoId);
const isLeft = useIsLeft(convoId);
const isKickedFromGroup = useIsKickedFromGroup(convoId);
const isFriend = useIsPrivateAndFriend(convoId);
const isPrivate = useIsPrivate(convoId);
if (
!selectedConvoId ||
!convoId ||
isLeft ||
isKickedFromGroup ||
isBlocked ||
@ -606,7 +601,7 @@ export const NotificationForConvoMenuItem = (): JSX.Element | null => {
<Item
key={item.value}
onClick={async () => {
await setNotificationForConvoId(selectedConvoId, item.value);
await setNotificationForConvoId(convoId, item.value);
}}
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 {
hasValidIncomingRequestValues,
@ -353,3 +353,16 @@ export function useQuoteAuthorName(
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);
if (!selected) {
return [];
@ -1211,7 +1211,7 @@ export function useSelectedIsNoteToSelf() {
}
export function useSelectedMembers() {
return useSelector(getGroupMembers);
return useSelector(getSelectedGroupMembers);
}
export function useSelectedSubscriberCount() {

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

Loading…
Cancel
Save