Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix for userconfig PR 2 #2810

Merged
merged 4 commits into from
Jul 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 33 additions & 19 deletions ts/components/avatar/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'> & {
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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>
);
Expand Down
37 changes: 14 additions & 23 deletions ts/components/avatar/AvatarPlaceHolder/AvatarPlaceHolder.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
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;
name: string;
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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
66 changes: 52 additions & 14 deletions ts/components/avatar/AvatarPlaceHolder/ClosedGroupAvatar.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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>
);
};
15 changes: 15 additions & 0 deletions ts/components/icon/MemberAvatarPlaceHolder.tsx
Original file line number Diff line number Diff line change
@@ -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>
);
};
45 changes: 20 additions & 25 deletions ts/components/menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
useAvatarPath,
useConversationUsername,
useHasNickname,
useIsActive,
useIsBlinded,
useIsBlocked,
useIsIncomingRequest,
Expand All @@ -15,6 +16,7 @@ import {
useIsPrivate,
useIsPrivateAndFriend,
useIsPublic,
useNotificationSetting,
useWeAreAdmin,
} from '../../hooks/useParamSelector';
import {
Expand All @@ -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 {
Expand All @@ -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/selectedConversation';
import { useSelectedConversationKey } from '../../state/selectors/selectedConversation';
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';

/** Menu items standardized */

Expand Down Expand Up @@ -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 ||
Expand Down Expand Up @@ -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}
>
Expand Down
40 changes: 0 additions & 40 deletions ts/hooks/useMembersAvatars.tsx

This file was deleted.

Loading