Click to download avatar for unaccepted conversations
This commit is contained in:
@@ -21,7 +21,7 @@ import { getAuthorId } from './messages/helpers';
|
|||||||
import { maybeDeriveGroupV2Id } from './groups';
|
import { maybeDeriveGroupV2Id } from './groups';
|
||||||
import { assertDev, strictAssert } from './util/assert';
|
import { assertDev, strictAssert } from './util/assert';
|
||||||
import { drop } from './util/drop';
|
import { drop } from './util/drop';
|
||||||
import { isGroupV1, isGroupV2 } from './util/whatTypeOfConversation';
|
import { isGroup, isGroupV1, isGroupV2 } from './util/whatTypeOfConversation';
|
||||||
import type { ServiceIdString, AciString, PniString } from './types/ServiceId';
|
import type { ServiceIdString, AciString, PniString } from './types/ServiceId';
|
||||||
import {
|
import {
|
||||||
isServiceIdString,
|
isServiceIdString,
|
||||||
@@ -39,6 +39,8 @@ import * as StorageService from './services/storage';
|
|||||||
import type { ConversationPropsForUnreadStats } from './util/countUnreadStats';
|
import type { ConversationPropsForUnreadStats } from './util/countUnreadStats';
|
||||||
import { countAllConversationsUnreadStats } from './util/countUnreadStats';
|
import { countAllConversationsUnreadStats } from './util/countUnreadStats';
|
||||||
import { isTestOrMockEnvironment } from './environment';
|
import { isTestOrMockEnvironment } from './environment';
|
||||||
|
import { isConversationAccepted } from './util/isConversationAccepted';
|
||||||
|
import { areWePending } from './util/groupMembershipUtils';
|
||||||
import { conversationJobQueue } from './jobs/conversationJobQueue';
|
import { conversationJobQueue } from './jobs/conversationJobQueue';
|
||||||
|
|
||||||
type ConvoMatchType =
|
type ConvoMatchType =
|
||||||
@@ -1372,6 +1374,52 @@ export class ConversationController {
|
|||||||
this.get(conversationId)?.onOpenComplete(loadStart);
|
this.get(conversationId)?.onOpenComplete(loadStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
migrateAvatarsForNonAcceptedConversations(): void {
|
||||||
|
if (window.storage.get('avatarsHaveBeenMigrated')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const conversations = this.getAll();
|
||||||
|
let numberOfConversationsMigrated = 0;
|
||||||
|
for (const conversation of conversations) {
|
||||||
|
const attrs = conversation.attributes;
|
||||||
|
if (
|
||||||
|
!isConversationAccepted(attrs) ||
|
||||||
|
(isGroup(attrs) && areWePending(attrs))
|
||||||
|
) {
|
||||||
|
const avatarPath = attrs.avatar?.path;
|
||||||
|
const profileAvatarPath = attrs.profileAvatar?.path;
|
||||||
|
|
||||||
|
if (avatarPath || profileAvatarPath) {
|
||||||
|
drop(
|
||||||
|
(async () => {
|
||||||
|
const { doesAttachmentExist, deleteAttachmentData } =
|
||||||
|
window.Signal.Migrations;
|
||||||
|
if (avatarPath && (await doesAttachmentExist(avatarPath))) {
|
||||||
|
await deleteAttachmentData(avatarPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
profileAvatarPath &&
|
||||||
|
(await doesAttachmentExist(profileAvatarPath))
|
||||||
|
) {
|
||||||
|
await deleteAttachmentData(profileAvatarPath);
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
conversation.set('avatar', undefined);
|
||||||
|
conversation.set('profileAvatar', undefined);
|
||||||
|
drop(updateConversation(conversation.attributes));
|
||||||
|
numberOfConversationsMigrated += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info(
|
||||||
|
`ConversationController: unset avatars for ${numberOfConversationsMigrated} unaccepted conversations`
|
||||||
|
);
|
||||||
|
drop(window.storage.put('avatarsHaveBeenMigrated', true));
|
||||||
|
}
|
||||||
|
|
||||||
repairPinnedConversations(): void {
|
repairPinnedConversations(): void {
|
||||||
const pinnedIds = window.storage.get('pinnedConversationIds', []);
|
const pinnedIds = window.storage.get('pinnedConversationIds', []);
|
||||||
|
|
||||||
|
44
ts/Crypto.ts
44
ts/Crypto.ts
@@ -670,6 +670,34 @@ export function constantTimeEqual(
|
|||||||
return crypto.constantTimeEqual(left, right);
|
return crypto.constantTimeEqual(left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getIdentifierHash({
|
||||||
|
aci,
|
||||||
|
e164,
|
||||||
|
pni,
|
||||||
|
groupId,
|
||||||
|
}: {
|
||||||
|
aci: AciString | undefined;
|
||||||
|
e164: string | undefined;
|
||||||
|
pni: PniString | undefined;
|
||||||
|
groupId: string | undefined;
|
||||||
|
}): number | null {
|
||||||
|
let identifier: Uint8Array;
|
||||||
|
if (aci != null) {
|
||||||
|
identifier = Aci.parseFromServiceIdString(aci).getServiceIdBinary();
|
||||||
|
} else if (e164 != null) {
|
||||||
|
identifier = Bytes.fromString(e164);
|
||||||
|
} else if (pni != null) {
|
||||||
|
identifier = Pni.parseFromServiceIdString(pni).getServiceIdBinary();
|
||||||
|
} else if (groupId != null) {
|
||||||
|
identifier = Bytes.fromBase64(groupId);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const digest = hash(HashType.size256, identifier);
|
||||||
|
return digest[0];
|
||||||
|
}
|
||||||
|
|
||||||
export function generateAvatarColor({
|
export function generateAvatarColor({
|
||||||
aci,
|
aci,
|
||||||
e164,
|
e164,
|
||||||
@@ -681,19 +709,11 @@ export function generateAvatarColor({
|
|||||||
pni: PniString | undefined;
|
pni: PniString | undefined;
|
||||||
groupId: string | undefined;
|
groupId: string | undefined;
|
||||||
}): string {
|
}): string {
|
||||||
let identifier: Uint8Array;
|
const hashValue = getIdentifierHash({ aci, e164, pni, groupId });
|
||||||
if (aci != null) {
|
|
||||||
identifier = Aci.parseFromServiceIdString(aci).getServiceIdBinary();
|
if (hashValue == null) {
|
||||||
} else if (e164 != null) {
|
|
||||||
identifier = Bytes.fromString(e164);
|
|
||||||
} else if (pni != null) {
|
|
||||||
identifier = Pni.parseFromServiceIdString(pni).getServiceIdBinary();
|
|
||||||
} else if (groupId != null) {
|
|
||||||
identifier = Bytes.fromBase64(groupId);
|
|
||||||
} else {
|
|
||||||
return sample(AvatarColors) || AvatarColors[0];
|
return sample(AvatarColors) || AvatarColors[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
const digest = hash(HashType.size256, identifier);
|
return AvatarColors[hashValue % AVATAR_COLOR_COUNT];
|
||||||
return AvatarColors[digest[0] % AVATAR_COLOR_COUNT];
|
|
||||||
}
|
}
|
||||||
|
@@ -1445,6 +1445,10 @@ export async function startApp(): Promise<void> {
|
|||||||
if (window.isBeforeVersion(lastVersion, 'v5.31.0')) {
|
if (window.isBeforeVersion(lastVersion, 'v5.31.0')) {
|
||||||
window.ConversationController.repairPinnedConversations();
|
window.ConversationController.repairPinnedConversations();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!window.storage.get('avatarsHaveBeenMigrated', false)) {
|
||||||
|
window.ConversationController.migrateAvatarsForNonAcceptedConversations();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void badgeImageFileDownloader.checkForFilesToDownload();
|
void badgeImageFileDownloader.checkForFilesToDownload();
|
||||||
|
@@ -129,7 +129,7 @@ export function AddUserToAnotherGroupModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...pick(convo, 'id', 'avatarUrl', 'title', 'unblurredAvatarUrl'),
|
...pick(convo, 'id', 'avatarUrl', 'title', 'hasAvatar'),
|
||||||
memberships,
|
memberships,
|
||||||
membersCount,
|
membersCount,
|
||||||
disabledReason,
|
disabledReason,
|
||||||
|
@@ -4,7 +4,6 @@
|
|||||||
import type { Meta, StoryFn } from '@storybook/react';
|
import type { Meta, StoryFn } from '@storybook/react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
import { isBoolean } from 'lodash';
|
|
||||||
import { expect, fn, within, userEvent } from '@storybook/test';
|
import { expect, fn, within, userEvent } from '@storybook/test';
|
||||||
import type { AvatarColorType } from '../types/Colors';
|
import type { AvatarColorType } from '../types/Colors';
|
||||||
import type { Props } from './Avatar';
|
import type { Props } from './Avatar';
|
||||||
@@ -60,16 +59,13 @@ export default {
|
|||||||
} satisfies Meta<Props>;
|
} satisfies Meta<Props>;
|
||||||
|
|
||||||
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||||
acceptedMessageRequest: isBoolean(overrideProps.acceptedMessageRequest)
|
|
||||||
? overrideProps.acceptedMessageRequest
|
|
||||||
: true,
|
|
||||||
avatarUrl: overrideProps.avatarUrl || '',
|
avatarUrl: overrideProps.avatarUrl || '',
|
||||||
badge: overrideProps.badge,
|
badge: overrideProps.badge,
|
||||||
blur: overrideProps.blur,
|
blur: overrideProps.blur,
|
||||||
color: overrideProps.color || AvatarColors[0],
|
color: overrideProps.color || AvatarColors[0],
|
||||||
conversationType: overrideProps.conversationType || 'direct',
|
conversationType: overrideProps.conversationType || 'direct',
|
||||||
|
hasAvatar: Boolean(overrideProps.hasAvatar),
|
||||||
i18n,
|
i18n,
|
||||||
isMe: false,
|
|
||||||
loading: Boolean(overrideProps.loading),
|
loading: Boolean(overrideProps.loading),
|
||||||
noteToSelf: Boolean(overrideProps.noteToSelf),
|
noteToSelf: Boolean(overrideProps.noteToSelf),
|
||||||
onClick: fn(action('onClick')),
|
onClick: fn(action('onClick')),
|
||||||
@@ -200,8 +196,9 @@ Loading.args = createProps({
|
|||||||
|
|
||||||
export const BlurredBasedOnProps = TemplateSingle.bind({});
|
export const BlurredBasedOnProps = TemplateSingle.bind({});
|
||||||
BlurredBasedOnProps.args = createProps({
|
BlurredBasedOnProps.args = createProps({
|
||||||
acceptedMessageRequest: false,
|
hasAvatar: true,
|
||||||
avatarUrl: '/fixtures/kitten-3-64-64.jpg',
|
avatarUrl: '/fixtures/kitten-3-64-64.jpg',
|
||||||
|
blur: AvatarBlur.BlurPicture,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ForceBlurred = TemplateSingle.bind({});
|
export const ForceBlurred = TemplateSingle.bind({});
|
||||||
|
@@ -25,8 +25,8 @@ import { assertDev } from '../util/assert';
|
|||||||
import { getBadgeImageFileLocalPath } from '../badges/getBadgeImageFileLocalPath';
|
import { getBadgeImageFileLocalPath } from '../badges/getBadgeImageFileLocalPath';
|
||||||
import { getInitials } from '../util/getInitials';
|
import { getInitials } from '../util/getInitials';
|
||||||
import { isBadgeVisible } from '../badges/isBadgeVisible';
|
import { isBadgeVisible } from '../badges/isBadgeVisible';
|
||||||
import { shouldBlurAvatar } from '../util/shouldBlurAvatar';
|
|
||||||
import { SIGNAL_AVATAR_PATH } from '../types/SignalConversation';
|
import { SIGNAL_AVATAR_PATH } from '../types/SignalConversation';
|
||||||
|
import { getAvatarPlaceholderGradient } from '../utils/getAvatarPlaceholderGradient';
|
||||||
|
|
||||||
export enum AvatarBlur {
|
export enum AvatarBlur {
|
||||||
NoBlur,
|
NoBlur,
|
||||||
@@ -54,20 +54,18 @@ type BadgePlacementType = { bottom: number; right: number };
|
|||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
|
avatarPlaceholderGradient?: Readonly<[string, string]>;
|
||||||
blur?: AvatarBlur;
|
blur?: AvatarBlur;
|
||||||
color?: AvatarColorType;
|
color?: AvatarColorType;
|
||||||
|
hasAvatar?: boolean;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
|
||||||
acceptedMessageRequest: boolean;
|
|
||||||
conversationType: 'group' | 'direct' | 'callLink';
|
conversationType: 'group' | 'direct' | 'callLink';
|
||||||
isMe: boolean;
|
|
||||||
noteToSelf?: boolean;
|
noteToSelf?: boolean;
|
||||||
phoneNumber?: string;
|
phoneNumber?: string;
|
||||||
profileName?: string;
|
profileName?: string;
|
||||||
sharedGroupNames: ReadonlyArray<string>;
|
sharedGroupNames: ReadonlyArray<string>;
|
||||||
size: AvatarSize;
|
size: AvatarSize;
|
||||||
title: string;
|
title: string;
|
||||||
unblurredAvatarUrl?: string;
|
|
||||||
searchResult?: boolean;
|
searchResult?: boolean;
|
||||||
storyRing?: HasStories;
|
storyRing?: HasStories;
|
||||||
|
|
||||||
@@ -100,39 +98,26 @@ const BADGE_PLACEMENT_BY_SIZE = new Map<number, BadgePlacementType>([
|
|||||||
[112, { bottom: -4, right: 3 }],
|
[112, { bottom: -4, right: 3 }],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const getDefaultBlur = (
|
|
||||||
...args: Parameters<typeof shouldBlurAvatar>
|
|
||||||
): AvatarBlur =>
|
|
||||||
shouldBlurAvatar(...args) ? AvatarBlur.BlurPicture : AvatarBlur.NoBlur;
|
|
||||||
|
|
||||||
export function Avatar({
|
export function Avatar({
|
||||||
acceptedMessageRequest,
|
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
|
avatarPlaceholderGradient = getAvatarPlaceholderGradient(0),
|
||||||
badge,
|
badge,
|
||||||
className,
|
className,
|
||||||
color = 'A200',
|
color = 'A200',
|
||||||
conversationType,
|
conversationType,
|
||||||
|
hasAvatar,
|
||||||
i18n,
|
i18n,
|
||||||
isMe,
|
|
||||||
innerRef,
|
innerRef,
|
||||||
loading,
|
loading,
|
||||||
noteToSelf,
|
noteToSelf,
|
||||||
onClick,
|
onClick,
|
||||||
onClickBadge,
|
onClickBadge,
|
||||||
sharedGroupNames,
|
|
||||||
size,
|
size,
|
||||||
theme,
|
theme,
|
||||||
title,
|
title,
|
||||||
unblurredAvatarUrl,
|
|
||||||
searchResult,
|
searchResult,
|
||||||
storyRing,
|
storyRing,
|
||||||
blur = getDefaultBlur({
|
blur = AvatarBlur.NoBlur,
|
||||||
acceptedMessageRequest,
|
|
||||||
avatarUrl,
|
|
||||||
isMe,
|
|
||||||
sharedGroupNames,
|
|
||||||
unblurredAvatarUrl,
|
|
||||||
}),
|
|
||||||
...ariaProps
|
...ariaProps
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const [imageBroken, setImageBroken] = useState(false);
|
const [imageBroken, setImageBroken] = useState(false);
|
||||||
@@ -204,6 +189,20 @@ export function Avatar({
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
} else if (hasAvatar && !hasImage) {
|
||||||
|
contentsChildren = (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className="module-Avatar__image"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `linear-gradient(to bottom, ${avatarPlaceholderGradient[0]}, ${avatarPlaceholderGradient[1]})`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{blur === AvatarBlur.BlurPictureWithClickToView && (
|
||||||
|
<div className="module-Avatar__click-to-view">{i18n('icu:view')}</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
} else if (searchResult) {
|
} else if (searchResult) {
|
||||||
contentsChildren = (
|
contentsChildren = (
|
||||||
<div
|
<div
|
||||||
|
@@ -10,9 +10,11 @@ import { Lightbox } from './Lightbox';
|
|||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
|
avatarPlaceholderGradient?: Readonly<[string, string]>;
|
||||||
avatarColor?: AvatarColorType;
|
avatarColor?: AvatarColorType;
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
conversationTitle?: string;
|
conversationTitle?: string;
|
||||||
|
hasAvatar?: boolean;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isGroup?: boolean;
|
isGroup?: boolean;
|
||||||
noteToSelf?: boolean;
|
noteToSelf?: boolean;
|
||||||
@@ -20,9 +22,11 @@ export type PropsType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function AvatarLightbox({
|
export function AvatarLightbox({
|
||||||
|
avatarPlaceholderGradient,
|
||||||
avatarColor,
|
avatarColor,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
conversationTitle,
|
conversationTitle,
|
||||||
|
hasAvatar,
|
||||||
i18n,
|
i18n,
|
||||||
isGroup,
|
isGroup,
|
||||||
noteToSelf,
|
noteToSelf,
|
||||||
@@ -44,9 +48,11 @@ export function AvatarLightbox({
|
|||||||
selectedIndex={0}
|
selectedIndex={0}
|
||||||
>
|
>
|
||||||
<AvatarPreview
|
<AvatarPreview
|
||||||
|
avatarPlaceholderGradient={avatarPlaceholderGradient}
|
||||||
avatarColor={avatarColor}
|
avatarColor={avatarColor}
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
conversationTitle={conversationTitle}
|
conversationTitle={conversationTitle}
|
||||||
|
hasAvatar={hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isGroup={isGroup}
|
isGroup={isGroup}
|
||||||
noteToSelf={noteToSelf}
|
noteToSelf={noteToSelf}
|
||||||
|
@@ -12,6 +12,7 @@ import type { AvatarColorType } from '../types/Colors';
|
|||||||
import { AvatarColors } from '../types/Colors';
|
import { AvatarColors } from '../types/Colors';
|
||||||
import { getInitials } from '../util/getInitials';
|
import { getInitials } from '../util/getInitials';
|
||||||
import { imagePathToBytes } from '../util/imagePathToBytes';
|
import { imagePathToBytes } from '../util/imagePathToBytes';
|
||||||
|
import { type ConversationType } from '../state/ducks/conversations';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
avatarColor?: AvatarColorType;
|
avatarColor?: AvatarColorType;
|
||||||
@@ -26,19 +27,22 @@ export type PropsType = {
|
|||||||
onClear?: () => unknown;
|
onClear?: () => unknown;
|
||||||
onClick?: () => unknown;
|
onClick?: () => unknown;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
};
|
} & Pick<ConversationType, 'avatarPlaceholderGradient' | 'hasAvatar'>;
|
||||||
|
|
||||||
enum ImageStatus {
|
enum ImageStatus {
|
||||||
Nothing = 'nothing',
|
Nothing = 'nothing',
|
||||||
Loading = 'loading',
|
Loading = 'loading',
|
||||||
HasImage = 'has-image',
|
HasImage = 'has-image',
|
||||||
|
HasPlaceholder = 'has-placeholder',
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AvatarPreview({
|
export function AvatarPreview({
|
||||||
|
avatarPlaceholderGradient,
|
||||||
avatarColor = AvatarColors[0],
|
avatarColor = AvatarColors[0],
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
avatarValue,
|
avatarValue,
|
||||||
conversationTitle,
|
conversationTitle,
|
||||||
|
hasAvatar,
|
||||||
i18n,
|
i18n,
|
||||||
isEditable,
|
isEditable,
|
||||||
isGroup,
|
isGroup,
|
||||||
@@ -127,6 +131,8 @@ export function AvatarPreview({
|
|||||||
} else if (avatarUrl) {
|
} else if (avatarUrl) {
|
||||||
encodedPath = avatarUrl;
|
encodedPath = avatarUrl;
|
||||||
imageStatus = ImageStatus.HasImage;
|
imageStatus = ImageStatus.HasImage;
|
||||||
|
} else if (hasAvatar && avatarPlaceholderGradient) {
|
||||||
|
imageStatus = ImageStatus.HasPlaceholder;
|
||||||
} else {
|
} else {
|
||||||
imageStatus = ImageStatus.Nothing;
|
imageStatus = ImageStatus.Nothing;
|
||||||
}
|
}
|
||||||
@@ -184,6 +190,22 @@ export function AvatarPreview({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (imageStatus === ImageStatus.HasPlaceholder) {
|
||||||
|
return (
|
||||||
|
<div className="AvatarPreview">
|
||||||
|
<div
|
||||||
|
className="AvatarPreview__avatar"
|
||||||
|
style={{
|
||||||
|
...componentStyle,
|
||||||
|
backgroundImage: avatarPlaceholderGradient
|
||||||
|
? `linear-gradient(to bottom, ${avatarPlaceholderGradient[0]}, ${avatarPlaceholderGradient[1]})`
|
||||||
|
: undefined,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="AvatarPreview">
|
<div className="AvatarPreview">
|
||||||
<div
|
<div
|
||||||
|
@@ -91,8 +91,6 @@ export function CallLinkAddNameModal({
|
|||||||
color={getColorForCallLink(callLink.rootKey)}
|
color={getColorForCallLink(callLink.rootKey)}
|
||||||
conversationType="callLink"
|
conversationType="callLink"
|
||||||
size={AvatarSize.SIXTY_FOUR}
|
size={AvatarSize.SIXTY_FOUR}
|
||||||
acceptedMessageRequest
|
|
||||||
isMe={false}
|
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
title={
|
title={
|
||||||
callLink.name === ''
|
callLink.name === ''
|
||||||
|
@@ -107,8 +107,6 @@ export function CallLinkDetails({
|
|||||||
color={getColorForCallLink(callLink.rootKey)}
|
color={getColorForCallLink(callLink.rootKey)}
|
||||||
conversationType="callLink"
|
conversationType="callLink"
|
||||||
size={AvatarSize.SIXTY_FOUR}
|
size={AvatarSize.SIXTY_FOUR}
|
||||||
acceptedMessageRequest
|
|
||||||
isMe={false}
|
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
title={callLink.name ?? i18n('icu:calling__call-link-default-title')}
|
title={callLink.name ?? i18n('icu:calling__call-link-default-title')}
|
||||||
/>
|
/>
|
||||||
@@ -277,8 +275,6 @@ function renderMissingCallLink({
|
|||||||
badge={undefined}
|
badge={undefined}
|
||||||
conversationType="callLink"
|
conversationType="callLink"
|
||||||
size={AvatarSize.SIXTY_FOUR}
|
size={AvatarSize.SIXTY_FOUR}
|
||||||
acceptedMessageRequest
|
|
||||||
isMe={false}
|
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
title={i18n('icu:calling__call-link-default-title')}
|
title={i18n('icu:calling__call-link-default-title')}
|
||||||
/>
|
/>
|
||||||
|
@@ -128,8 +128,6 @@ export function CallLinkEditModal({
|
|||||||
color={getColorForCallLink(callLink.rootKey)}
|
color={getColorForCallLink(callLink.rootKey)}
|
||||||
conversationType="callLink"
|
conversationType="callLink"
|
||||||
size={AvatarSize.SIXTY_FOUR}
|
size={AvatarSize.SIXTY_FOUR}
|
||||||
acceptedMessageRequest
|
|
||||||
isMe={false}
|
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
title={
|
title={
|
||||||
callLink.name === ''
|
callLink.name === ''
|
||||||
|
@@ -62,19 +62,18 @@ export function CallLinkPendingParticipantModal({
|
|||||||
theme={Theme.Dark}
|
theme={Theme.Dark}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={conversation.acceptedMessageRequest}
|
|
||||||
avatarUrl={conversation.avatarUrl}
|
avatarUrl={conversation.avatarUrl}
|
||||||
|
avatarPlaceholderGradient={conversation.avatarPlaceholderGradient}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={conversation.color}
|
color={conversation.color}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
|
hasAvatar={conversation.hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={conversation.isMe}
|
|
||||||
profileName={conversation.profileName}
|
profileName={conversation.profileName}
|
||||||
sharedGroupNames={conversation.sharedGroupNames}
|
sharedGroupNames={conversation.sharedGroupNames}
|
||||||
size={AvatarSize.EIGHTY}
|
size={AvatarSize.EIGHTY}
|
||||||
title={conversation.title}
|
title={conversation.title}
|
||||||
theme={ThemeType.dark}
|
theme={ThemeType.dark}
|
||||||
unblurredAvatarUrl={conversation.unblurredAvatarUrl}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
@@ -12,16 +12,17 @@ import type { ConversationType } from '../state/ducks/conversations';
|
|||||||
export type Props = {
|
export type Props = {
|
||||||
conversation: Pick<
|
conversation: Pick<
|
||||||
ConversationType,
|
ConversationType,
|
||||||
|
| 'avatarPlaceholderGradient'
|
||||||
| 'acceptedMessageRequest'
|
| 'acceptedMessageRequest'
|
||||||
| 'avatarUrl'
|
| 'avatarUrl'
|
||||||
| 'color'
|
| 'color'
|
||||||
|
| 'hasAvatar'
|
||||||
| 'isMe'
|
| 'isMe'
|
||||||
| 'name'
|
| 'name'
|
||||||
| 'phoneNumber'
|
| 'phoneNumber'
|
||||||
| 'profileName'
|
| 'profileName'
|
||||||
| 'sharedGroupNames'
|
| 'sharedGroupNames'
|
||||||
| 'title'
|
| 'title'
|
||||||
| 'unblurredAvatarUrl'
|
|
||||||
>;
|
>;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
close: () => void;
|
close: () => void;
|
||||||
@@ -45,14 +46,14 @@ export function CallNeedPermissionScreen({
|
|||||||
return (
|
return (
|
||||||
<div className="module-call-need-permission-screen">
|
<div className="module-call-need-permission-screen">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={conversation.acceptedMessageRequest}
|
avatarPlaceholderGradient={conversation.avatarPlaceholderGradient}
|
||||||
avatarUrl={conversation.avatarUrl}
|
avatarUrl={conversation.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={conversation.color || AvatarColors[0]}
|
color={conversation.color || AvatarColors[0]}
|
||||||
noteToSelf={false}
|
noteToSelf={false}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
|
hasAvatar={conversation.hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={conversation.isMe}
|
|
||||||
phoneNumber={conversation.phoneNumber}
|
phoneNumber={conversation.phoneNumber}
|
||||||
profileName={conversation.profileName}
|
profileName={conversation.profileName}
|
||||||
title={conversation.title}
|
title={conversation.title}
|
||||||
|
@@ -500,14 +500,14 @@ export function CallScreen({
|
|||||||
) : (
|
) : (
|
||||||
<CallBackgroundBlur avatarUrl={me.avatarUrl}>
|
<CallBackgroundBlur avatarUrl={me.avatarUrl}>
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest
|
avatarPlaceholderGradient={me.avatarPlaceholderGradient}
|
||||||
avatarUrl={me.avatarUrl}
|
avatarUrl={me.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={me.color || AvatarColors[0]}
|
color={me.color || AvatarColors[0]}
|
||||||
|
hasAvatar={me.hasAvatar}
|
||||||
noteToSelf={false}
|
noteToSelf={false}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe
|
|
||||||
phoneNumber={me.phoneNumber}
|
phoneNumber={me.phoneNumber}
|
||||||
profileName={me.profileName}
|
profileName={me.profileName}
|
||||||
title={me.title}
|
title={me.title}
|
||||||
|
@@ -79,7 +79,7 @@ function UnknownContacts({
|
|||||||
: 0;
|
: 0;
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={participant.acceptedMessageRequest}
|
avatarPlaceholderGradient={participant.avatarPlaceholderGradient}
|
||||||
avatarUrl={participant.avatarUrl}
|
avatarUrl={participant.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
className="CallingAdhocCallInfo__UnknownContactAvatar"
|
className="CallingAdhocCallInfo__UnknownContactAvatar"
|
||||||
@@ -87,7 +87,6 @@ function UnknownContacts({
|
|||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
key={key}
|
key={key}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={participant.isMe}
|
|
||||||
profileName={participant.profileName}
|
profileName={participant.profileName}
|
||||||
title={participant.title}
|
title={participant.title}
|
||||||
sharedGroupNames={participant.sharedGroupNames}
|
sharedGroupNames={participant.sharedGroupNames}
|
||||||
@@ -210,13 +209,12 @@ export function CallingAdhocCallInfo({
|
|||||||
>
|
>
|
||||||
<div className="module-calling-participants-list__avatar-and-name">
|
<div className="module-calling-participants-list__avatar-and-name">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={participant.acceptedMessageRequest}
|
avatarPlaceholderGradient={participant.avatarPlaceholderGradient}
|
||||||
avatarUrl={participant.avatarUrl}
|
avatarUrl={participant.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={participant.color}
|
color={participant.color}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={participant.isMe}
|
|
||||||
profileName={participant.profileName}
|
profileName={participant.profileName}
|
||||||
title={participant.title}
|
title={participant.title}
|
||||||
sharedGroupNames={participant.sharedGroupNames}
|
sharedGroupNames={participant.sharedGroupNames}
|
||||||
|
@@ -35,9 +35,11 @@ export type PropsType = {
|
|||||||
callMode: CallMode;
|
callMode: CallMode;
|
||||||
conversation: Pick<
|
conversation: Pick<
|
||||||
CallingConversationType,
|
CallingConversationType,
|
||||||
|
| 'avatarPlaceholderGradient'
|
||||||
| 'acceptedMessageRequest'
|
| 'acceptedMessageRequest'
|
||||||
| 'avatarUrl'
|
| 'avatarUrl'
|
||||||
| 'color'
|
| 'color'
|
||||||
|
| 'hasAvatar'
|
||||||
| 'isMe'
|
| 'isMe'
|
||||||
| 'memberships'
|
| 'memberships'
|
||||||
| 'name'
|
| 'name'
|
||||||
@@ -48,7 +50,6 @@ export type PropsType = {
|
|||||||
| 'systemNickname'
|
| 'systemNickname'
|
||||||
| 'title'
|
| 'title'
|
||||||
| 'type'
|
| 'type'
|
||||||
| 'unblurredAvatarUrl'
|
|
||||||
>;
|
>;
|
||||||
getIsSharingPhoneNumberWithEverybody: () => boolean;
|
getIsSharingPhoneNumberWithEverybody: () => boolean;
|
||||||
groupMembers?: Array<
|
groupMembers?: Array<
|
||||||
|
@@ -126,15 +126,14 @@ export const CallingParticipantsList = React.memo(
|
|||||||
>
|
>
|
||||||
<div className="module-calling-participants-list__avatar-and-name">
|
<div className="module-calling-participants-list__avatar-and-name">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={
|
avatarPlaceholderGradient={
|
||||||
participant.acceptedMessageRequest
|
participant.avatarPlaceholderGradient
|
||||||
}
|
}
|
||||||
avatarUrl={participant.avatarUrl}
|
avatarUrl={participant.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={participant.color}
|
color={participant.color}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={participant.isMe}
|
|
||||||
profileName={participant.profileName}
|
profileName={participant.profileName}
|
||||||
title={participant.title}
|
title={participant.title}
|
||||||
sharedGroupNames={participant.sharedGroupNames}
|
sharedGroupNames={participant.sharedGroupNames}
|
||||||
|
@@ -308,13 +308,14 @@ export function CallingPendingParticipants({
|
|||||||
className="module-calling-participants-list__avatar-and-name CallingPendingParticipants__ParticipantButton"
|
className="module-calling-participants-list__avatar-and-name CallingPendingParticipants__ParticipantButton"
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={participant.acceptedMessageRequest}
|
avatarPlaceholderGradient={
|
||||||
|
participant.avatarPlaceholderGradient
|
||||||
|
}
|
||||||
avatarUrl={participant.avatarUrl}
|
avatarUrl={participant.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={participant.color}
|
color={participant.color}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={participant.isMe}
|
|
||||||
profileName={participant.profileName}
|
profileName={participant.profileName}
|
||||||
title={participant.title}
|
title={participant.title}
|
||||||
sharedGroupNames={participant.sharedGroupNames}
|
sharedGroupNames={participant.sharedGroupNames}
|
||||||
@@ -396,13 +397,12 @@ export function CallingPendingParticipants({
|
|||||||
className="module-calling-participants-list__avatar-and-name CallingPendingParticipants__ParticipantButton"
|
className="module-calling-participants-list__avatar-and-name CallingPendingParticipants__ParticipantButton"
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={participant.acceptedMessageRequest}
|
avatarPlaceholderGradient={participant.avatarPlaceholderGradient}
|
||||||
avatarUrl={participant.avatarUrl}
|
avatarUrl={participant.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={participant.color}
|
color={participant.color}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={participant.isMe}
|
|
||||||
profileName={participant.profileName}
|
profileName={participant.profileName}
|
||||||
title={participant.title}
|
title={participant.title}
|
||||||
sharedGroupNames={participant.sharedGroupNames}
|
sharedGroupNames={participant.sharedGroupNames}
|
||||||
|
@@ -40,11 +40,10 @@ function NoVideo({
|
|||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const {
|
const {
|
||||||
acceptedMessageRequest,
|
avatarPlaceholderGradient,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
color,
|
color,
|
||||||
type: conversationType,
|
type: conversationType,
|
||||||
isMe,
|
|
||||||
phoneNumber,
|
phoneNumber,
|
||||||
profileName,
|
profileName,
|
||||||
sharedGroupNames,
|
sharedGroupNames,
|
||||||
@@ -56,14 +55,13 @@ function NoVideo({
|
|||||||
<CallBackgroundBlur avatarUrl={avatarUrl}>
|
<CallBackgroundBlur avatarUrl={avatarUrl}>
|
||||||
<div className="module-calling-pip__video--avatar">
|
<div className="module-calling-pip__video--avatar">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={acceptedMessageRequest}
|
avatarPlaceholderGradient={avatarPlaceholderGradient}
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={color || AvatarColors[0]}
|
color={color || AvatarColors[0]}
|
||||||
noteToSelf={false}
|
noteToSelf={false}
|
||||||
conversationType={conversationType}
|
conversationType={conversationType}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={isMe}
|
|
||||||
phoneNumber={phoneNumber}
|
phoneNumber={phoneNumber}
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
title={title}
|
title={title}
|
||||||
|
@@ -30,9 +30,11 @@ type PeekedParticipantType = Pick<
|
|||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
conversation: Pick<
|
conversation: Pick<
|
||||||
CallingConversationType,
|
CallingConversationType,
|
||||||
|
| 'avatarPlaceholderGradient'
|
||||||
| 'acceptedMessageRequest'
|
| 'acceptedMessageRequest'
|
||||||
| 'avatarUrl'
|
| 'avatarUrl'
|
||||||
| 'color'
|
| 'color'
|
||||||
|
| 'hasAvatar'
|
||||||
| 'isMe'
|
| 'isMe'
|
||||||
| 'phoneNumber'
|
| 'phoneNumber'
|
||||||
| 'profileName'
|
| 'profileName'
|
||||||
@@ -41,7 +43,6 @@ export type PropsType = {
|
|||||||
| 'systemNickname'
|
| 'systemNickname'
|
||||||
| 'title'
|
| 'title'
|
||||||
| 'type'
|
| 'type'
|
||||||
| 'unblurredAvatarUrl'
|
|
||||||
>;
|
>;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
me: Pick<ConversationType, 'id' | 'serviceId'>;
|
me: Pick<ConversationType, 'id' | 'serviceId'>;
|
||||||
@@ -216,19 +217,18 @@ export function CallingPreCallInfo({
|
|||||||
return (
|
return (
|
||||||
<div className="module-CallingPreCallInfo">
|
<div className="module-CallingPreCallInfo">
|
||||||
<Avatar
|
<Avatar
|
||||||
|
avatarPlaceholderGradient={conversation.avatarPlaceholderGradient}
|
||||||
avatarUrl={conversation.avatarUrl}
|
avatarUrl={conversation.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={conversation.color}
|
color={conversation.color}
|
||||||
acceptedMessageRequest={conversation.acceptedMessageRequest}
|
|
||||||
conversationType={conversation.type}
|
conversationType={conversation.type}
|
||||||
isMe={conversation.isMe}
|
hasAvatar={conversation.hasAvatar}
|
||||||
noteToSelf={false}
|
noteToSelf={false}
|
||||||
phoneNumber={conversation.phoneNumber}
|
phoneNumber={conversation.phoneNumber}
|
||||||
profileName={conversation.profileName}
|
profileName={conversation.profileName}
|
||||||
sharedGroupNames={conversation.sharedGroupNames}
|
sharedGroupNames={conversation.sharedGroupNames}
|
||||||
size={AvatarSize.SIXTY_FOUR}
|
size={AvatarSize.SIXTY_FOUR}
|
||||||
title={conversation.title}
|
title={conversation.title}
|
||||||
unblurredAvatarUrl={conversation.unblurredAvatarUrl}
|
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
/>
|
/>
|
||||||
<div className="module-CallingPreCallInfo__title">
|
<div className="module-CallingPreCallInfo__title">
|
||||||
|
@@ -101,13 +101,15 @@ export function CallingRaisedHandsList({
|
|||||||
>
|
>
|
||||||
<div className="CallingRaisedHandsList__AvatarAndName module-calling-participants-list__avatar-and-name">
|
<div className="CallingRaisedHandsList__AvatarAndName module-calling-participants-list__avatar-and-name">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={participant.acceptedMessageRequest}
|
avatarPlaceholderGradient={
|
||||||
|
participant.avatarPlaceholderGradient
|
||||||
|
}
|
||||||
avatarUrl={participant.avatarUrl}
|
avatarUrl={participant.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={participant.color}
|
color={participant.color}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
|
hasAvatar={participant.hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={participant.isMe}
|
|
||||||
profileName={participant.profileName}
|
profileName={participant.profileName}
|
||||||
title={participant.title}
|
title={participant.title}
|
||||||
sharedGroupNames={participant.sharedGroupNames}
|
sharedGroupNames={participant.sharedGroupNames}
|
||||||
|
@@ -900,12 +900,14 @@ export function CallsList({
|
|||||||
aria-selected={isSelected}
|
aria-selected={isSelected}
|
||||||
leading={
|
leading={
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest
|
avatarPlaceholderGradient={
|
||||||
|
conversation.avatarPlaceholderGradient
|
||||||
|
}
|
||||||
avatarUrl={conversation.avatarUrl}
|
avatarUrl={conversation.avatarUrl}
|
||||||
color={conversation.color}
|
color={conversation.color}
|
||||||
conversationType={conversation.type}
|
conversationType={conversation.type}
|
||||||
|
hasAvatar={conversation.hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={false}
|
|
||||||
title={conversation.title}
|
title={conversation.title}
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
size={AvatarSize.THIRTY_SIX}
|
size={AvatarSize.THIRTY_SIX}
|
||||||
|
@@ -217,11 +217,12 @@ export function CallsNewCall({
|
|||||||
<ListTile
|
<ListTile
|
||||||
leading={
|
leading={
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest
|
avatarPlaceholderGradient={
|
||||||
|
item.conversation.avatarPlaceholderGradient
|
||||||
|
}
|
||||||
avatarUrl={item.conversation.avatarUrl}
|
avatarUrl={item.conversation.avatarUrl}
|
||||||
conversationType="group"
|
conversationType="group"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={false}
|
|
||||||
title={item.conversation.title}
|
title={item.conversation.title}
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
|
@@ -14,32 +14,31 @@ export type PropsType = {
|
|||||||
} & Pick<
|
} & Pick<
|
||||||
ConversationType,
|
ConversationType,
|
||||||
| 'about'
|
| 'about'
|
||||||
| 'acceptedMessageRequest'
|
| 'avatarPlaceholderGradient'
|
||||||
| 'avatarUrl'
|
| 'avatarUrl'
|
||||||
| 'color'
|
| 'color'
|
||||||
| 'firstName'
|
| 'firstName'
|
||||||
|
| 'hasAvatar'
|
||||||
| 'id'
|
| 'id'
|
||||||
| 'isMe'
|
| 'isMe'
|
||||||
| 'phoneNumber'
|
| 'phoneNumber'
|
||||||
| 'profileName'
|
| 'profileName'
|
||||||
| 'sharedGroupNames'
|
| 'sharedGroupNames'
|
||||||
| 'title'
|
| 'title'
|
||||||
| 'unblurredAvatarUrl'
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function ContactPill({
|
export function ContactPill({
|
||||||
acceptedMessageRequest,
|
avatarPlaceholderGradient,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
color,
|
color,
|
||||||
firstName,
|
firstName,
|
||||||
|
hasAvatar,
|
||||||
i18n,
|
i18n,
|
||||||
isMe,
|
|
||||||
id,
|
id,
|
||||||
phoneNumber,
|
phoneNumber,
|
||||||
profileName,
|
profileName,
|
||||||
sharedGroupNames,
|
sharedGroupNames,
|
||||||
title,
|
title,
|
||||||
unblurredAvatarUrl,
|
|
||||||
onClickRemove,
|
onClickRemove,
|
||||||
}: PropsType): JSX.Element {
|
}: PropsType): JSX.Element {
|
||||||
const removeLabel = i18n('icu:ContactPill--remove');
|
const removeLabel = i18n('icu:ContactPill--remove');
|
||||||
@@ -47,20 +46,19 @@ export function ContactPill({
|
|||||||
return (
|
return (
|
||||||
<div className="module-ContactPill">
|
<div className="module-ContactPill">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={acceptedMessageRequest}
|
avatarPlaceholderGradient={avatarPlaceholderGradient}
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={color}
|
color={color}
|
||||||
noteToSelf={false}
|
noteToSelf={false}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
|
hasAvatar={hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={isMe}
|
|
||||||
phoneNumber={phoneNumber}
|
phoneNumber={phoneNumber}
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
title={title}
|
title={title}
|
||||||
sharedGroupNames={sharedGroupNames}
|
sharedGroupNames={sharedGroupNames}
|
||||||
size={AvatarSize.TWENTY}
|
size={AvatarSize.TWENTY}
|
||||||
unblurredAvatarUrl={unblurredAvatarUrl}
|
|
||||||
/>
|
/>
|
||||||
<ContactName
|
<ContactName
|
||||||
firstName={firstName}
|
firstName={firstName}
|
||||||
|
@@ -38,6 +38,7 @@ const contactPillProps = (
|
|||||||
getDefaultConversation({
|
getDefaultConversation({
|
||||||
avatarUrl: gifUrl,
|
avatarUrl: gifUrl,
|
||||||
firstName: 'John',
|
firstName: 'John',
|
||||||
|
hasAvatar: true,
|
||||||
id: 'abc123',
|
id: 'abc123',
|
||||||
isMe: false,
|
isMe: false,
|
||||||
name: 'John Bon Bon Jovi',
|
name: 'John Bon Bon Jovi',
|
||||||
|
@@ -402,12 +402,14 @@ export function ConversationList({
|
|||||||
break;
|
break;
|
||||||
case RowType.Conversation: {
|
case RowType.Conversation: {
|
||||||
const itemProps = pick(row.conversation, [
|
const itemProps = pick(row.conversation, [
|
||||||
|
'avatarPlaceholderGradient',
|
||||||
'acceptedMessageRequest',
|
'acceptedMessageRequest',
|
||||||
'avatarUrl',
|
'avatarUrl',
|
||||||
'badges',
|
'badges',
|
||||||
'color',
|
'color',
|
||||||
'draftPreview',
|
'draftPreview',
|
||||||
'groupId',
|
'groupId',
|
||||||
|
'hasAvatar',
|
||||||
'id',
|
'id',
|
||||||
'isBlocked',
|
'isBlocked',
|
||||||
'isMe',
|
'isMe',
|
||||||
@@ -425,7 +427,6 @@ export function ConversationList({
|
|||||||
'title',
|
'title',
|
||||||
'type',
|
'type',
|
||||||
'typingContactIdTimestamps',
|
'typingContactIdTimestamps',
|
||||||
'unblurredAvatarUrl',
|
|
||||||
'unreadCount',
|
'unreadCount',
|
||||||
'unreadMentionsCount',
|
'unreadMentionsCount',
|
||||||
'serviceId',
|
'serviceId',
|
||||||
|
@@ -51,19 +51,21 @@ export function DirectCallRemoteParticipant({
|
|||||||
function renderAvatar(
|
function renderAvatar(
|
||||||
i18n: LocalizerType,
|
i18n: LocalizerType,
|
||||||
{
|
{
|
||||||
acceptedMessageRequest,
|
avatarPlaceholderGradient,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
color,
|
color,
|
||||||
isMe,
|
hasAvatar,
|
||||||
phoneNumber,
|
phoneNumber,
|
||||||
profileName,
|
profileName,
|
||||||
sharedGroupNames,
|
sharedGroupNames,
|
||||||
title,
|
title,
|
||||||
}: Pick<
|
}: Pick<
|
||||||
ConversationType,
|
ConversationType,
|
||||||
|
| 'avatarPlaceholderGradient'
|
||||||
| 'acceptedMessageRequest'
|
| 'acceptedMessageRequest'
|
||||||
| 'avatarUrl'
|
| 'avatarUrl'
|
||||||
| 'color'
|
| 'color'
|
||||||
|
| 'hasAvatar'
|
||||||
| 'isMe'
|
| 'isMe'
|
||||||
| 'phoneNumber'
|
| 'phoneNumber'
|
||||||
| 'profileName'
|
| 'profileName'
|
||||||
@@ -75,14 +77,14 @@ function renderAvatar(
|
|||||||
<div className="module-ongoing-call__remote-video-disabled">
|
<div className="module-ongoing-call__remote-video-disabled">
|
||||||
<CallBackgroundBlur avatarUrl={avatarUrl}>
|
<CallBackgroundBlur avatarUrl={avatarUrl}>
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={acceptedMessageRequest}
|
avatarPlaceholderGradient={avatarPlaceholderGradient}
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={color || AvatarColors[0]}
|
color={color || AvatarColors[0]}
|
||||||
|
hasAvatar={hasAvatar}
|
||||||
noteToSelf={false}
|
noteToSelf={false}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={isMe}
|
|
||||||
phoneNumber={phoneNumber}
|
phoneNumber={phoneNumber}
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
title={title}
|
title={title}
|
||||||
|
@@ -90,16 +90,16 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
acceptedMessageRequest,
|
avatarPlaceholderGradient,
|
||||||
addedTime,
|
addedTime,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
color,
|
color,
|
||||||
demuxId,
|
demuxId,
|
||||||
|
hasAvatar,
|
||||||
hasRemoteAudio,
|
hasRemoteAudio,
|
||||||
hasRemoteVideo,
|
hasRemoteVideo,
|
||||||
isHandRaised,
|
isHandRaised,
|
||||||
isBlocked,
|
isBlocked,
|
||||||
isMe,
|
|
||||||
mediaKeysReceived,
|
mediaKeysReceived,
|
||||||
profileName,
|
profileName,
|
||||||
sharedGroupNames,
|
sharedGroupNames,
|
||||||
@@ -455,14 +455,14 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
|
|||||||
} else {
|
} else {
|
||||||
noVideoNode = (
|
noVideoNode = (
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={acceptedMessageRequest}
|
avatarPlaceholderGradient={avatarPlaceholderGradient}
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={color || AvatarColors[0]}
|
color={color || AvatarColors[0]}
|
||||||
noteToSelf={false}
|
noteToSelf={false}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
|
hasAvatar={hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={isMe}
|
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
title={title}
|
title={title}
|
||||||
sharedGroupNames={sharedGroupNames}
|
sharedGroupNames={sharedGroupNames}
|
||||||
|
@@ -109,16 +109,15 @@ function Contacts({
|
|||||||
{contacts.map(contact => (
|
{contacts.map(contact => (
|
||||||
<li key={contact.id} className="module-GroupDialog__contacts__contact">
|
<li key={contact.id} className="module-GroupDialog__contacts__contact">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={contact.acceptedMessageRequest}
|
avatarPlaceholderGradient={contact.avatarPlaceholderGradient}
|
||||||
avatarUrl={contact.avatarUrl}
|
avatarUrl={contact.avatarUrl}
|
||||||
badge={getPreferredBadge(contact.badges)}
|
badge={getPreferredBadge(contact.badges)}
|
||||||
color={contact.color}
|
color={contact.color}
|
||||||
conversationType={contact.type}
|
conversationType={contact.type}
|
||||||
isMe={contact.isMe}
|
hasAvatar={contact.hasAvatar}
|
||||||
noteToSelf={contact.isMe}
|
noteToSelf={contact.isMe}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
title={contact.title}
|
title={contact.title}
|
||||||
unblurredAvatarUrl={contact.unblurredAvatarUrl}
|
|
||||||
sharedGroupNames={contact.sharedGroupNames}
|
sharedGroupNames={contact.sharedGroupNames}
|
||||||
size={AvatarSize.TWENTY_EIGHT}
|
size={AvatarSize.TWENTY_EIGHT}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
@@ -69,14 +69,12 @@ export const GroupV2JoinDialog = React.memo(function GroupV2JoinDialogInner({
|
|||||||
/>
|
/>
|
||||||
<div className="module-group-v2-join-dialog__avatar">
|
<div className="module-group-v2-join-dialog__avatar">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={false}
|
|
||||||
avatarUrl={avatar ? avatar.url : undefined}
|
avatarUrl={avatar ? avatar.url : undefined}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
blur={AvatarBlur.NoBlur}
|
blur={AvatarBlur.NoBlur}
|
||||||
loading={avatar && !avatar.url}
|
loading={avatar && !avatar.url}
|
||||||
conversationType="group"
|
conversationType="group"
|
||||||
title={title}
|
title={title}
|
||||||
isMe={false}
|
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
size={80}
|
size={80}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
@@ -193,10 +193,8 @@ export function IncomingCallBar(props: PropsType): JSX.Element | null {
|
|||||||
} = props;
|
} = props;
|
||||||
const {
|
const {
|
||||||
id: conversationId,
|
id: conversationId,
|
||||||
acceptedMessageRequest,
|
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
color,
|
color,
|
||||||
isMe,
|
|
||||||
phoneNumber,
|
phoneNumber,
|
||||||
profileName,
|
profileName,
|
||||||
sharedGroupNames,
|
sharedGroupNames,
|
||||||
@@ -274,14 +272,12 @@ export function IncomingCallBar(props: PropsType): JSX.Element | null {
|
|||||||
<div className="IncomingCallBar__conversation">
|
<div className="IncomingCallBar__conversation">
|
||||||
<div className="IncomingCallBar__conversation--avatar">
|
<div className="IncomingCallBar__conversation--avatar">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={acceptedMessageRequest}
|
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={color || AvatarColors[0]}
|
color={color || AvatarColors[0]}
|
||||||
noteToSelf={false}
|
noteToSelf={false}
|
||||||
conversationType={conversationType}
|
conversationType={conversationType}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={isMe}
|
|
||||||
phoneNumber={phoneNumber}
|
phoneNumber={phoneNumber}
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
title={title}
|
title={title}
|
||||||
|
@@ -174,18 +174,19 @@ export function LeftPaneSearchInput({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={searchConversation.acceptedMessageRequest}
|
avatarPlaceholderGradient={
|
||||||
|
searchConversation.avatarPlaceholderGradient
|
||||||
|
}
|
||||||
avatarUrl={searchConversation.avatarUrl}
|
avatarUrl={searchConversation.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={searchConversation.color}
|
color={searchConversation.color}
|
||||||
conversationType={searchConversation.type}
|
conversationType={searchConversation.type}
|
||||||
|
hasAvatar={searchConversation.hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={searchConversation.isMe}
|
|
||||||
noteToSelf={searchConversation.isMe}
|
noteToSelf={searchConversation.isMe}
|
||||||
sharedGroupNames={searchConversation.sharedGroupNames}
|
sharedGroupNames={searchConversation.sharedGroupNames}
|
||||||
size={AvatarSize.TWENTY}
|
size={AvatarSize.TWENTY}
|
||||||
title={searchConversation.title}
|
title={searchConversation.title}
|
||||||
unblurredAvatarUrl={searchConversation.unblurredAvatarUrl}
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
aria-label={i18n('icu:clearSearch')}
|
aria-label={i18n('icu:clearSearch')}
|
||||||
|
@@ -882,19 +882,18 @@ function LightboxHeader({
|
|||||||
<div className="Lightbox__header--container">
|
<div className="Lightbox__header--container">
|
||||||
<div className="Lightbox__header--avatar">
|
<div className="Lightbox__header--avatar">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={conversation.acceptedMessageRequest}
|
avatarPlaceholderGradient={conversation.avatarPlaceholderGradient}
|
||||||
avatarUrl={conversation.avatarUrl}
|
avatarUrl={conversation.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={conversation.color}
|
color={conversation.color}
|
||||||
conversationType={conversation.type}
|
conversationType={conversation.type}
|
||||||
|
hasAvatar={conversation.hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={conversation.isMe}
|
|
||||||
phoneNumber={conversation.e164}
|
phoneNumber={conversation.e164}
|
||||||
profileName={conversation.profileName}
|
profileName={conversation.profileName}
|
||||||
sharedGroupNames={conversation.sharedGroupNames}
|
sharedGroupNames={conversation.sharedGroupNames}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
title={conversation.title}
|
title={conversation.title}
|
||||||
unblurredAvatarUrl={conversation.unblurredAvatarUrl}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="Lightbox__header--content">
|
<div className="Lightbox__header--content">
|
||||||
|
@@ -48,15 +48,7 @@ export function MyStoryButton({
|
|||||||
? getNewestMyStory(myStories[0])
|
? getNewestMyStory(myStories[0])
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const {
|
const { avatarUrl, color, profileName, sharedGroupNames, title } = me;
|
||||||
acceptedMessageRequest,
|
|
||||||
avatarUrl,
|
|
||||||
color,
|
|
||||||
isMe,
|
|
||||||
profileName,
|
|
||||||
sharedGroupNames,
|
|
||||||
title,
|
|
||||||
} = me;
|
|
||||||
|
|
||||||
if (!newestStory) {
|
if (!newestStory) {
|
||||||
return (
|
return (
|
||||||
@@ -69,13 +61,11 @@ export function MyStoryButton({
|
|||||||
>
|
>
|
||||||
<div className="MyStories__avatar-container">
|
<div className="MyStories__avatar-container">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={acceptedMessageRequest}
|
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={getAvatarColor(color)}
|
color={getAvatarColor(color)}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={Boolean(isMe)}
|
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
sharedGroupNames={sharedGroupNames}
|
sharedGroupNames={sharedGroupNames}
|
||||||
size={AvatarSize.FORTY_EIGHT}
|
size={AvatarSize.FORTY_EIGHT}
|
||||||
@@ -122,13 +112,11 @@ export function MyStoryButton({
|
|||||||
onContextMenuShowingChanged={setActive}
|
onContextMenuShowingChanged={setActive}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={acceptedMessageRequest}
|
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={getAvatarColor(color)}
|
color={getAvatarColor(color)}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={Boolean(isMe)}
|
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
sharedGroupNames={sharedGroupNames}
|
sharedGroupNames={sharedGroupNames}
|
||||||
size={AvatarSize.FORTY_EIGHT}
|
size={AvatarSize.FORTY_EIGHT}
|
||||||
|
@@ -372,14 +372,12 @@ export function NavTabs({
|
|||||||
<span className="NavTabs__ItemButton">
|
<span className="NavTabs__ItemButton">
|
||||||
<span className="NavTabs__ItemContent">
|
<span className="NavTabs__ItemContent">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest
|
|
||||||
avatarUrl={me.avatarUrl}
|
avatarUrl={me.avatarUrl}
|
||||||
badge={badge}
|
badge={badge}
|
||||||
className="module-main-header__avatar"
|
className="module-main-header__avatar"
|
||||||
color={me.color}
|
color={me.color}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe
|
|
||||||
phoneNumber={me.phoneNumber}
|
phoneNumber={me.phoneNumber}
|
||||||
profileName={me.profileName}
|
profileName={me.profileName}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
@@ -451,20 +451,19 @@ function ContactRow({
|
|||||||
return (
|
return (
|
||||||
<li className="module-SafetyNumberChangeDialog__row" key={contact.id}>
|
<li className="module-SafetyNumberChangeDialog__row" key={contact.id}>
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={contact.acceptedMessageRequest}
|
avatarPlaceholderGradient={contact.avatarPlaceholderGradient}
|
||||||
avatarUrl={contact.avatarUrl}
|
avatarUrl={contact.avatarUrl}
|
||||||
badge={getPreferredBadge(contact.badges)}
|
badge={getPreferredBadge(contact.badges)}
|
||||||
color={contact.color}
|
color={contact.color}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
|
hasAvatar={contact.hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={contact.isMe}
|
|
||||||
phoneNumber={contact.phoneNumber}
|
phoneNumber={contact.phoneNumber}
|
||||||
profileName={contact.profileName}
|
profileName={contact.profileName}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
title={contact.title}
|
title={contact.title}
|
||||||
sharedGroupNames={contact.sharedGroupNames}
|
sharedGroupNames={contact.sharedGroupNames}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
unblurredAvatarUrl={contact.unblurredAvatarUrl}
|
|
||||||
/>
|
/>
|
||||||
<div className="module-SafetyNumberChangeDialog__row--wrapper">
|
<div className="module-SafetyNumberChangeDialog__row--wrapper">
|
||||||
<div className="module-SafetyNumberChangeDialog__row--name">
|
<div className="module-SafetyNumberChangeDialog__row--name">
|
||||||
|
@@ -557,13 +557,14 @@ export function SendStoryModal({
|
|||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={group.acceptedMessageRequest}
|
avatarPlaceholderGradient={
|
||||||
|
group.avatarPlaceholderGradient
|
||||||
|
}
|
||||||
avatarUrl={group.avatarUrl}
|
avatarUrl={group.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={group.color}
|
color={group.color}
|
||||||
conversationType={group.type}
|
conversationType={group.type}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={false}
|
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
title={group.title}
|
title={group.title}
|
||||||
@@ -707,13 +708,11 @@ export function SendStoryModal({
|
|||||||
>
|
>
|
||||||
{list.id === MY_STORY_ID ? (
|
{list.id === MY_STORY_ID ? (
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={me.acceptedMessageRequest}
|
|
||||||
avatarUrl={me.avatarUrl}
|
avatarUrl={me.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={me.color}
|
color={me.color}
|
||||||
conversationType={me.type}
|
conversationType={me.type}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe
|
|
||||||
sharedGroupNames={me.sharedGroupNames}
|
sharedGroupNames={me.sharedGroupNames}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
storyRing={undefined}
|
storyRing={undefined}
|
||||||
@@ -822,13 +821,11 @@ export function SendStoryModal({
|
|||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={group.acceptedMessageRequest}
|
|
||||||
avatarUrl={group.avatarUrl}
|
avatarUrl={group.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={group.color}
|
color={group.color}
|
||||||
conversationType={group.type}
|
conversationType={group.type}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={false}
|
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
storyRing={group.hasStories}
|
storyRing={group.hasStories}
|
||||||
|
@@ -160,13 +160,12 @@ function DistributionListItem({
|
|||||||
<span className="StoriesSettingsModal__list__left">
|
<span className="StoriesSettingsModal__list__left">
|
||||||
{isMyStory ? (
|
{isMyStory ? (
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={me.acceptedMessageRequest}
|
avatarPlaceholderGradient={me.avatarPlaceholderGradient}
|
||||||
avatarUrl={me.avatarUrl}
|
avatarUrl={me.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={me.color}
|
color={me.color}
|
||||||
conversationType={me.type}
|
conversationType={me.type}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe
|
|
||||||
sharedGroupNames={me.sharedGroupNames}
|
sharedGroupNames={me.sharedGroupNames}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
title={me.title}
|
title={me.title}
|
||||||
@@ -214,13 +213,12 @@ function GroupStoryItem({
|
|||||||
>
|
>
|
||||||
<span className="StoriesSettingsModal__list__left">
|
<span className="StoriesSettingsModal__list__left">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={groupStory.acceptedMessageRequest}
|
avatarPlaceholderGradient={groupStory.avatarPlaceholderGradient}
|
||||||
avatarUrl={groupStory.avatarUrl}
|
avatarUrl={groupStory.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={groupStory.color}
|
color={groupStory.color}
|
||||||
conversationType={groupStory.type}
|
conversationType={groupStory.type}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={false}
|
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
title={groupStory.title}
|
title={groupStory.title}
|
||||||
@@ -675,13 +673,12 @@ export function DistributionListSettingsModal({
|
|||||||
>
|
>
|
||||||
<span className="StoriesSettingsModal__list__left">
|
<span className="StoriesSettingsModal__list__left">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={member.acceptedMessageRequest}
|
avatarPlaceholderGradient={member.avatarPlaceholderGradient}
|
||||||
avatarUrl={member.avatarUrl}
|
avatarUrl={member.avatarUrl}
|
||||||
badge={getPreferredBadge(member.badges)}
|
badge={getPreferredBadge(member.badges)}
|
||||||
color={member.color}
|
color={member.color}
|
||||||
conversationType={member.type}
|
conversationType={member.type}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe
|
|
||||||
sharedGroupNames={member.sharedGroupNames}
|
sharedGroupNames={member.sharedGroupNames}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
@@ -1094,13 +1091,12 @@ export function EditDistributionListModal({
|
|||||||
>
|
>
|
||||||
<span className="StoriesSettingsModal__list__left">
|
<span className="StoriesSettingsModal__list__left">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={contact.acceptedMessageRequest}
|
avatarPlaceholderGradient={contact.avatarPlaceholderGradient}
|
||||||
avatarUrl={contact.avatarUrl}
|
avatarUrl={contact.avatarUrl}
|
||||||
badge={getPreferredBadge(contact.badges)}
|
badge={getPreferredBadge(contact.badges)}
|
||||||
color={contact.color}
|
color={contact.color}
|
||||||
conversationType={contact.type}
|
conversationType={contact.type}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe
|
|
||||||
sharedGroupNames={contact.sharedGroupNames}
|
sharedGroupNames={contact.sharedGroupNames}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
@@ -1190,10 +1186,10 @@ export function EditDistributionListModal({
|
|||||||
{selectedContacts.map(contact => (
|
{selectedContacts.map(contact => (
|
||||||
<ContactPill
|
<ContactPill
|
||||||
key={contact.id}
|
key={contact.id}
|
||||||
acceptedMessageRequest={contact.acceptedMessageRequest}
|
|
||||||
avatarUrl={contact.avatarUrl}
|
avatarUrl={contact.avatarUrl}
|
||||||
color={contact.color}
|
color={contact.color}
|
||||||
firstName={contact.firstName}
|
firstName={contact.firstName}
|
||||||
|
hasAvatar={contact.hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
id={contact.id}
|
id={contact.id}
|
||||||
isMe={contact.isMe}
|
isMe={contact.isMe}
|
||||||
@@ -1287,13 +1283,12 @@ export function GroupStorySettingsModal({
|
|||||||
>
|
>
|
||||||
<div className="GroupStorySettingsModal__header">
|
<div className="GroupStorySettingsModal__header">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={group.acceptedMessageRequest}
|
avatarPlaceholderGradient={group.avatarPlaceholderGradient}
|
||||||
avatarUrl={group.avatarUrl}
|
avatarUrl={group.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={group.color}
|
color={group.color}
|
||||||
conversationType={group.type}
|
conversationType={group.type}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={false}
|
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
title={group.title}
|
title={group.title}
|
||||||
@@ -1316,13 +1311,12 @@ export function GroupStorySettingsModal({
|
|||||||
className="GroupStorySettingsModal__members_item"
|
className="GroupStorySettingsModal__members_item"
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={member.acceptedMessageRequest}
|
avatarPlaceholderGradient={member.avatarPlaceholderGradient}
|
||||||
avatarUrl={member.avatarUrl}
|
avatarUrl={member.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={member.color}
|
color={member.color}
|
||||||
conversationType={member.type}
|
conversationType={member.type}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={false}
|
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
title={member.title}
|
title={member.title}
|
||||||
|
@@ -128,20 +128,17 @@ export function StoryDetailsModal({
|
|||||||
return (
|
return (
|
||||||
<div key={contact.id} className="StoryDetailsModal__contact">
|
<div key={contact.id} className="StoryDetailsModal__contact">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={contact.acceptedMessageRequest}
|
|
||||||
avatarUrl={contact.avatarUrl}
|
avatarUrl={contact.avatarUrl}
|
||||||
badge={getPreferredBadge(contact.badges)}
|
badge={getPreferredBadge(contact.badges)}
|
||||||
color={contact.color}
|
color={contact.color}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={contact.isMe}
|
|
||||||
phoneNumber={contact.phoneNumber}
|
phoneNumber={contact.phoneNumber}
|
||||||
profileName={contact.profileName}
|
profileName={contact.profileName}
|
||||||
sharedGroupNames={contact.sharedGroupNames}
|
sharedGroupNames={contact.sharedGroupNames}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
theme={ThemeType.dark}
|
theme={ThemeType.dark}
|
||||||
title={contact.title}
|
title={contact.title}
|
||||||
unblurredAvatarUrl={contact.unblurredAvatarUrl}
|
|
||||||
/>
|
/>
|
||||||
<div className="StoryDetailsModal__contact__text">
|
<div className="StoryDetailsModal__contact__text">
|
||||||
<ContactName title={contact.title} />
|
<ContactName title={contact.title} />
|
||||||
@@ -171,13 +168,12 @@ export function StoryDetailsModal({
|
|||||||
</div>
|
</div>
|
||||||
<div className="StoryDetailsModal__contact">
|
<div className="StoryDetailsModal__contact">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={sender.acceptedMessageRequest}
|
avatarPlaceholderGradient={sender.avatarPlaceholderGradient}
|
||||||
avatarUrl={sender.avatarUrl}
|
avatarUrl={sender.avatarUrl}
|
||||||
badge={getPreferredBadge(sender.badges)}
|
badge={getPreferredBadge(sender.badges)}
|
||||||
color={sender.color}
|
color={sender.color}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={sender.isMe}
|
|
||||||
profileName={sender.profileName}
|
profileName={sender.profileName}
|
||||||
sharedGroupNames={sender.sharedGroupNames}
|
sharedGroupNames={sender.sharedGroupNames}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
|
@@ -34,21 +34,20 @@ export type PropsType = Pick<ConversationStoryType, 'group' | 'isHidden'> & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function StoryListItemAvatar({
|
function StoryListItemAvatar({
|
||||||
acceptedMessageRequest,
|
avatarPlaceholderGradient,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
avatarStoryRing,
|
avatarStoryRing,
|
||||||
badges,
|
badges,
|
||||||
color,
|
color,
|
||||||
getPreferredBadge,
|
getPreferredBadge,
|
||||||
i18n,
|
i18n,
|
||||||
isMe,
|
|
||||||
profileName,
|
profileName,
|
||||||
sharedGroupNames,
|
sharedGroupNames,
|
||||||
title,
|
title,
|
||||||
theme,
|
theme,
|
||||||
}: Pick<
|
}: Pick<
|
||||||
ConversationType,
|
ConversationType,
|
||||||
| 'acceptedMessageRequest'
|
| 'avatarPlaceholderGradient'
|
||||||
| 'avatarUrl'
|
| 'avatarUrl'
|
||||||
| 'color'
|
| 'color'
|
||||||
| 'profileName'
|
| 'profileName'
|
||||||
@@ -59,18 +58,16 @@ function StoryListItemAvatar({
|
|||||||
badges?: ConversationType['badges'];
|
badges?: ConversationType['badges'];
|
||||||
getPreferredBadge: PreferredBadgeSelectorType;
|
getPreferredBadge: PreferredBadgeSelectorType;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isMe?: boolean;
|
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={acceptedMessageRequest}
|
avatarPlaceholderGradient={avatarPlaceholderGradient}
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
badge={badges ? getPreferredBadge(badges) : undefined}
|
badge={badges ? getPreferredBadge(badges) : undefined}
|
||||||
color={getAvatarColor(color)}
|
color={getAvatarColor(color)}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={Boolean(isMe)}
|
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
sharedGroupNames={sharedGroupNames}
|
sharedGroupNames={sharedGroupNames}
|
||||||
size={AvatarSize.FORTY_EIGHT}
|
size={AvatarSize.FORTY_EIGHT}
|
||||||
|
@@ -201,7 +201,6 @@ export function StoryViewer({
|
|||||||
timestamp,
|
timestamp,
|
||||||
} = story;
|
} = story;
|
||||||
const {
|
const {
|
||||||
acceptedMessageRequest,
|
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
color,
|
color,
|
||||||
isMe,
|
isMe,
|
||||||
@@ -737,13 +736,11 @@ export function StoryViewer({
|
|||||||
<div className="StoryViewer__meta__playback-bar">
|
<div className="StoryViewer__meta__playback-bar">
|
||||||
<div className="StoryViewer__meta__playback-bar__container">
|
<div className="StoryViewer__meta__playback-bar__container">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={acceptedMessageRequest}
|
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={getAvatarColor(color)}
|
color={getAvatarColor(color)}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={Boolean(isMe)}
|
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
sharedGroupNames={sharedGroupNames}
|
sharedGroupNames={sharedGroupNames}
|
||||||
size={AvatarSize.TWENTY_EIGHT}
|
size={AvatarSize.TWENTY_EIGHT}
|
||||||
@@ -751,14 +748,12 @@ export function StoryViewer({
|
|||||||
/>
|
/>
|
||||||
{group && (
|
{group && (
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={group.acceptedMessageRequest}
|
|
||||||
avatarUrl={group.avatarUrl}
|
avatarUrl={group.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
className="StoryViewer__meta--group-avatar"
|
className="StoryViewer__meta--group-avatar"
|
||||||
color={getAvatarColor(group.color)}
|
color={getAvatarColor(group.color)}
|
||||||
conversationType="group"
|
conversationType="group"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={false}
|
|
||||||
profileName={group.profileName}
|
profileName={group.profileName}
|
||||||
sharedGroupNames={group.sharedGroupNames}
|
sharedGroupNames={group.sharedGroupNames}
|
||||||
size={AvatarSize.TWENTY_EIGHT}
|
size={AvatarSize.TWENTY_EIGHT}
|
||||||
|
@@ -382,13 +382,11 @@ export function StoryViewsNRepliesModal({
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={view.recipient.acceptedMessageRequest}
|
|
||||||
avatarUrl={view.recipient.avatarUrl}
|
avatarUrl={view.recipient.avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={getAvatarColor(view.recipient.color)}
|
color={getAvatarColor(view.recipient.color)}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={Boolean(view.recipient.isMe)}
|
|
||||||
profileName={view.recipient.profileName}
|
profileName={view.recipient.profileName}
|
||||||
sharedGroupNames={view.recipient.sharedGroupNames || []}
|
sharedGroupNames={view.recipient.sharedGroupNames || []}
|
||||||
size={AvatarSize.TWENTY_EIGHT}
|
size={AvatarSize.TWENTY_EIGHT}
|
||||||
@@ -564,13 +562,11 @@ function ReplyOrReactionMessage({
|
|||||||
>
|
>
|
||||||
<div className="StoryViewsNRepliesModal__reaction--container">
|
<div className="StoryViewsNRepliesModal__reaction--container">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={reply.author.acceptedMessageRequest}
|
|
||||||
avatarUrl={reply.author.avatarUrl}
|
avatarUrl={reply.author.avatarUrl}
|
||||||
badge={getPreferredBadge(reply.author.badges)}
|
badge={getPreferredBadge(reply.author.badges)}
|
||||||
color={getAvatarColor(reply.author.color)}
|
color={getAvatarColor(reply.author.color)}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={Boolean(reply.author.isMe)}
|
|
||||||
profileName={reply.author.profileName}
|
profileName={reply.author.profileName}
|
||||||
sharedGroupNames={reply.author.sharedGroupNames || []}
|
sharedGroupNames={reply.author.sharedGroupNames || []}
|
||||||
size={AvatarSize.TWENTY_EIGHT}
|
size={AvatarSize.TWENTY_EIGHT}
|
||||||
|
@@ -68,7 +68,8 @@ export default {
|
|||||||
toggleSafetyNumberModal: action('toggleSafetyNumberModal'),
|
toggleSafetyNumberModal: action('toggleSafetyNumberModal'),
|
||||||
toggleProfileNameWarningModal: action('toggleProfileNameWarningModal'),
|
toggleProfileNameWarningModal: action('toggleProfileNameWarningModal'),
|
||||||
updateSharedGroups: action('updateSharedGroups'),
|
updateSharedGroups: action('updateSharedGroups'),
|
||||||
unblurAvatar: action('unblurAvatar'),
|
startAvatarDownload: action('startAvatarDownload'),
|
||||||
|
pendingAvatarDownload: false,
|
||||||
conversation,
|
conversation,
|
||||||
fromOrAddedByTrustedContact: false,
|
fromOrAddedByTrustedContact: false,
|
||||||
isSignalConnection: false,
|
isSignalConnection: false,
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
// Copyright 2024 Signal Messenger, LLC
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { type ReactNode, useCallback, useEffect } from 'react';
|
import React, { type ReactNode, useCallback, useEffect, useMemo } from 'react';
|
||||||
import type { ConversationType } from '../../state/ducks/conversations';
|
import type { ConversationType } from '../../state/ducks/conversations';
|
||||||
import type { LocalizerType } from '../../types/Util';
|
import type { LocalizerType } from '../../types/Util';
|
||||||
import { isInSystemContacts } from '../../util/isInSystemContacts';
|
import { isInSystemContacts } from '../../util/isInSystemContacts';
|
||||||
import { shouldBlurAvatar } from '../../util/shouldBlurAvatar';
|
|
||||||
import { Avatar, AvatarBlur, AvatarSize } from '../Avatar';
|
import { Avatar, AvatarBlur, AvatarSize } from '../Avatar';
|
||||||
import { Modal } from '../Modal';
|
import { Modal } from '../Modal';
|
||||||
import { UserText } from '../UserText';
|
import { UserText } from '../UserText';
|
||||||
@@ -28,11 +27,12 @@ export type PropsType = Readonly<{
|
|||||||
conversation: ConversationType;
|
conversation: ConversationType;
|
||||||
fromOrAddedByTrustedContact?: boolean;
|
fromOrAddedByTrustedContact?: boolean;
|
||||||
isSignalConnection: boolean;
|
isSignalConnection: boolean;
|
||||||
|
pendingAvatarDownload?: boolean;
|
||||||
|
startAvatarDownload?: (id: string) => unknown;
|
||||||
toggleSignalConnectionsModal: () => void;
|
toggleSignalConnectionsModal: () => void;
|
||||||
toggleSafetyNumberModal: (id: string) => void;
|
toggleSafetyNumberModal: (id: string) => void;
|
||||||
toggleProfileNameWarningModal: () => void;
|
toggleProfileNameWarningModal: () => void;
|
||||||
updateSharedGroups: (id: string) => void;
|
updateSharedGroups: (id: string) => void;
|
||||||
unblurAvatar: (conversationId: string) => void;
|
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export function AboutContactModal({
|
export function AboutContactModal({
|
||||||
@@ -40,30 +40,44 @@ export function AboutContactModal({
|
|||||||
conversation,
|
conversation,
|
||||||
fromOrAddedByTrustedContact,
|
fromOrAddedByTrustedContact,
|
||||||
isSignalConnection,
|
isSignalConnection,
|
||||||
|
pendingAvatarDownload,
|
||||||
|
startAvatarDownload,
|
||||||
toggleSignalConnectionsModal,
|
toggleSignalConnectionsModal,
|
||||||
toggleSafetyNumberModal,
|
toggleSafetyNumberModal,
|
||||||
toggleProfileNameWarningModal,
|
toggleProfileNameWarningModal,
|
||||||
updateSharedGroups,
|
updateSharedGroups,
|
||||||
unblurAvatar,
|
|
||||||
onClose,
|
onClose,
|
||||||
onOpenNotePreviewModal,
|
onOpenNotePreviewModal,
|
||||||
}: PropsType): JSX.Element {
|
}: PropsType): JSX.Element {
|
||||||
const { isMe } = conversation;
|
const { avatarUrl, hasAvatar, isMe } = conversation;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Kick off the expensive hydration of the current sharedGroupNames
|
// Kick off the expensive hydration of the current sharedGroupNames
|
||||||
updateSharedGroups(conversation.id);
|
updateSharedGroups(conversation.id);
|
||||||
}, [conversation.id, updateSharedGroups]);
|
}, [conversation.id, updateSharedGroups]);
|
||||||
|
|
||||||
const avatarBlur = shouldBlurAvatar(conversation)
|
// If hasAvatar is true, we show the download button instead of blur
|
||||||
|
const enableClickToLoad = !avatarUrl && !isMe && hasAvatar;
|
||||||
|
|
||||||
|
const avatarBlur = enableClickToLoad
|
||||||
? AvatarBlur.BlurPictureWithClickToView
|
? AvatarBlur.BlurPictureWithClickToView
|
||||||
: AvatarBlur.NoBlur;
|
: AvatarBlur.NoBlur;
|
||||||
|
|
||||||
const onAvatarClick = useCallback(() => {
|
const avatarOnClick = useMemo(() => {
|
||||||
if (avatarBlur === AvatarBlur.BlurPictureWithClickToView) {
|
if (!enableClickToLoad) {
|
||||||
unblurAvatar(conversation.id);
|
return undefined;
|
||||||
}
|
}
|
||||||
}, [avatarBlur, unblurAvatar, conversation.id]);
|
return () => {
|
||||||
|
if (!pendingAvatarDownload && startAvatarDownload) {
|
||||||
|
startAvatarDownload(conversation.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
conversation.id,
|
||||||
|
startAvatarDownload,
|
||||||
|
enableClickToLoad,
|
||||||
|
pendingAvatarDownload,
|
||||||
|
]);
|
||||||
|
|
||||||
const onSignalConnectionClick = useCallback(
|
const onSignalConnectionClick = useCallback(
|
||||||
(ev: React.MouseEvent) => {
|
(ev: React.MouseEvent) => {
|
||||||
@@ -131,20 +145,20 @@ export function AboutContactModal({
|
|||||||
>
|
>
|
||||||
<div className="AboutContactModal__row AboutContactModal__row--centered">
|
<div className="AboutContactModal__row AboutContactModal__row--centered">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={conversation.acceptedMessageRequest}
|
avatarPlaceholderGradient={conversation.avatarPlaceholderGradient}
|
||||||
avatarUrl={conversation.avatarUrl}
|
avatarUrl={conversation.avatarUrl}
|
||||||
blur={avatarBlur}
|
blur={avatarBlur}
|
||||||
onClick={avatarBlur === AvatarBlur.NoBlur ? undefined : onAvatarClick}
|
onClick={avatarOnClick}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={conversation.color}
|
color={conversation.color}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
|
hasAvatar={conversation.hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={conversation.isMe}
|
loading={pendingAvatarDownload && !conversation.avatarUrl}
|
||||||
profileName={conversation.profileName}
|
profileName={conversation.profileName}
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
size={AvatarSize.TWO_HUNDRED_SIXTEEN}
|
size={AvatarSize.TWO_HUNDRED_SIXTEEN}
|
||||||
title={conversation.title}
|
title={conversation.title}
|
||||||
unblurredAvatarUrl={conversation.unblurredAvatarUrl}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -53,6 +53,7 @@ export default {
|
|||||||
),
|
),
|
||||||
removeMemberFromGroup: action('removeMemberFromGroup'),
|
removeMemberFromGroup: action('removeMemberFromGroup'),
|
||||||
showConversation: action('showConversation'),
|
showConversation: action('showConversation'),
|
||||||
|
startAvatarDownload: action('startAvatarDownload'),
|
||||||
theme: ThemeType.light,
|
theme: ThemeType.light,
|
||||||
toggleAboutContactModal: action('AboutContactModal'),
|
toggleAboutContactModal: action('AboutContactModal'),
|
||||||
toggleAdmin: action('toggleAdmin'),
|
toggleAdmin: action('toggleAdmin'),
|
||||||
|
@@ -14,7 +14,7 @@ import type { LocalizerType, ThemeType } from '../../types/Util';
|
|||||||
import type { ViewUserStoriesActionCreatorType } from '../../state/ducks/stories';
|
import type { ViewUserStoriesActionCreatorType } from '../../state/ducks/stories';
|
||||||
import { StoryViewModeType } from '../../types/Stories';
|
import { StoryViewModeType } from '../../types/Stories';
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
import { Avatar, AvatarSize } from '../Avatar';
|
import { Avatar, AvatarBlur, AvatarSize } from '../Avatar';
|
||||||
import { AvatarLightbox } from '../AvatarLightbox';
|
import { AvatarLightbox } from '../AvatarLightbox';
|
||||||
import { BadgeDialog } from '../BadgeDialog';
|
import { BadgeDialog } from '../BadgeDialog';
|
||||||
import { ConfirmationDialog } from '../ConfirmationDialog';
|
import { ConfirmationDialog } from '../ConfirmationDialog';
|
||||||
@@ -55,6 +55,7 @@ type PropsActionType = {
|
|||||||
onOutgoingVideoCallInConversation: (conversationId: string) => unknown;
|
onOutgoingVideoCallInConversation: (conversationId: string) => unknown;
|
||||||
removeMemberFromGroup: (conversationId: string, contactId: string) => void;
|
removeMemberFromGroup: (conversationId: string, contactId: string) => void;
|
||||||
showConversation: ShowConversationType;
|
showConversation: ShowConversationType;
|
||||||
|
startAvatarDownload: () => void;
|
||||||
toggleAdmin: (conversationId: string, contactId: string) => void;
|
toggleAdmin: (conversationId: string, contactId: string) => void;
|
||||||
toggleAboutContactModal: (conversationId: string) => unknown;
|
toggleAboutContactModal: (conversationId: string) => unknown;
|
||||||
togglePip: () => void;
|
togglePip: () => void;
|
||||||
@@ -98,6 +99,7 @@ export function ContactModal({
|
|||||||
onOutgoingVideoCallInConversation,
|
onOutgoingVideoCallInConversation,
|
||||||
removeMemberFromGroup,
|
removeMemberFromGroup,
|
||||||
showConversation,
|
showConversation,
|
||||||
|
startAvatarDownload,
|
||||||
theme,
|
theme,
|
||||||
toggleAboutContactModal,
|
toggleAboutContactModal,
|
||||||
toggleAddUserToAnotherGroupModal,
|
toggleAddUserToAnotherGroupModal,
|
||||||
@@ -310,13 +312,18 @@ export function ContactModal({
|
|||||||
>
|
>
|
||||||
<div className="ContactModal">
|
<div className="ContactModal">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={contact.acceptedMessageRequest}
|
avatarPlaceholderGradient={contact.avatarPlaceholderGradient}
|
||||||
avatarUrl={contact.avatarUrl}
|
avatarUrl={contact.avatarUrl}
|
||||||
badge={preferredBadge}
|
badge={preferredBadge}
|
||||||
|
blur={
|
||||||
|
!contact.avatarUrl && !contact.isMe && contact.hasAvatar
|
||||||
|
? AvatarBlur.BlurPictureWithClickToView
|
||||||
|
: AvatarBlur.NoBlur
|
||||||
|
}
|
||||||
color={contact.color}
|
color={contact.color}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
|
hasAvatar={contact.hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={contact.isMe}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (conversation && hasStories) {
|
if (conversation && hasStories) {
|
||||||
viewUserStories({
|
viewUserStories({
|
||||||
@@ -324,6 +331,12 @@ export function ContactModal({
|
|||||||
storyViewMode: StoryViewModeType.User,
|
storyViewMode: StoryViewModeType.User,
|
||||||
});
|
});
|
||||||
hideContactModal();
|
hideContactModal();
|
||||||
|
} else if (
|
||||||
|
!contact.avatarUrl &&
|
||||||
|
!contact.isMe &&
|
||||||
|
contact.hasAvatar
|
||||||
|
) {
|
||||||
|
startAvatarDownload();
|
||||||
} else {
|
} else {
|
||||||
setView(ContactModalView.ShowingAvatar);
|
setView(ContactModalView.ShowingAvatar);
|
||||||
}
|
}
|
||||||
@@ -335,7 +348,6 @@ export function ContactModal({
|
|||||||
storyRing={hasStories}
|
storyRing={hasStories}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
title={contact.title}
|
title={contact.title}
|
||||||
unblurredAvatarUrl={contact.unblurredAvatarUrl}
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -468,9 +480,11 @@ export function ContactModal({
|
|||||||
case ContactModalView.ShowingAvatar:
|
case ContactModalView.ShowingAvatar:
|
||||||
return (
|
return (
|
||||||
<AvatarLightbox
|
<AvatarLightbox
|
||||||
|
avatarPlaceholderGradient={contact.avatarPlaceholderGradient}
|
||||||
avatarColor={contact.color}
|
avatarColor={contact.color}
|
||||||
avatarUrl={contact.avatarUrl}
|
avatarUrl={contact.avatarUrl}
|
||||||
conversationTitle={contact.title}
|
conversationTitle={contact.title}
|
||||||
|
hasAvatar={contact.hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onClose={() => setView(ContactModalView.Default)}
|
onClose={() => setView(ContactModalView.Default)}
|
||||||
/>
|
/>
|
||||||
|
@@ -460,13 +460,17 @@ function HeaderContent({
|
|||||||
const avatar = (
|
const avatar = (
|
||||||
<span className="module-ConversationHeader__header__avatar">
|
<span className="module-ConversationHeader__header__avatar">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={conversation.acceptedMessageRequest}
|
avatarPlaceholderGradient={
|
||||||
|
conversation.gradientStart && conversation.gradientEnd
|
||||||
|
? [conversation.gradientStart, conversation.gradientEnd]
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
avatarUrl={conversation.avatarUrl ?? undefined}
|
avatarUrl={conversation.avatarUrl ?? undefined}
|
||||||
badge={badge ?? undefined}
|
badge={badge ?? undefined}
|
||||||
color={conversation.color ?? undefined}
|
color={conversation.color ?? undefined}
|
||||||
conversationType={conversation.type}
|
conversationType={conversation.type}
|
||||||
|
hasAvatar={conversation.hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={conversation.isMe}
|
|
||||||
noteToSelf={conversation.isMe}
|
noteToSelf={conversation.isMe}
|
||||||
onClick={hasStories ? onViewUserStories : onClick}
|
onClick={hasStories ? onViewUserStories : onClick}
|
||||||
phoneNumber={conversation.phoneNumber ?? undefined}
|
phoneNumber={conversation.phoneNumber ?? undefined}
|
||||||
@@ -477,7 +481,6 @@ function HeaderContent({
|
|||||||
storyRing={conversation.isMe ? undefined : (hasStories ?? undefined)}
|
storyRing={conversation.isMe ? undefined : (hasStories ?? undefined)}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
title={conversation.title}
|
title={conversation.title}
|
||||||
unblurredAvatarUrl={conversation.unblurredAvatarUrl ?? undefined}
|
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
@@ -23,12 +23,13 @@ export default {
|
|||||||
i18n,
|
i18n,
|
||||||
isDirectConvoAndHasNickname: false,
|
isDirectConvoAndHasNickname: false,
|
||||||
theme: ThemeType.light,
|
theme: ThemeType.light,
|
||||||
unblurAvatar: action('unblurAvatar'),
|
|
||||||
updateSharedGroups: action('updateSharedGroups'),
|
updateSharedGroups: action('updateSharedGroups'),
|
||||||
viewUserStories: action('viewUserStories'),
|
viewUserStories: action('viewUserStories'),
|
||||||
toggleAboutContactModal: action('toggleAboutContactModal'),
|
toggleAboutContactModal: action('toggleAboutContactModal'),
|
||||||
toggleProfileNameWarningModal: action('toggleProfileNameWarningModal'),
|
toggleProfileNameWarningModal: action('toggleProfileNameWarningModal'),
|
||||||
openConversationDetails: action('openConversationDetails'),
|
openConversationDetails: action('openConversationDetails'),
|
||||||
|
startAvatarDownload: action('startAvatarDownload'),
|
||||||
|
pendingAvatarDownload: false,
|
||||||
},
|
},
|
||||||
} satisfies Meta<Props>;
|
} satisfies Meta<Props>;
|
||||||
|
|
||||||
|
@@ -13,7 +13,6 @@ import type { LocalizerType, ThemeType } from '../../types/Util';
|
|||||||
import type { HasStories } from '../../types/Stories';
|
import type { HasStories } from '../../types/Stories';
|
||||||
import type { ViewUserStoriesActionCreatorType } from '../../state/ducks/stories';
|
import type { ViewUserStoriesActionCreatorType } from '../../state/ducks/stories';
|
||||||
import { StoryViewModeType } from '../../types/Stories';
|
import { StoryViewModeType } from '../../types/Stories';
|
||||||
import { shouldBlurAvatar } from '../../util/shouldBlurAvatar';
|
|
||||||
import { Button, ButtonVariant } from '../Button';
|
import { Button, ButtonVariant } from '../Button';
|
||||||
import { SafetyTipsModal } from '../SafetyTipsModal';
|
import { SafetyTipsModal } from '../SafetyTipsModal';
|
||||||
import { I18n } from '../I18n';
|
import { I18n } from '../I18n';
|
||||||
@@ -23,6 +22,7 @@ export type Props = {
|
|||||||
acceptedMessageRequest?: boolean;
|
acceptedMessageRequest?: boolean;
|
||||||
fromOrAddedByTrustedContact?: boolean;
|
fromOrAddedByTrustedContact?: boolean;
|
||||||
groupDescription?: string;
|
groupDescription?: string;
|
||||||
|
hasAvatar?: boolean;
|
||||||
hasStories?: HasStories;
|
hasStories?: HasStories;
|
||||||
id: string;
|
id: string;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
@@ -31,10 +31,10 @@ export type Props = {
|
|||||||
isSignalConversation?: boolean;
|
isSignalConversation?: boolean;
|
||||||
membersCount?: number;
|
membersCount?: number;
|
||||||
openConversationDetails?: () => unknown;
|
openConversationDetails?: () => unknown;
|
||||||
|
pendingAvatarDownload?: boolean;
|
||||||
phoneNumber?: string;
|
phoneNumber?: string;
|
||||||
sharedGroupNames?: ReadonlyArray<string>;
|
sharedGroupNames?: ReadonlyArray<string>;
|
||||||
unblurAvatar: (conversationId: string) => void;
|
startAvatarDownload: () => void;
|
||||||
unblurredAvatarUrl?: string;
|
|
||||||
updateSharedGroups: (conversationId: string) => unknown;
|
updateSharedGroups: (conversationId: string) => unknown;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
viewUserStories: ViewUserStoriesActionCreatorType;
|
viewUserStories: ViewUserStoriesActionCreatorType;
|
||||||
@@ -57,6 +57,7 @@ const renderExtraInformation = ({
|
|||||||
sharedGroupNames,
|
sharedGroupNames,
|
||||||
}: Pick<
|
}: Pick<
|
||||||
Props,
|
Props,
|
||||||
|
| 'avatarPlaceholderGradient'
|
||||||
| 'acceptedMessageRequest'
|
| 'acceptedMessageRequest'
|
||||||
| 'conversationType'
|
| 'conversationType'
|
||||||
| 'fromOrAddedByTrustedContact'
|
| 'fromOrAddedByTrustedContact'
|
||||||
@@ -227,6 +228,7 @@ function ReleaseNotesExtraInformation({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ConversationHero({
|
export function ConversationHero({
|
||||||
|
avatarPlaceholderGradient,
|
||||||
i18n,
|
i18n,
|
||||||
about,
|
about,
|
||||||
acceptedMessageRequest,
|
acceptedMessageRequest,
|
||||||
@@ -236,6 +238,7 @@ export function ConversationHero({
|
|||||||
conversationType,
|
conversationType,
|
||||||
fromOrAddedByTrustedContact,
|
fromOrAddedByTrustedContact,
|
||||||
groupDescription,
|
groupDescription,
|
||||||
|
hasAvatar,
|
||||||
hasStories,
|
hasStories,
|
||||||
id,
|
id,
|
||||||
isDirectConvoAndHasNickname,
|
isDirectConvoAndHasNickname,
|
||||||
@@ -243,13 +246,13 @@ export function ConversationHero({
|
|||||||
openConversationDetails,
|
openConversationDetails,
|
||||||
isSignalConversation,
|
isSignalConversation,
|
||||||
membersCount,
|
membersCount,
|
||||||
|
pendingAvatarDownload,
|
||||||
sharedGroupNames = [],
|
sharedGroupNames = [],
|
||||||
phoneNumber,
|
phoneNumber,
|
||||||
profileName,
|
profileName,
|
||||||
|
startAvatarDownload,
|
||||||
theme,
|
theme,
|
||||||
title,
|
title,
|
||||||
unblurAvatar,
|
|
||||||
unblurredAvatarUrl,
|
|
||||||
updateSharedGroups,
|
updateSharedGroups,
|
||||||
viewUserStories,
|
viewUserStories,
|
||||||
toggleAboutContactModal,
|
toggleAboutContactModal,
|
||||||
@@ -264,17 +267,14 @@ export function ConversationHero({
|
|||||||
|
|
||||||
let avatarBlur: AvatarBlur = AvatarBlur.NoBlur;
|
let avatarBlur: AvatarBlur = AvatarBlur.NoBlur;
|
||||||
let avatarOnClick: undefined | (() => void);
|
let avatarOnClick: undefined | (() => void);
|
||||||
if (
|
|
||||||
shouldBlurAvatar({
|
if (!avatarUrl && !isMe && hasAvatar) {
|
||||||
acceptedMessageRequest,
|
|
||||||
avatarUrl,
|
|
||||||
isMe,
|
|
||||||
sharedGroupNames,
|
|
||||||
unblurredAvatarUrl,
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
avatarBlur = AvatarBlur.BlurPictureWithClickToView;
|
avatarBlur = AvatarBlur.BlurPictureWithClickToView;
|
||||||
avatarOnClick = () => unblurAvatar(id);
|
avatarOnClick = () => {
|
||||||
|
if (!pendingAvatarDownload) {
|
||||||
|
startAvatarDownload();
|
||||||
|
}
|
||||||
|
};
|
||||||
} else if (hasStories) {
|
} else if (hasStories) {
|
||||||
avatarOnClick = () => {
|
avatarOnClick = () => {
|
||||||
viewUserStories({
|
viewUserStories({
|
||||||
@@ -312,7 +312,7 @@ export function ConversationHero({
|
|||||||
<>
|
<>
|
||||||
<div className="module-conversation-hero">
|
<div className="module-conversation-hero">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={acceptedMessageRequest}
|
avatarPlaceholderGradient={avatarPlaceholderGradient}
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
badge={badge}
|
badge={badge}
|
||||||
blur={avatarBlur}
|
blur={avatarBlur}
|
||||||
@@ -320,7 +320,8 @@ export function ConversationHero({
|
|||||||
color={color}
|
color={color}
|
||||||
conversationType={conversationType}
|
conversationType={conversationType}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={isMe}
|
hasAvatar={hasAvatar}
|
||||||
|
loading={pendingAvatarDownload && !avatarUrl}
|
||||||
noteToSelf={isMe}
|
noteToSelf={isMe}
|
||||||
onClick={avatarOnClick}
|
onClick={avatarOnClick}
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
|
@@ -279,18 +279,19 @@ export type PropsData = {
|
|||||||
contact?: ReadonlyDeep<EmbeddedContactForUIType>;
|
contact?: ReadonlyDeep<EmbeddedContactForUIType>;
|
||||||
author: Pick<
|
author: Pick<
|
||||||
ConversationType,
|
ConversationType,
|
||||||
|
| 'avatarPlaceholderGradient'
|
||||||
| 'acceptedMessageRequest'
|
| 'acceptedMessageRequest'
|
||||||
| 'avatarUrl'
|
| 'avatarUrl'
|
||||||
| 'badges'
|
| 'badges'
|
||||||
| 'color'
|
| 'color'
|
||||||
| 'firstName'
|
| 'firstName'
|
||||||
|
| 'hasAvatar'
|
||||||
| 'id'
|
| 'id'
|
||||||
| 'isMe'
|
| 'isMe'
|
||||||
| 'phoneNumber'
|
| 'phoneNumber'
|
||||||
| 'profileName'
|
| 'profileName'
|
||||||
| 'sharedGroupNames'
|
| 'sharedGroupNames'
|
||||||
| 'title'
|
| 'title'
|
||||||
| 'unblurredAvatarUrl'
|
|
||||||
>;
|
>;
|
||||||
conversationType: ConversationTypeType;
|
conversationType: ConversationTypeType;
|
||||||
attachments?: ReadonlyArray<AttachmentForUIType>;
|
attachments?: ReadonlyArray<AttachmentForUIType>;
|
||||||
@@ -1578,12 +1579,10 @@ export class Message extends React.PureComponent<Props, State> {
|
|||||||
{first.isCallLink && (
|
{first.isCallLink && (
|
||||||
<div className="module-message__link-preview__call-link-icon">
|
<div className="module-message__link-preview__call-link-icon">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest
|
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={getColorForCallLink(getKeyFromCallLink(first.url))}
|
color={getColorForCallLink(getKeyFromCallLink(first.url))}
|
||||||
conversationType="callLink"
|
conversationType="callLink"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={false}
|
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
size={64}
|
size={64}
|
||||||
title={title ?? i18n('icu:calling__call-link-default-title')}
|
title={title ?? i18n('icu:calling__call-link-default-title')}
|
||||||
@@ -2173,13 +2172,11 @@ export class Message extends React.PureComponent<Props, State> {
|
|||||||
<AvatarSpacer size={GROUP_AVATAR_SIZE} />
|
<AvatarSpacer size={GROUP_AVATAR_SIZE} />
|
||||||
) : (
|
) : (
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={author.acceptedMessageRequest}
|
|
||||||
avatarUrl={author.avatarUrl}
|
avatarUrl={author.avatarUrl}
|
||||||
badge={getPreferredBadge(author.badges)}
|
badge={getPreferredBadge(author.badges)}
|
||||||
color={author.color}
|
color={author.color}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={author.isMe}
|
|
||||||
onClick={event => {
|
onClick={event => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -2192,7 +2189,6 @@ export class Message extends React.PureComponent<Props, State> {
|
|||||||
size={GROUP_AVATAR_SIZE}
|
size={GROUP_AVATAR_SIZE}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
title={author.title}
|
title={author.title}
|
||||||
unblurredAvatarUrl={author.unblurredAvatarUrl}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -49,7 +49,6 @@ export type Contact = Pick<
|
|||||||
| 'profileName'
|
| 'profileName'
|
||||||
| 'sharedGroupNames'
|
| 'sharedGroupNames'
|
||||||
| 'title'
|
| 'title'
|
||||||
| 'unblurredAvatarUrl'
|
|
||||||
> & {
|
> & {
|
||||||
status?: SendStatus;
|
status?: SendStatus;
|
||||||
statusTimestamp?: number;
|
statusTimestamp?: number;
|
||||||
@@ -168,34 +167,28 @@ export function MessageDetail({
|
|||||||
|
|
||||||
function renderAvatar(contact: Contact): JSX.Element {
|
function renderAvatar(contact: Contact): JSX.Element {
|
||||||
const {
|
const {
|
||||||
acceptedMessageRequest,
|
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
badges,
|
badges,
|
||||||
color,
|
color,
|
||||||
isMe,
|
|
||||||
phoneNumber,
|
phoneNumber,
|
||||||
profileName,
|
profileName,
|
||||||
sharedGroupNames,
|
sharedGroupNames,
|
||||||
title,
|
title,
|
||||||
unblurredAvatarUrl,
|
|
||||||
} = contact;
|
} = contact;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={acceptedMessageRequest}
|
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
badge={getPreferredBadge(badges)}
|
badge={getPreferredBadge(badges)}
|
||||||
color={color}
|
color={color}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={isMe}
|
|
||||||
phoneNumber={phoneNumber}
|
phoneNumber={phoneNumber}
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
title={title}
|
title={title}
|
||||||
sharedGroupNames={sharedGroupNames}
|
sharedGroupNames={sharedGroupNames}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
unblurredAvatarUrl={unblurredAvatarUrl}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -257,13 +257,11 @@ export const ReactionViewer = React.forwardRef<HTMLDivElement, Props>(
|
|||||||
>
|
>
|
||||||
<div className="module-reaction-viewer__body__row__avatar">
|
<div className="module-reaction-viewer__body__row__avatar">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={from.acceptedMessageRequest}
|
|
||||||
avatarUrl={from.avatarUrl}
|
avatarUrl={from.avatarUrl}
|
||||||
badge={getPreferredBadge(from.badges)}
|
badge={getPreferredBadge(from.badges)}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
sharedGroupNames={from.sharedGroupNames}
|
sharedGroupNames={from.sharedGroupNames}
|
||||||
size={32}
|
size={32}
|
||||||
isMe={from.isMe}
|
|
||||||
color={from.color}
|
color={from.color}
|
||||||
profileName={from.profileName}
|
profileName={from.profileName}
|
||||||
phoneNumber={from.phoneNumber}
|
phoneNumber={from.phoneNumber}
|
||||||
|
@@ -124,12 +124,10 @@ export function Thumbnail({
|
|||||||
return (
|
return (
|
||||||
<div className={getClassName('__icon-container-call-link')}>
|
<div className={getClassName('__icon-container-call-link')}>
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest
|
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
color={getColorForCallLink(getKeyFromCallLink(url))}
|
color={getColorForCallLink(getKeyFromCallLink(url))}
|
||||||
conversationType="callLink"
|
conversationType="callLink"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={false}
|
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
size={64}
|
size={64}
|
||||||
title={title ?? i18n('icu:calling__call-link-default-title')}
|
title={title ?? i18n('icu:calling__call-link-default-title')}
|
||||||
|
@@ -334,8 +334,6 @@ const actions = () => ({
|
|||||||
closeContactSpoofingReview: action('closeContactSpoofingReview'),
|
closeContactSpoofingReview: action('closeContactSpoofingReview'),
|
||||||
reviewConversationNameCollision: action('reviewConversationNameCollision'),
|
reviewConversationNameCollision: action('reviewConversationNameCollision'),
|
||||||
|
|
||||||
unblurAvatar: action('unblurAvatar'),
|
|
||||||
|
|
||||||
peekGroupCallForTheFirstTime: action('peekGroupCallForTheFirstTime'),
|
peekGroupCallForTheFirstTime: action('peekGroupCallForTheFirstTime'),
|
||||||
peekGroupCallIfItHasMembers: action('peekGroupCallIfItHasMembers'),
|
peekGroupCallIfItHasMembers: action('peekGroupCallIfItHasMembers'),
|
||||||
|
|
||||||
@@ -346,6 +344,8 @@ const actions = () => ({
|
|||||||
onOpenMessageRequestActionsConfirmation: action(
|
onOpenMessageRequestActionsConfirmation: action(
|
||||||
'onOpenMessageRequestActionsConfirmation'
|
'onOpenMessageRequestActionsConfirmation'
|
||||||
),
|
),
|
||||||
|
|
||||||
|
startAvatarDownload: action('startAvatarDownload'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderItem = ({
|
const renderItem = ({
|
||||||
@@ -416,7 +416,8 @@ const renderHeroRow = () => {
|
|||||||
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party']}
|
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party']}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
title={getTitle()}
|
title={getTitle()}
|
||||||
unblurAvatar={action('unblurAvatar')}
|
startAvatarDownload={action('startAvatarDownload')}
|
||||||
|
pendingAvatarDownload={false}
|
||||||
updateSharedGroups={noop}
|
updateSharedGroups={noop}
|
||||||
viewUserStories={action('viewUserStories')}
|
viewUserStories={action('viewUserStories')}
|
||||||
toggleAboutContactModal={action('toggleAboutContactModal')}
|
toggleAboutContactModal={action('toggleAboutContactModal')}
|
||||||
|
@@ -123,13 +123,11 @@ function TypingBubbleAvatar({
|
|||||||
return (
|
return (
|
||||||
<animated.div className="module-message__typing-avatar" style={springProps}>
|
<animated.div className="module-message__typing-avatar" style={springProps}>
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={contact.acceptedMessageRequest}
|
|
||||||
avatarUrl={contact.avatarUrl}
|
avatarUrl={contact.avatarUrl}
|
||||||
badge={getPreferredBadge(contact.badges)}
|
badge={getPreferredBadge(contact.badges)}
|
||||||
color={contact.color}
|
color={contact.color}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={contact.isMe}
|
|
||||||
onClick={event => {
|
onClick={event => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@@ -34,14 +34,12 @@ export function renderAvatar({
|
|||||||
|
|
||||||
const renderAttachmentDownloaded = () => (
|
const renderAttachmentDownloaded = () => (
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={false}
|
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
blur={AvatarBlur.NoBlur}
|
blur={AvatarBlur.NoBlur}
|
||||||
color={AvatarColors[0]}
|
color={AvatarColors[0]}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe
|
|
||||||
title={title}
|
title={title}
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
size={size}
|
size={size}
|
||||||
|
@@ -411,10 +411,11 @@ export function ChooseGroupMembersModal({
|
|||||||
{selectedContacts.map(contact => (
|
{selectedContacts.map(contact => (
|
||||||
<ContactPill
|
<ContactPill
|
||||||
key={contact.id}
|
key={contact.id}
|
||||||
acceptedMessageRequest={contact.acceptedMessageRequest}
|
avatarPlaceholderGradient={contact.avatarPlaceholderGradient}
|
||||||
avatarUrl={contact.avatarUrl}
|
avatarUrl={contact.avatarUrl}
|
||||||
color={contact.color}
|
color={contact.color}
|
||||||
firstName={contact.systemGivenName ?? contact.firstName}
|
firstName={contact.systemGivenName ?? contact.firstName}
|
||||||
|
hasAvatar={contact.hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={contact.isMe}
|
isMe={contact.isMe}
|
||||||
id={contact.id}
|
id={contact.id}
|
||||||
|
@@ -87,6 +87,7 @@ const createProps = (
|
|||||||
pushPanelForConversation: action('pushPanelForConversation'),
|
pushPanelForConversation: action('pushPanelForConversation'),
|
||||||
showConversation: action('showConversation'),
|
showConversation: action('showConversation'),
|
||||||
showLightbox: action('showLightbox'),
|
showLightbox: action('showLightbox'),
|
||||||
|
startAvatarDownload: action('startAvatarDownload'),
|
||||||
updateGroupAttributes: async () => {
|
updateGroupAttributes: async () => {
|
||||||
action('updateGroupAttributes')();
|
action('updateGroupAttributes')();
|
||||||
},
|
},
|
||||||
|
@@ -94,8 +94,10 @@ export type StateProps = {
|
|||||||
maxRecommendedGroupSize: number;
|
maxRecommendedGroupSize: number;
|
||||||
memberships: ReadonlyArray<GroupV2Membership>;
|
memberships: ReadonlyArray<GroupV2Membership>;
|
||||||
pendingApprovalMemberships: ReadonlyArray<GroupV2RequestingMembership>;
|
pendingApprovalMemberships: ReadonlyArray<GroupV2RequestingMembership>;
|
||||||
|
pendingAvatarDownload?: boolean;
|
||||||
pendingMemberships: ReadonlyArray<GroupV2PendingMembership>;
|
pendingMemberships: ReadonlyArray<GroupV2PendingMembership>;
|
||||||
selectedNavTab: NavTab;
|
selectedNavTab: NavTab;
|
||||||
|
startAvatarDownload: () => void;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
userAvatarData: ReadonlyArray<AvatarDataType>;
|
userAvatarData: ReadonlyArray<AvatarDataType>;
|
||||||
renderChooseGroupMembersModal: (
|
renderChooseGroupMembersModal: (
|
||||||
@@ -197,6 +199,7 @@ export function ConversationDetails({
|
|||||||
onOutgoingAudioCallInConversation,
|
onOutgoingAudioCallInConversation,
|
||||||
onOutgoingVideoCallInConversation,
|
onOutgoingVideoCallInConversation,
|
||||||
pendingApprovalMemberships,
|
pendingApprovalMemberships,
|
||||||
|
pendingAvatarDownload,
|
||||||
pendingMemberships,
|
pendingMemberships,
|
||||||
pushPanelForConversation,
|
pushPanelForConversation,
|
||||||
renderChooseGroupMembersModal,
|
renderChooseGroupMembersModal,
|
||||||
@@ -210,6 +213,7 @@ export function ConversationDetails({
|
|||||||
showContactModal,
|
showContactModal,
|
||||||
showConversation,
|
showConversation,
|
||||||
showLightbox,
|
showLightbox,
|
||||||
|
startAvatarDownload,
|
||||||
theme,
|
theme,
|
||||||
toggleAboutContactModal,
|
toggleAboutContactModal,
|
||||||
toggleSafetyNumberModal,
|
toggleSafetyNumberModal,
|
||||||
@@ -410,6 +414,8 @@ export function ConversationDetails({
|
|||||||
isGroup={isGroup}
|
isGroup={isGroup}
|
||||||
isSignalConversation={isSignalConversation}
|
isSignalConversation={isSignalConversation}
|
||||||
membersCount={conversation.membersCount ?? null}
|
membersCount={conversation.membersCount ?? null}
|
||||||
|
pendingAvatarDownload={pendingAvatarDownload ?? false}
|
||||||
|
startAvatarDownload={startAvatarDownload}
|
||||||
startEditing={(isGroupTitle: boolean) => {
|
startEditing={(isGroupTitle: boolean) => {
|
||||||
setModalState(
|
setModalState(
|
||||||
isGroupTitle
|
isGroupTitle
|
||||||
|
@@ -43,6 +43,8 @@ function Wrapper(overrideProps: Partial<Props>) {
|
|||||||
isGroup
|
isGroup
|
||||||
isMe={false}
|
isMe={false}
|
||||||
isSignalConversation={false}
|
isSignalConversation={false}
|
||||||
|
pendingAvatarDownload={false}
|
||||||
|
startAvatarDownload={action('startAvatarDownload')}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
toggleAboutContactModal={action('toggleAboutContactModal')}
|
toggleAboutContactModal={action('toggleAboutContactModal')}
|
||||||
{...overrideProps}
|
{...overrideProps}
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import { Avatar, AvatarSize } from '../../Avatar';
|
import { Avatar, AvatarBlur, AvatarSize } from '../../Avatar';
|
||||||
import { AvatarLightbox } from '../../AvatarLightbox';
|
import { AvatarLightbox } from '../../AvatarLightbox';
|
||||||
import type { ConversationType } from '../../../state/ducks/conversations';
|
import type { ConversationType } from '../../../state/ducks/conversations';
|
||||||
import { GroupDescription } from '../GroupDescription';
|
import { GroupDescription } from '../GroupDescription';
|
||||||
@@ -27,6 +27,8 @@ export type Props = {
|
|||||||
isMe: boolean;
|
isMe: boolean;
|
||||||
isSignalConversation: boolean;
|
isSignalConversation: boolean;
|
||||||
membersCount: number | null;
|
membersCount: number | null;
|
||||||
|
pendingAvatarDownload: boolean;
|
||||||
|
startAvatarDownload: () => void;
|
||||||
startEditing: (isGroupTitle: boolean) => void;
|
startEditing: (isGroupTitle: boolean) => void;
|
||||||
toggleAboutContactModal: (contactId: string) => void;
|
toggleAboutContactModal: (contactId: string) => void;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
@@ -47,6 +49,8 @@ export function ConversationDetailsHeader({
|
|||||||
isMe,
|
isMe,
|
||||||
isSignalConversation,
|
isSignalConversation,
|
||||||
membersCount,
|
membersCount,
|
||||||
|
pendingAvatarDownload,
|
||||||
|
startAvatarDownload,
|
||||||
startEditing,
|
startEditing,
|
||||||
toggleAboutContactModal,
|
toggleAboutContactModal,
|
||||||
theme,
|
theme,
|
||||||
@@ -84,8 +88,15 @@ export function ConversationDetailsHeader({
|
|||||||
preferredBadge = badges?.[0];
|
preferredBadge = badges?.[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shouldShowClickToView =
|
||||||
|
!conversation.avatarUrl && !isMe && conversation.hasAvatar;
|
||||||
|
const avatarBlur = shouldShowClickToView
|
||||||
|
? AvatarBlur.BlurPictureWithClickToView
|
||||||
|
: AvatarBlur.NoBlur;
|
||||||
|
|
||||||
const avatar = (
|
const avatar = (
|
||||||
<Avatar
|
<Avatar
|
||||||
|
blur={avatarBlur}
|
||||||
badge={preferredBadge}
|
badge={preferredBadge}
|
||||||
conversationType={conversation.type}
|
conversationType={conversation.type}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
@@ -93,9 +104,18 @@ export function ConversationDetailsHeader({
|
|||||||
{...conversation}
|
{...conversation}
|
||||||
noteToSelf={isMe}
|
noteToSelf={isMe}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
if (shouldShowClickToView) {
|
||||||
|
startAvatarDownload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
setActiveModal(ConversationDetailsHeaderActiveModal.ShowingAvatar);
|
setActiveModal(ConversationDetailsHeaderActiveModal.ShowingAvatar);
|
||||||
}}
|
}}
|
||||||
|
loading={pendingAvatarDownload}
|
||||||
onClickBadge={() => {
|
onClickBadge={() => {
|
||||||
|
if (shouldShowClickToView) {
|
||||||
|
startAvatarDownload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
setActiveModal(ConversationDetailsHeaderActiveModal.ShowingBadges);
|
setActiveModal(ConversationDetailsHeaderActiveModal.ShowingBadges);
|
||||||
}}
|
}}
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
@@ -108,9 +128,11 @@ export function ConversationDetailsHeader({
|
|||||||
case ConversationDetailsHeaderActiveModal.ShowingAvatar:
|
case ConversationDetailsHeaderActiveModal.ShowingAvatar:
|
||||||
modal = (
|
modal = (
|
||||||
<AvatarLightbox
|
<AvatarLightbox
|
||||||
|
avatarPlaceholderGradient={conversation.avatarPlaceholderGradient}
|
||||||
avatarColor={conversation.color}
|
avatarColor={conversation.color}
|
||||||
avatarUrl={conversation.avatarUrl}
|
avatarUrl={conversation.avatarUrl}
|
||||||
conversationTitle={conversation.title}
|
conversationTitle={conversation.title}
|
||||||
|
hasAvatar={conversation.hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isGroup={isGroup}
|
isGroup={isGroup}
|
||||||
noteToSelf={isMe}
|
noteToSelf={isMe}
|
||||||
|
@@ -58,17 +58,17 @@ type PropsType = {
|
|||||||
testId?: string;
|
testId?: string;
|
||||||
} & Pick<
|
} & Pick<
|
||||||
ConversationType,
|
ConversationType,
|
||||||
| 'acceptedMessageRequest'
|
| 'avatarPlaceholderGradient'
|
||||||
| 'avatarUrl'
|
| 'avatarUrl'
|
||||||
| 'color'
|
| 'color'
|
||||||
| 'groupId'
|
| 'groupId'
|
||||||
|
| 'hasAvatar'
|
||||||
| 'isMe'
|
| 'isMe'
|
||||||
| 'markedUnread'
|
| 'markedUnread'
|
||||||
| 'phoneNumber'
|
| 'phoneNumber'
|
||||||
| 'profileName'
|
| 'profileName'
|
||||||
| 'sharedGroupNames'
|
| 'sharedGroupNames'
|
||||||
| 'title'
|
| 'title'
|
||||||
| 'unblurredAvatarUrl'
|
|
||||||
| 'serviceId'
|
| 'serviceId'
|
||||||
> &
|
> &
|
||||||
(
|
(
|
||||||
@@ -79,7 +79,7 @@ type PropsType = {
|
|||||||
export const BaseConversationListItem: FunctionComponent<PropsType> =
|
export const BaseConversationListItem: FunctionComponent<PropsType> =
|
||||||
React.memo(function BaseConversationListItem(props) {
|
React.memo(function BaseConversationListItem(props) {
|
||||||
const {
|
const {
|
||||||
acceptedMessageRequest,
|
avatarPlaceholderGradient,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
avatarSize,
|
avatarSize,
|
||||||
buttonAriaLabel,
|
buttonAriaLabel,
|
||||||
@@ -88,6 +88,7 @@ export const BaseConversationListItem: FunctionComponent<PropsType> =
|
|||||||
conversationType,
|
conversationType,
|
||||||
disabled,
|
disabled,
|
||||||
groupId,
|
groupId,
|
||||||
|
hasAvatar,
|
||||||
headerDate,
|
headerDate,
|
||||||
headerName,
|
headerName,
|
||||||
i18n,
|
i18n,
|
||||||
@@ -108,7 +109,6 @@ export const BaseConversationListItem: FunctionComponent<PropsType> =
|
|||||||
shouldShowSpinner,
|
shouldShowSpinner,
|
||||||
testId: overrideTestId,
|
testId: overrideTestId,
|
||||||
title,
|
title,
|
||||||
unblurredAvatarUrl,
|
|
||||||
unreadCount,
|
unreadCount,
|
||||||
unreadMentionsCount,
|
unreadMentionsCount,
|
||||||
serviceId,
|
serviceId,
|
||||||
@@ -195,20 +195,19 @@ export const BaseConversationListItem: FunctionComponent<PropsType> =
|
|||||||
<>
|
<>
|
||||||
<div className={AVATAR_CONTAINER_CLASS_NAME}>
|
<div className={AVATAR_CONTAINER_CLASS_NAME}>
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={acceptedMessageRequest}
|
avatarPlaceholderGradient={avatarPlaceholderGradient}
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
color={color}
|
color={color}
|
||||||
conversationType={conversationType}
|
conversationType={conversationType}
|
||||||
|
hasAvatar={hasAvatar}
|
||||||
noteToSelf={isAvatarNoteToSelf}
|
noteToSelf={isAvatarNoteToSelf}
|
||||||
searchResult={isUsernameSearchResult}
|
searchResult={isUsernameSearchResult}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={isMe}
|
|
||||||
phoneNumber={phoneNumber}
|
phoneNumber={phoneNumber}
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
title={title}
|
title={title}
|
||||||
sharedGroupNames={sharedGroupNames}
|
sharedGroupNames={sharedGroupNames}
|
||||||
size={avatarSize ?? AvatarSize.FORTY_EIGHT}
|
size={avatarSize ?? AvatarSize.FORTY_EIGHT}
|
||||||
unblurredAvatarUrl={unblurredAvatarUrl}
|
|
||||||
// This is here to appease the type checker.
|
// This is here to appease the type checker.
|
||||||
{...(props.badge
|
{...(props.badge
|
||||||
? { badge: props.badge, theme: props.theme }
|
? { badge: props.badge, theme: props.theme }
|
||||||
|
@@ -37,7 +37,6 @@ export type PropsDataType = {
|
|||||||
| 'sharedGroupNames'
|
| 'sharedGroupNames'
|
||||||
| 'title'
|
| 'title'
|
||||||
| 'type'
|
| 'type'
|
||||||
| 'unblurredAvatarUrl'
|
|
||||||
| 'serviceId'
|
| 'serviceId'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@@ -55,7 +54,6 @@ type PropsType = PropsDataType & PropsHousekeepingType;
|
|||||||
export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
|
export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
|
||||||
function ContactCheckbox({
|
function ContactCheckbox({
|
||||||
about,
|
about,
|
||||||
acceptedMessageRequest,
|
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
badge,
|
badge,
|
||||||
color,
|
color,
|
||||||
@@ -71,7 +69,6 @@ export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
|
|||||||
theme,
|
theme,
|
||||||
title,
|
title,
|
||||||
type,
|
type,
|
||||||
unblurredAvatarUrl,
|
|
||||||
}) {
|
}) {
|
||||||
const disabled = Boolean(disabledReason);
|
const disabled = Boolean(disabledReason);
|
||||||
|
|
||||||
@@ -103,19 +100,16 @@ export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
|
|||||||
isChecked={isChecked}
|
isChecked={isChecked}
|
||||||
leading={
|
leading={
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={acceptedMessageRequest}
|
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
color={color}
|
color={color}
|
||||||
conversationType={type}
|
conversationType={type}
|
||||||
noteToSelf={Boolean(isMe)}
|
noteToSelf={Boolean(isMe)}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={isMe}
|
|
||||||
phoneNumber={phoneNumber}
|
phoneNumber={phoneNumber}
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
title={title}
|
title={title}
|
||||||
sharedGroupNames={sharedGroupNames}
|
sharedGroupNames={sharedGroupNames}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
unblurredAvatarUrl={unblurredAvatarUrl}
|
|
||||||
// appease the type checker.
|
// appease the type checker.
|
||||||
{...(badge ? { badge, theme } : { badge: undefined })}
|
{...(badge ? { badge, theme } : { badge: undefined })}
|
||||||
/>
|
/>
|
||||||
|
@@ -37,7 +37,6 @@ export type ContactListItemConversationType = Pick<
|
|||||||
| 'systemFamilyName'
|
| 'systemFamilyName'
|
||||||
| 'title'
|
| 'title'
|
||||||
| 'type'
|
| 'type'
|
||||||
| 'unblurredAvatarUrl'
|
|
||||||
| 'username'
|
| 'username'
|
||||||
| 'e164'
|
| 'e164'
|
||||||
| 'serviceId'
|
| 'serviceId'
|
||||||
@@ -63,7 +62,6 @@ type PropsType = PropsDataType & PropsHousekeepingType;
|
|||||||
export const ContactListItem: FunctionComponent<PropsType> = React.memo(
|
export const ContactListItem: FunctionComponent<PropsType> = React.memo(
|
||||||
function ContactListItem({
|
function ContactListItem({
|
||||||
about,
|
about,
|
||||||
acceptedMessageRequest,
|
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
badge,
|
badge,
|
||||||
color,
|
color,
|
||||||
@@ -85,7 +83,6 @@ export const ContactListItem: FunctionComponent<PropsType> = React.memo(
|
|||||||
theme,
|
theme,
|
||||||
title,
|
title,
|
||||||
type,
|
type,
|
||||||
unblurredAvatarUrl,
|
|
||||||
serviceId,
|
serviceId,
|
||||||
}) {
|
}) {
|
||||||
const [isConfirmingBlocking, setConfirmingBlocking] = useState(false);
|
const [isConfirmingBlocking, setConfirmingBlocking] = useState(false);
|
||||||
@@ -264,19 +261,16 @@ export const ContactListItem: FunctionComponent<PropsType> = React.memo(
|
|||||||
moduleClassName="ContactListItem"
|
moduleClassName="ContactListItem"
|
||||||
leading={
|
leading={
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={acceptedMessageRequest}
|
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
color={color}
|
color={color}
|
||||||
conversationType={type}
|
conversationType={type}
|
||||||
noteToSelf={Boolean(isMe)}
|
noteToSelf={Boolean(isMe)}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={isMe}
|
|
||||||
phoneNumber={phoneNumber}
|
phoneNumber={phoneNumber}
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
title={title}
|
title={title}
|
||||||
sharedGroupNames={sharedGroupNames}
|
sharedGroupNames={sharedGroupNames}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
unblurredAvatarUrl={unblurredAvatarUrl}
|
|
||||||
// This is here to appease the type checker.
|
// This is here to appease the type checker.
|
||||||
{...(badge ? { badge, theme } : { badge: undefined })}
|
{...(badge ? { badge, theme } : { badge: undefined })}
|
||||||
/>
|
/>
|
||||||
|
@@ -38,12 +38,14 @@ export type MessageStatusType = (typeof MessageStatuses)[number];
|
|||||||
|
|
||||||
export type PropsData = Pick<
|
export type PropsData = Pick<
|
||||||
ConversationType,
|
ConversationType,
|
||||||
|
| 'avatarPlaceholderGradient'
|
||||||
| 'acceptedMessageRequest'
|
| 'acceptedMessageRequest'
|
||||||
| 'avatarUrl'
|
| 'avatarUrl'
|
||||||
| 'badges'
|
| 'badges'
|
||||||
| 'color'
|
| 'color'
|
||||||
| 'draftPreview'
|
| 'draftPreview'
|
||||||
| 'groupId'
|
| 'groupId'
|
||||||
|
| 'hasAvatar'
|
||||||
| 'id'
|
| 'id'
|
||||||
| 'isBlocked'
|
| 'isBlocked'
|
||||||
| 'isMe'
|
| 'isMe'
|
||||||
@@ -62,7 +64,6 @@ export type PropsData = Pick<
|
|||||||
| 'title'
|
| 'title'
|
||||||
| 'type'
|
| 'type'
|
||||||
| 'typingContactIdTimestamps'
|
| 'typingContactIdTimestamps'
|
||||||
| 'unblurredAvatarUrl'
|
|
||||||
| 'unreadCount'
|
| 'unreadCount'
|
||||||
| 'unreadMentionsCount'
|
| 'unreadMentionsCount'
|
||||||
| 'serviceId'
|
| 'serviceId'
|
||||||
@@ -82,6 +83,7 @@ export type Props = PropsData & PropsHousekeeping;
|
|||||||
|
|
||||||
export const ConversationListItem: FunctionComponent<Props> = React.memo(
|
export const ConversationListItem: FunctionComponent<Props> = React.memo(
|
||||||
function ConversationListItem({
|
function ConversationListItem({
|
||||||
|
avatarPlaceholderGradient,
|
||||||
acceptedMessageRequest,
|
acceptedMessageRequest,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
badge,
|
badge,
|
||||||
@@ -89,6 +91,7 @@ export const ConversationListItem: FunctionComponent<Props> = React.memo(
|
|||||||
color,
|
color,
|
||||||
draftPreview,
|
draftPreview,
|
||||||
groupId,
|
groupId,
|
||||||
|
hasAvatar,
|
||||||
i18n,
|
i18n,
|
||||||
id,
|
id,
|
||||||
isBlocked,
|
isBlocked,
|
||||||
@@ -109,7 +112,6 @@ export const ConversationListItem: FunctionComponent<Props> = React.memo(
|
|||||||
title,
|
title,
|
||||||
type,
|
type,
|
||||||
typingContactIdTimestamps,
|
typingContactIdTimestamps,
|
||||||
unblurredAvatarUrl,
|
|
||||||
unreadCount,
|
unreadCount,
|
||||||
unreadMentionsCount,
|
unreadMentionsCount,
|
||||||
serviceId,
|
serviceId,
|
||||||
@@ -211,13 +213,14 @@ export const ConversationListItem: FunctionComponent<Props> = React.memo(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseConversationListItem
|
<BaseConversationListItem
|
||||||
acceptedMessageRequest={acceptedMessageRequest}
|
avatarPlaceholderGradient={avatarPlaceholderGradient}
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
badge={badge}
|
badge={badge}
|
||||||
buttonAriaLabel={buttonAriaLabel}
|
buttonAriaLabel={buttonAriaLabel}
|
||||||
color={color}
|
color={color}
|
||||||
conversationType={type}
|
conversationType={type}
|
||||||
groupId={groupId}
|
groupId={groupId}
|
||||||
|
hasAvatar={hasAvatar}
|
||||||
headerDate={lastUpdated}
|
headerDate={lastUpdated}
|
||||||
headerName={headerName}
|
headerName={headerName}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
@@ -237,7 +240,6 @@ export const ConversationListItem: FunctionComponent<Props> = React.memo(
|
|||||||
title={title}
|
title={title}
|
||||||
unreadCount={unreadCount}
|
unreadCount={unreadCount}
|
||||||
unreadMentionsCount={unreadMentionsCount}
|
unreadMentionsCount={unreadMentionsCount}
|
||||||
unblurredAvatarUrl={unblurredAvatarUrl}
|
|
||||||
serviceId={serviceId}
|
serviceId={serviceId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@@ -16,7 +16,7 @@ export enum DisabledReason {
|
|||||||
|
|
||||||
export type GroupListItemConversationType = Pick<
|
export type GroupListItemConversationType = Pick<
|
||||||
ConversationType,
|
ConversationType,
|
||||||
'id' | 'title' | 'avatarUrl'
|
'avatarPlaceholderGradient' | 'id' | 'title' | 'avatarUrl' | 'hasAvatar'
|
||||||
> & {
|
> & {
|
||||||
disabledReason: DisabledReason | undefined;
|
disabledReason: DisabledReason | undefined;
|
||||||
membersCount: number;
|
membersCount: number;
|
||||||
@@ -55,11 +55,11 @@ export function GroupListItem({
|
|||||||
<ListTile
|
<ListTile
|
||||||
leading={
|
leading={
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest
|
avatarPlaceholderGradient={group.avatarPlaceholderGradient}
|
||||||
avatarUrl={group.avatarUrl}
|
avatarUrl={group.avatarUrl}
|
||||||
conversationType="group"
|
conversationType="group"
|
||||||
|
hasAvatar={group.hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={false}
|
|
||||||
title={group.title}
|
title={group.title}
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
|
@@ -48,7 +48,6 @@ export type PropsDataType = {
|
|||||||
| 'sharedGroupNames'
|
| 'sharedGroupNames'
|
||||||
| 'title'
|
| 'title'
|
||||||
| 'type'
|
| 'type'
|
||||||
| 'unblurredAvatarUrl'
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
to: Pick<
|
to: Pick<
|
||||||
@@ -183,7 +182,6 @@ export const MessageSearchResult: FunctionComponent<PropsType> = React.memo(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseConversationListItem
|
<BaseConversationListItem
|
||||||
acceptedMessageRequest={from.acceptedMessageRequest}
|
|
||||||
avatarUrl={from.avatarUrl}
|
avatarUrl={from.avatarUrl}
|
||||||
badge={getPreferredBadge(from.badges)}
|
badge={getPreferredBadge(from.badges)}
|
||||||
color={from.color}
|
color={from.color}
|
||||||
@@ -192,8 +190,8 @@ export const MessageSearchResult: FunctionComponent<PropsType> = React.memo(
|
|||||||
headerName={headerName}
|
headerName={headerName}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
id={id}
|
id={id}
|
||||||
isNoteToSelf={isNoteToSelf}
|
|
||||||
isMe={from.isMe}
|
isMe={from.isMe}
|
||||||
|
isNoteToSelf={isNoteToSelf}
|
||||||
isSelected={false}
|
isSelected={false}
|
||||||
messageText={messageText}
|
messageText={messageText}
|
||||||
onClick={onClickItem}
|
onClick={onClickItem}
|
||||||
@@ -202,7 +200,6 @@ export const MessageSearchResult: FunctionComponent<PropsType> = React.memo(
|
|||||||
sharedGroupNames={from.sharedGroupNames}
|
sharedGroupNames={from.sharedGroupNames}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
title={from.title}
|
title={from.title}
|
||||||
unblurredAvatarUrl={from.unblurredAvatarUrl}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -93,11 +93,9 @@ export const PhoneNumberCheckbox: FunctionComponent<PropsType> = React.memo(
|
|||||||
|
|
||||||
const avatar = (
|
const avatar = (
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={false}
|
|
||||||
color={AvatarColors[0]}
|
color={AvatarColors[0]}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={false}
|
|
||||||
phoneNumber={phoneNumber.userInput}
|
phoneNumber={phoneNumber.userInput}
|
||||||
title={phoneNumber.userInput}
|
title={phoneNumber.userInput}
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
|
@@ -92,11 +92,9 @@ export const StartNewConversation: FunctionComponent<Props> = React.memo(
|
|||||||
<ListTile
|
<ListTile
|
||||||
leading={
|
leading={
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={false}
|
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
searchResult
|
searchResult
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={false}
|
|
||||||
title={phoneNumber.userInput}
|
title={phoneNumber.userInput}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
|
@@ -66,12 +66,10 @@ export const UsernameCheckbox: FunctionComponent<PropsType> = React.memo(
|
|||||||
|
|
||||||
const avatar = (
|
const avatar = (
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={false}
|
|
||||||
color={AvatarColors[0]}
|
color={AvatarColors[0]}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
searchResult
|
searchResult
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={false}
|
|
||||||
title={title}
|
title={title}
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
|
@@ -61,11 +61,9 @@ export function UsernameSearchResultListItem({
|
|||||||
<ListTile
|
<ListTile
|
||||||
leading={
|
leading={
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={false}
|
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
searchResult
|
searchResult
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={false}
|
|
||||||
title={username}
|
title={username}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
badge={undefined}
|
badge={undefined}
|
||||||
|
@@ -214,11 +214,12 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
|||||||
<ContactPills>
|
<ContactPills>
|
||||||
{this.#selectedContacts.map(contact => (
|
{this.#selectedContacts.map(contact => (
|
||||||
<ContactPill
|
<ContactPill
|
||||||
|
avatarPlaceholderGradient={contact.avatarPlaceholderGradient}
|
||||||
key={contact.id}
|
key={contact.id}
|
||||||
acceptedMessageRequest={contact.acceptedMessageRequest}
|
|
||||||
avatarUrl={contact.avatarUrl}
|
avatarUrl={contact.avatarUrl}
|
||||||
color={contact.color}
|
color={contact.color}
|
||||||
firstName={contact.systemGivenName ?? contact.firstName}
|
firstName={contact.systemGivenName ?? contact.firstName}
|
||||||
|
hasAvatar={contact.hasAvatar}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
id={contact.id}
|
id={contact.id}
|
||||||
isMe={contact.isMe}
|
isMe={contact.isMe}
|
||||||
|
76
ts/groups.ts
76
ts/groups.ts
@@ -104,6 +104,8 @@ import { getProfile } from './util/getProfile';
|
|||||||
import { generateMessageId } from './util/generateMessageId';
|
import { generateMessageId } from './util/generateMessageId';
|
||||||
import { postSaveUpdates } from './util/cleanup';
|
import { postSaveUpdates } from './util/cleanup';
|
||||||
import { MessageModel } from './models/messages';
|
import { MessageModel } from './models/messages';
|
||||||
|
import { areWePending } from './util/groupMembershipUtils';
|
||||||
|
import { isConversationAccepted } from './util/isConversationAccepted';
|
||||||
|
|
||||||
type AccessRequiredEnum = Proto.AccessControl.AccessRequired;
|
type AccessRequiredEnum = Proto.AccessControl.AccessRequired;
|
||||||
|
|
||||||
@@ -3792,11 +3794,11 @@ async function updateGroupViaPreJoinInfo({
|
|||||||
|
|
||||||
newAttributes = {
|
newAttributes = {
|
||||||
...newAttributes,
|
...newAttributes,
|
||||||
...(await applyNewAvatar(
|
...(await applyNewAvatar({
|
||||||
dropNull(preJoinInfo.avatar),
|
newAvatarUrl: dropNull(preJoinInfo.avatar),
|
||||||
newAttributes,
|
attributes: newAttributes,
|
||||||
logId
|
logId,
|
||||||
)),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -5441,7 +5443,11 @@ async function applyGroupChange({
|
|||||||
const { avatar } = actions.modifyAvatar;
|
const { avatar } = actions.modifyAvatar;
|
||||||
result = {
|
result = {
|
||||||
...result,
|
...result,
|
||||||
...(await applyNewAvatar(dropNull(avatar), result, logId)),
|
...(await applyNewAvatar({
|
||||||
|
newAvatarUrl: dropNull(avatar),
|
||||||
|
attributes: result,
|
||||||
|
logId,
|
||||||
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5721,13 +5727,23 @@ export async function decryptGroupAvatar(
|
|||||||
|
|
||||||
// Overwriting result.avatar as part of functionality
|
// Overwriting result.avatar as part of functionality
|
||||||
export async function applyNewAvatar(
|
export async function applyNewAvatar(
|
||||||
newAvatarUrl: string | undefined,
|
options:
|
||||||
attributes: Readonly<
|
| {
|
||||||
Pick<ConversationAttributesType, 'avatar' | 'secretParams'>
|
newAvatarUrl?: string | undefined;
|
||||||
>,
|
attributes: Pick<ConversationAttributesType, 'avatar' | 'secretParams'>;
|
||||||
logId: string
|
logId: string;
|
||||||
): Promise<Pick<ConversationAttributesType, 'avatar'>> {
|
forceDownload: true;
|
||||||
const result: Pick<ConversationAttributesType, 'avatar'> = {};
|
}
|
||||||
|
| {
|
||||||
|
newAvatarUrl?: string | undefined;
|
||||||
|
attributes: ConversationAttributesType;
|
||||||
|
logId: string;
|
||||||
|
forceDownload?: false | undefined;
|
||||||
|
}
|
||||||
|
): Promise<Pick<ConversationAttributesType, 'avatar' | 'remoteAvatarUrl'>> {
|
||||||
|
const { newAvatarUrl, attributes, logId, forceDownload } = options;
|
||||||
|
const result: Pick<ConversationAttributesType, 'avatar' | 'remoteAvatarUrl'> =
|
||||||
|
{};
|
||||||
try {
|
try {
|
||||||
// Avatar has been dropped
|
// Avatar has been dropped
|
||||||
if (!newAvatarUrl && attributes.avatar) {
|
if (!newAvatarUrl && attributes.avatar) {
|
||||||
@@ -5739,19 +5755,34 @@ export async function applyNewAvatar(
|
|||||||
result.avatar = undefined;
|
result.avatar = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const avatarUrlToUse =
|
||||||
|
newAvatarUrl ||
|
||||||
|
('remoteAvatarUrl' in attributes
|
||||||
|
? attributes.remoteAvatarUrl
|
||||||
|
: undefined);
|
||||||
|
|
||||||
// Group has avatar; has it changed?
|
// Group has avatar; has it changed?
|
||||||
if (
|
if (
|
||||||
newAvatarUrl &&
|
avatarUrlToUse &&
|
||||||
(!attributes.avatar?.path || attributes.avatar.url !== newAvatarUrl)
|
(!attributes.avatar?.path || attributes.avatar.url !== avatarUrlToUse)
|
||||||
) {
|
) {
|
||||||
if (!attributes.secretParams) {
|
if (!attributes.secretParams) {
|
||||||
throw new Error('applyNewAvatar: group was missing secretParams!');
|
throw new Error('applyNewAvatar: group was missing secretParams!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await decryptGroupAvatar(
|
if (
|
||||||
newAvatarUrl,
|
!forceDownload &&
|
||||||
|
(areWePending(attributes) || !isConversationAccepted(attributes))
|
||||||
|
) {
|
||||||
|
result.remoteAvatarUrl = avatarUrlToUse;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: Uint8Array = await decryptGroupAvatar(
|
||||||
|
avatarUrlToUse,
|
||||||
attributes.secretParams
|
attributes.secretParams
|
||||||
);
|
);
|
||||||
|
|
||||||
const hash = computeHash(data);
|
const hash = computeHash(data);
|
||||||
|
|
||||||
if (attributes.avatar?.hash === hash) {
|
if (attributes.avatar?.hash === hash) {
|
||||||
@@ -5760,7 +5791,7 @@ export async function applyNewAvatar(
|
|||||||
);
|
);
|
||||||
result.avatar = {
|
result.avatar = {
|
||||||
...attributes.avatar,
|
...attributes.avatar,
|
||||||
url: newAvatarUrl,
|
url: avatarUrlToUse,
|
||||||
};
|
};
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -5770,10 +5801,9 @@ export async function applyNewAvatar(
|
|||||||
attributes.avatar.path
|
attributes.avatar.path
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const local = await window.Signal.Migrations.writeNewAttachmentData(data);
|
const local = await window.Signal.Migrations.writeNewAttachmentData(data);
|
||||||
result.avatar = {
|
result.avatar = {
|
||||||
url: newAvatarUrl,
|
url: avatarUrlToUse,
|
||||||
...local,
|
...local,
|
||||||
hash,
|
hash,
|
||||||
};
|
};
|
||||||
@@ -5871,7 +5901,11 @@ async function applyGroupState({
|
|||||||
// avatar
|
// avatar
|
||||||
result = {
|
result = {
|
||||||
...result,
|
...result,
|
||||||
...(await applyNewAvatar(dropNull(groupState.avatar), result, logId)),
|
...(await applyNewAvatar({
|
||||||
|
newAvatarUrl: dropNull(groupState.avatar),
|
||||||
|
attributes: result,
|
||||||
|
logId,
|
||||||
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
// disappearingMessagesTimer
|
// disappearingMessagesTimer
|
||||||
|
@@ -415,7 +415,12 @@ export async function joinViaLink(value: string): Promise<void> {
|
|||||||
secretParams,
|
secretParams,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const patch = await applyNewAvatar(result.avatar, attributes, logId);
|
const patch = await applyNewAvatar({
|
||||||
|
newAvatarUrl: result.avatar,
|
||||||
|
attributes,
|
||||||
|
logId,
|
||||||
|
forceDownload: true,
|
||||||
|
});
|
||||||
attributes = { ...attributes, ...patch };
|
attributes = { ...attributes, ...patch };
|
||||||
|
|
||||||
if (attributes.avatar && attributes.avatar.path) {
|
if (attributes.avatar && attributes.avatar.path) {
|
||||||
|
@@ -28,6 +28,7 @@ export type MinimalConversation = Satisfies<
|
|||||||
| 'color'
|
| 'color'
|
||||||
| 'expireTimer'
|
| 'expireTimer'
|
||||||
| 'groupVersion'
|
| 'groupVersion'
|
||||||
|
| 'hasAvatar'
|
||||||
| 'id'
|
| 'id'
|
||||||
| 'isArchived'
|
| 'isArchived'
|
||||||
| 'isBlocked'
|
| 'isBlocked'
|
||||||
@@ -43,8 +44,10 @@ export type MinimalConversation = Satisfies<
|
|||||||
| 'profileName'
|
| 'profileName'
|
||||||
| 'title'
|
| 'title'
|
||||||
| 'type'
|
| 'type'
|
||||||
| 'unblurredAvatarUrl'
|
> & {
|
||||||
>
|
gradientStart?: string;
|
||||||
|
gradientEnd?: string;
|
||||||
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function useMinimalConversation(
|
export function useMinimalConversation(
|
||||||
@@ -59,6 +62,7 @@ export function useMinimalConversation(
|
|||||||
color,
|
color,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
groupVersion,
|
groupVersion,
|
||||||
|
hasAvatar,
|
||||||
id,
|
id,
|
||||||
isArchived,
|
isArchived,
|
||||||
isBlocked,
|
isBlocked,
|
||||||
@@ -74,10 +78,13 @@ export function useMinimalConversation(
|
|||||||
profileName,
|
profileName,
|
||||||
title,
|
title,
|
||||||
type,
|
type,
|
||||||
unblurredAvatarUrl,
|
|
||||||
} = conversation;
|
} = conversation;
|
||||||
return useMemo(() => {
|
const gradientStart = conversation.avatarPlaceholderGradient?.[0];
|
||||||
|
const gradientEnd = conversation.avatarPlaceholderGradient?.[1];
|
||||||
|
return useMemo((): MinimalConversation => {
|
||||||
return {
|
return {
|
||||||
|
gradientStart,
|
||||||
|
gradientEnd,
|
||||||
acceptedMessageRequest,
|
acceptedMessageRequest,
|
||||||
announcementsOnly,
|
announcementsOnly,
|
||||||
areWeAdmin,
|
areWeAdmin,
|
||||||
@@ -86,6 +93,7 @@ export function useMinimalConversation(
|
|||||||
color,
|
color,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
groupVersion,
|
groupVersion,
|
||||||
|
hasAvatar,
|
||||||
id,
|
id,
|
||||||
isArchived,
|
isArchived,
|
||||||
isBlocked,
|
isBlocked,
|
||||||
@@ -101,9 +109,10 @@ export function useMinimalConversation(
|
|||||||
profileName,
|
profileName,
|
||||||
title,
|
title,
|
||||||
type,
|
type,
|
||||||
unblurredAvatarUrl,
|
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
|
gradientStart,
|
||||||
|
gradientEnd,
|
||||||
acceptedMessageRequest,
|
acceptedMessageRequest,
|
||||||
announcementsOnly,
|
announcementsOnly,
|
||||||
areWeAdmin,
|
areWeAdmin,
|
||||||
@@ -112,6 +121,7 @@ export function useMinimalConversation(
|
|||||||
color,
|
color,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
groupVersion,
|
groupVersion,
|
||||||
|
hasAvatar,
|
||||||
id,
|
id,
|
||||||
isArchived,
|
isArchived,
|
||||||
isBlocked,
|
isBlocked,
|
||||||
@@ -127,6 +137,5 @@ export function useMinimalConversation(
|
|||||||
profileName,
|
profileName,
|
||||||
title,
|
title,
|
||||||
type,
|
type,
|
||||||
unblurredAvatarUrl,
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@@ -44,7 +44,11 @@ export class GroupAvatarJobQueue extends JobQueue<GroupAvatarJobData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate correct attributes patch
|
// Generate correct attributes patch
|
||||||
const patch = await applyNewAvatar(newAvatarUrl, attributes, logId);
|
const patch = await applyNewAvatar({
|
||||||
|
newAvatarUrl,
|
||||||
|
attributes,
|
||||||
|
logId,
|
||||||
|
});
|
||||||
|
|
||||||
convo.set(patch);
|
convo.set(patch);
|
||||||
await DataWriter.updateConversation(convo.attributes);
|
await DataWriter.updateConversation(convo.attributes);
|
||||||
|
12
ts/model-types.d.ts
vendored
12
ts/model-types.d.ts
vendored
@@ -499,18 +499,12 @@ export type ConversationAttributesType = {
|
|||||||
isTemporary?: boolean;
|
isTemporary?: boolean;
|
||||||
temporaryMemberCount?: number;
|
temporaryMemberCount?: number;
|
||||||
|
|
||||||
// Avatars are blurred for some unapproved conversations, but users can manually unblur
|
|
||||||
// them. If the avatar was unblurred and then changed, we don't update this value so
|
|
||||||
// the new avatar gets blurred.
|
|
||||||
//
|
|
||||||
// This value is useless once the message request has been approved. We don't clean it
|
|
||||||
// up but could. We don't persist it but could (though we'd probably want to clean it
|
|
||||||
// up in that case).
|
|
||||||
unblurredAvatarUrl?: string;
|
|
||||||
|
|
||||||
// Legacy field, mapped to above in getConversation()
|
// Legacy field, mapped to above in getConversation()
|
||||||
unblurredAvatarPath?: string;
|
unblurredAvatarPath?: string;
|
||||||
|
|
||||||
|
// remoteAvatarUrl
|
||||||
|
remoteAvatarUrl?: string;
|
||||||
|
|
||||||
// Only used during backup integration tests. After import, our data model merges
|
// Only used during backup integration tests. After import, our data model merges
|
||||||
// Contact and Chat frames from a backup, and we will then by default export both, even
|
// Contact and Chat frames from a backup, and we will then by default export both, even
|
||||||
// if the Chat frame was not imported. That's fine in normal usage, but breaks
|
// if the Chat frame was not imported. That's fine in normal usage, but breaks
|
||||||
|
@@ -34,7 +34,6 @@ import {
|
|||||||
getAvatar,
|
getAvatar,
|
||||||
getRawAvatarPath,
|
getRawAvatarPath,
|
||||||
getLocalAvatarUrl,
|
getLocalAvatarUrl,
|
||||||
getLocalProfileAvatarUrl,
|
|
||||||
} from '../util/avatarUtils';
|
} from '../util/avatarUtils';
|
||||||
import { getDraftPreview } from '../util/getDraftPreview';
|
import { getDraftPreview } from '../util/getDraftPreview';
|
||||||
import { hasDraft } from '../util/hasDraft';
|
import { hasDraft } from '../util/hasDraft';
|
||||||
@@ -192,6 +191,7 @@ import { getIsInitialContactSync } from '../services/contactSync';
|
|||||||
import { queueAttachmentDownloadsForMessage } from '../util/queueAttachmentDownloads';
|
import { queueAttachmentDownloadsForMessage } from '../util/queueAttachmentDownloads';
|
||||||
import { cleanupMessages } from '../util/cleanup';
|
import { cleanupMessages } from '../util/cleanup';
|
||||||
import { MessageModel } from './messages';
|
import { MessageModel } from './messages';
|
||||||
|
import { applyNewAvatar } from '../groups';
|
||||||
|
|
||||||
/* eslint-disable more/no-then */
|
/* eslint-disable more/no-then */
|
||||||
window.Whisper = window.Whisper || {};
|
window.Whisper = window.Whisper || {};
|
||||||
@@ -2485,6 +2485,25 @@ export class ConversationModel extends window.Backbone
|
|||||||
// time to go through old messages to download attachments.
|
// time to go through old messages to download attachments.
|
||||||
if (didResponseChange && !wasPreviouslyAccepted) {
|
if (didResponseChange && !wasPreviouslyAccepted) {
|
||||||
await this.handleReadAndDownloadAttachments({ isLocalAction });
|
await this.handleReadAndDownloadAttachments({ isLocalAction });
|
||||||
|
if (this.attributes.remoteAvatarUrl) {
|
||||||
|
if (isDirectConversation(this.attributes)) {
|
||||||
|
drop(
|
||||||
|
this.setAndMaybeFetchProfileAvatar({
|
||||||
|
avatarUrl: this.attributes.remoteAvatarUrl,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isGroup(this.attributes)) {
|
||||||
|
const updateAttrs = await applyNewAvatar({
|
||||||
|
newAvatarUrl: this.attributes.remoteAvatarUrl,
|
||||||
|
attributes: this.attributes,
|
||||||
|
logId: 'applyMessageRequestResponse',
|
||||||
|
forceDownload: true,
|
||||||
|
});
|
||||||
|
this.set(updateAttrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLocalAction) {
|
if (isLocalAction) {
|
||||||
@@ -4922,10 +4941,12 @@ export class ConversationModel extends window.Backbone
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setAndMaybeFetchProfileAvatar(
|
async setAndMaybeFetchProfileAvatar(options: {
|
||||||
avatarUrl: undefined | null | string,
|
avatarUrl: undefined | null | string;
|
||||||
decryptionKey: Uint8Array
|
decryptionKey?: Uint8Array | null | undefined;
|
||||||
): Promise<void> {
|
forceFetch?: boolean;
|
||||||
|
}): Promise<void> {
|
||||||
|
const { avatarUrl, decryptionKey, forceFetch } = options;
|
||||||
if (isMe(this.attributes)) {
|
if (isMe(this.attributes)) {
|
||||||
if (avatarUrl) {
|
if (avatarUrl) {
|
||||||
await window.storage.put('avatarUrl', avatarUrl);
|
await window.storage.put('avatarUrl', avatarUrl);
|
||||||
@@ -4944,10 +4965,28 @@ export class ConversationModel extends window.Backbone
|
|||||||
throw new Error('setProfileAvatar: Cannot fetch avatar when offline!');
|
throw new Error('setProfileAvatar: Cannot fetch avatar when offline!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.getAccepted({ ignoreEmptyConvo: true }) && !forceFetch) {
|
||||||
|
this.set({ remoteAvatarUrl: avatarUrl });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const avatar = await messaging.getAvatar(avatarUrl);
|
const avatar = await messaging.getAvatar(avatarUrl);
|
||||||
|
|
||||||
|
// If decryptionKey isn't provided, use the one from the model
|
||||||
|
const modelProfileKey = this.get('profileKey');
|
||||||
|
const updatedDecryptionKey =
|
||||||
|
decryptionKey ||
|
||||||
|
(modelProfileKey ? Bytes.fromBase64(modelProfileKey) : null);
|
||||||
|
|
||||||
|
if (!updatedDecryptionKey) {
|
||||||
|
log.warn(
|
||||||
|
'setAndMaybeFetchProfileAvatar: No decryption key provided and none found on model'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// decrypt
|
// decrypt
|
||||||
const decrypted = decryptProfile(avatar, decryptionKey);
|
const decrypted = decryptProfile(avatar, updatedDecryptionKey);
|
||||||
|
|
||||||
// update the conversation avatar only if hash differs
|
// update the conversation avatar only if hash differs
|
||||||
if (decrypted) {
|
if (decrypted) {
|
||||||
@@ -5303,15 +5342,6 @@ export class ConversationModel extends window.Backbone
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
unblurAvatar(): void {
|
|
||||||
const avatarUrl = getLocalProfileAvatarUrl(this.attributes);
|
|
||||||
if (avatarUrl) {
|
|
||||||
this.set('unblurredAvatarUrl', avatarUrl);
|
|
||||||
} else {
|
|
||||||
this.unset('unblurredAvatarUrl');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
areWeAdmin(): boolean {
|
areWeAdmin(): boolean {
|
||||||
return areWeAdmin(this.attributes);
|
return areWeAdmin(this.attributes);
|
||||||
}
|
}
|
||||||
|
@@ -294,17 +294,16 @@ export class MentionCompletion {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={member.acceptedMessageRequest}
|
avatarPlaceholderGradient={member.avatarPlaceholderGradient}
|
||||||
avatarUrl={member.avatarUrl}
|
avatarUrl={member.avatarUrl}
|
||||||
badge={getPreferredBadge(member.badges)}
|
badge={getPreferredBadge(member.badges)}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
|
hasAvatar={member.hasAvatar}
|
||||||
i18n={this.options.i18n}
|
i18n={this.options.i18n}
|
||||||
isMe={member.isMe}
|
|
||||||
sharedGroupNames={member.sharedGroupNames}
|
sharedGroupNames={member.sharedGroupNames}
|
||||||
size={AvatarSize.TWENTY_EIGHT}
|
size={AvatarSize.TWENTY_EIGHT}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
title={member.title}
|
title={member.title}
|
||||||
unblurredAvatarUrl={member.unblurredAvatarUrl}
|
|
||||||
/>
|
/>
|
||||||
<div className="module-composition-input__suggestions__title">
|
<div className="module-composition-input__suggestions__title">
|
||||||
<UserText text={member.title} />
|
<UserText text={member.title} />
|
||||||
|
@@ -784,10 +784,10 @@ async function doGetProfile(
|
|||||||
try {
|
try {
|
||||||
if (requestDecryptionKey != null) {
|
if (requestDecryptionKey != null) {
|
||||||
// Note: Fetches avatar
|
// Note: Fetches avatar
|
||||||
await c.setAndMaybeFetchProfileAvatar(
|
await c.setAndMaybeFetchProfileAvatar({
|
||||||
profile.avatar,
|
avatarUrl: profile.avatar,
|
||||||
requestDecryptionKey
|
decryptionKey: requestDecryptionKey,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof HTTPError) {
|
if (error instanceof HTTPError) {
|
||||||
|
@@ -1743,7 +1743,10 @@ export async function mergeAccountRecord(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const avatarUrl = dropNull(accountRecord.avatarUrl);
|
const avatarUrl = dropNull(accountRecord.avatarUrl);
|
||||||
await conversation.setAndMaybeFetchProfileAvatar(avatarUrl, profileKey);
|
await conversation.setAndMaybeFetchProfileAvatar({
|
||||||
|
avatarUrl,
|
||||||
|
decryptionKey: profileKey,
|
||||||
|
});
|
||||||
await window.storage.put('avatarUrl', avatarUrl);
|
await window.storage.put('avatarUrl', avatarUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1655,9 +1655,7 @@ function saveConversation(db: WritableDB, data: ConversationType): void {
|
|||||||
`
|
`
|
||||||
).run({
|
).run({
|
||||||
id,
|
id,
|
||||||
json: objectToJSON(
|
json: objectToJSON(omit(data, ['profileLastFetchedAt'])),
|
||||||
omit(data, ['profileLastFetchedAt', 'unblurredAvatarUrl'])
|
|
||||||
),
|
|
||||||
|
|
||||||
e164: e164 || null,
|
e164: e164 || null,
|
||||||
serviceId: serviceId || null,
|
serviceId: serviceId || null,
|
||||||
@@ -1723,9 +1721,7 @@ function updateConversation(db: WritableDB, data: ConversationType): void {
|
|||||||
`
|
`
|
||||||
).run({
|
).run({
|
||||||
id,
|
id,
|
||||||
json: objectToJSON(
|
json: objectToJSON(omit(data, ['profileLastFetchedAt'])),
|
||||||
omit(data, ['profileLastFetchedAt', 'unblurredAvatarUrl'])
|
|
||||||
),
|
|
||||||
|
|
||||||
e164: e164 || null,
|
e164: e164 || null,
|
||||||
serviceId: serviceId || null,
|
serviceId: serviceId || null,
|
||||||
|
@@ -97,6 +97,7 @@ import {
|
|||||||
getConversationSelector,
|
getConversationSelector,
|
||||||
getMe,
|
getMe,
|
||||||
getMessagesByConversation,
|
getMessagesByConversation,
|
||||||
|
getPendingAvatarDownloadSelector,
|
||||||
} from '../selectors/conversations';
|
} from '../selectors/conversations';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import type {
|
import type {
|
||||||
@@ -302,8 +303,9 @@ export type ConversationType = ReadonlyDeep<
|
|||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
rawAvatarPath?: string;
|
rawAvatarPath?: string;
|
||||||
avatarHash?: string;
|
avatarHash?: string;
|
||||||
|
avatarPlaceholderGradient?: Readonly<[string, string]>;
|
||||||
profileAvatarUrl?: string;
|
profileAvatarUrl?: string;
|
||||||
unblurredAvatarUrl?: string;
|
hasAvatar?: boolean;
|
||||||
areWeAdmin?: boolean;
|
areWeAdmin?: boolean;
|
||||||
areWePending?: boolean;
|
areWePending?: boolean;
|
||||||
areWePendingApproval?: boolean;
|
areWePendingApproval?: boolean;
|
||||||
@@ -578,6 +580,10 @@ export type ConversationsStateType = ReadonlyDeep<{
|
|||||||
messagesLookup: MessageLookupType;
|
messagesLookup: MessageLookupType;
|
||||||
messagesByConversation: MessagesByConversationType;
|
messagesByConversation: MessagesByConversationType;
|
||||||
|
|
||||||
|
// Map of conversation IDs to a boolean indicating whether an avatar download
|
||||||
|
// was requested
|
||||||
|
pendingRequestedAvatarDownload: Record<string, boolean>;
|
||||||
|
|
||||||
preloadData?: ConversationPreloadDataType;
|
preloadData?: ConversationPreloadDataType;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@@ -640,6 +646,8 @@ export const SET_VOICE_NOTE_PLAYBACK_RATE =
|
|||||||
'conversations/SET_VOICE_NOTE_PLAYBACK_RATE';
|
'conversations/SET_VOICE_NOTE_PLAYBACK_RATE';
|
||||||
export const CONVERSATION_UNLOADED = 'CONVERSATION_UNLOADED';
|
export const CONVERSATION_UNLOADED = 'CONVERSATION_UNLOADED';
|
||||||
export const SHOW_SPOILER = 'conversations/SHOW_SPOILER';
|
export const SHOW_SPOILER = 'conversations/SHOW_SPOILER';
|
||||||
|
export const SET_PENDING_REQUESTED_AVATAR_DOWNLOAD =
|
||||||
|
'conversations/SET_PENDING_REQUESTED_AVATAR_DOWNLOAD';
|
||||||
|
|
||||||
export type CancelVerificationDataByConversationActionType = ReadonlyDeep<{
|
export type CancelVerificationDataByConversationActionType = ReadonlyDeep<{
|
||||||
type: typeof CANCEL_CONVERSATION_PENDING_VERIFICATION;
|
type: typeof CANCEL_CONVERSATION_PENDING_VERIFICATION;
|
||||||
@@ -830,6 +838,14 @@ export type ShowSpoilerActionType = ReadonlyDeep<{
|
|||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type SetPendingRequestedAvatarDownloadActionType = ReadonlyDeep<{
|
||||||
|
type: typeof SET_PENDING_REQUESTED_AVATAR_DOWNLOAD;
|
||||||
|
payload: {
|
||||||
|
conversationId: string;
|
||||||
|
value: boolean;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
|
||||||
export type MessagesAddedActionType = ReadonlyDeep<{
|
export type MessagesAddedActionType = ReadonlyDeep<{
|
||||||
type: 'MESSAGES_ADDED';
|
type: 'MESSAGES_ADDED';
|
||||||
payload: {
|
payload: {
|
||||||
@@ -1064,6 +1080,7 @@ export type ConversationActionType =
|
|||||||
| ReplaceAvatarsActionType
|
| ReplaceAvatarsActionType
|
||||||
| ReviewConversationNameCollisionActionType
|
| ReviewConversationNameCollisionActionType
|
||||||
| ScrollToMessageActionType
|
| ScrollToMessageActionType
|
||||||
|
| SetPendingRequestedAvatarDownloadActionType
|
||||||
| TargetedConversationChangedActionType
|
| TargetedConversationChangedActionType
|
||||||
| SetComposeGroupAvatarActionType
|
| SetComposeGroupAvatarActionType
|
||||||
| SetComposeGroupExpireTimerActionType
|
| SetComposeGroupExpireTimerActionType
|
||||||
@@ -1179,6 +1196,8 @@ export const actions = {
|
|||||||
saveAvatarToDisk,
|
saveAvatarToDisk,
|
||||||
scrollToMessage,
|
scrollToMessage,
|
||||||
scrollToOldestUnreadMention,
|
scrollToOldestUnreadMention,
|
||||||
|
setPendingRequestedAvatarDownload,
|
||||||
|
startAvatarDownload,
|
||||||
showSpoiler,
|
showSpoiler,
|
||||||
targetMessage,
|
targetMessage,
|
||||||
setAccessControlAddFromInviteLinkSetting,
|
setAccessControlAddFromInviteLinkSetting,
|
||||||
@@ -1220,7 +1239,6 @@ export const actions = {
|
|||||||
toggleHideStories,
|
toggleHideStories,
|
||||||
toggleSelectMessage,
|
toggleSelectMessage,
|
||||||
toggleSelectMode,
|
toggleSelectMode,
|
||||||
unblurAvatar,
|
|
||||||
updateConversationModelSharedGroups,
|
updateConversationModelSharedGroups,
|
||||||
updateGroupAttributes,
|
updateGroupAttributes,
|
||||||
updateLastMessage,
|
updateLastMessage,
|
||||||
@@ -1465,19 +1483,7 @@ function removeMember(
|
|||||||
payload: null,
|
payload: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function unblurAvatar(conversationId: string): NoopActionType {
|
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
|
||||||
if (!conversation) {
|
|
||||||
throw new Error('unblurAvatar: Conversation not found!');
|
|
||||||
}
|
|
||||||
|
|
||||||
conversation.unblurAvatar();
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'NOOP',
|
|
||||||
payload: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function updateSharedGroups(conversationId: string): NoopActionType {
|
function updateSharedGroups(conversationId: string): NoopActionType {
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
@@ -4873,6 +4879,73 @@ function doubleCheckMissingQuoteReference(messageId: string): NoopActionType {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setPendingRequestedAvatarDownload(
|
||||||
|
conversationId: string,
|
||||||
|
value: boolean
|
||||||
|
): SetPendingRequestedAvatarDownloadActionType {
|
||||||
|
return {
|
||||||
|
type: SET_PENDING_REQUESTED_AVATAR_DOWNLOAD,
|
||||||
|
payload: {
|
||||||
|
conversationId,
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function startAvatarDownload(
|
||||||
|
conversationId: string
|
||||||
|
): ThunkAction<
|
||||||
|
void,
|
||||||
|
RootStateType,
|
||||||
|
unknown,
|
||||||
|
SetPendingRequestedAvatarDownloadActionType
|
||||||
|
> {
|
||||||
|
return async (dispatch, getState) => {
|
||||||
|
const isAlreadyLoading =
|
||||||
|
getPendingAvatarDownloadSelector(getState())(conversationId);
|
||||||
|
if (isAlreadyLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(setPendingRequestedAvatarDownload(conversationId, true));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
if (!conversation) {
|
||||||
|
throw new Error('startAvatarDownload: Conversation not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!conversation.attributes.remoteAvatarUrl) {
|
||||||
|
throw new Error('startAvatarDownload: no avatar URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isGroup(conversation.attributes)) {
|
||||||
|
const updateAttrs = await groups.applyNewAvatar({
|
||||||
|
newAvatarUrl: conversation.attributes.remoteAvatarUrl,
|
||||||
|
attributes: conversation.attributes,
|
||||||
|
logId: 'startAvatarDownload',
|
||||||
|
forceDownload: true,
|
||||||
|
});
|
||||||
|
conversation.set(updateAttrs);
|
||||||
|
} else {
|
||||||
|
await conversation.setAndMaybeFetchProfileAvatar({
|
||||||
|
avatarUrl: conversation.attributes.remoteAvatarUrl,
|
||||||
|
decryptionKey: null,
|
||||||
|
forceFetch: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await DataWriter.updateConversation(conversation.attributes);
|
||||||
|
} catch (error) {
|
||||||
|
log.error(
|
||||||
|
'startAvatarDownload: Failed to download avatar',
|
||||||
|
Errors.toLogFormat(error)
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
dispatch(setPendingRequestedAvatarDownload(conversationId, false));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Reducer
|
// Reducer
|
||||||
|
|
||||||
export function getEmptyState(): ConversationsStateType {
|
export function getEmptyState(): ConversationsStateType {
|
||||||
@@ -4892,6 +4965,7 @@ export function getEmptyState(): ConversationsStateType {
|
|||||||
selectedMessageIds: undefined,
|
selectedMessageIds: undefined,
|
||||||
showArchived: false,
|
showArchived: false,
|
||||||
hasContactSpoofingReview: false,
|
hasContactSpoofingReview: false,
|
||||||
|
pendingRequestedAvatarDownload: {},
|
||||||
targetedConversationPanels: {
|
targetedConversationPanels: {
|
||||||
isAnimating: false,
|
isAnimating: false,
|
||||||
wasAnimated: false,
|
wasAnimated: false,
|
||||||
@@ -7232,5 +7306,17 @@ export function reducer(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action.type === SET_PENDING_REQUESTED_AVATAR_DOWNLOAD) {
|
||||||
|
const { conversationId, value } = action.payload;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
pendingRequestedAvatarDownload: {
|
||||||
|
...state.pendingRequestedAvatarDownload,
|
||||||
|
[conversationId]: value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@@ -1339,3 +1339,14 @@ export const getPreloadedConversationId = createSelector(
|
|||||||
getConversations,
|
getConversations,
|
||||||
({ preloadData }): string | undefined => preloadData?.conversationId
|
({ preloadData }): string | undefined => preloadData?.conversationId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getPendingAvatarDownloadSelector = createSelector(
|
||||||
|
getConversations,
|
||||||
|
(conversations: ConversationsStateType) => {
|
||||||
|
return (conversationId: string): boolean => {
|
||||||
|
return Boolean(
|
||||||
|
conversations.pendingRequestedAvatarDownload[conversationId]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
@@ -164,7 +164,6 @@ type FormattedContact = Partial<ConversationType> &
|
|||||||
| 'sharedGroupNames'
|
| 'sharedGroupNames'
|
||||||
| 'title'
|
| 'title'
|
||||||
| 'type'
|
| 'type'
|
||||||
| 'unblurredAvatarUrl'
|
|
||||||
>;
|
>;
|
||||||
export type PropsForMessage = Omit<TimelineMessagePropsData, 'interactionMode'>;
|
export type PropsForMessage = Omit<TimelineMessagePropsData, 'interactionMode'>;
|
||||||
export type MessagePropsType = Omit<
|
export type MessagePropsType = Omit<
|
||||||
@@ -351,11 +350,13 @@ const getAuthorForMessage = (
|
|||||||
options: GetContactOptions
|
options: GetContactOptions
|
||||||
): PropsData['author'] => {
|
): PropsData['author'] => {
|
||||||
const {
|
const {
|
||||||
|
avatarPlaceholderGradient,
|
||||||
acceptedMessageRequest,
|
acceptedMessageRequest,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
badges,
|
badges,
|
||||||
color,
|
color,
|
||||||
firstName,
|
firstName,
|
||||||
|
hasAvatar,
|
||||||
id,
|
id,
|
||||||
isMe,
|
isMe,
|
||||||
name,
|
name,
|
||||||
@@ -363,15 +364,16 @@ const getAuthorForMessage = (
|
|||||||
profileName,
|
profileName,
|
||||||
sharedGroupNames,
|
sharedGroupNames,
|
||||||
title,
|
title,
|
||||||
unblurredAvatarUrl,
|
|
||||||
} = getContact(message, options);
|
} = getContact(message, options);
|
||||||
|
|
||||||
const unsafe = {
|
const unsafe = {
|
||||||
|
avatarPlaceholderGradient,
|
||||||
acceptedMessageRequest,
|
acceptedMessageRequest,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
badges,
|
badges,
|
||||||
color,
|
color,
|
||||||
firstName,
|
firstName,
|
||||||
|
hasAvatar,
|
||||||
id,
|
id,
|
||||||
isMe,
|
isMe,
|
||||||
name,
|
name,
|
||||||
@@ -379,7 +381,6 @@ const getAuthorForMessage = (
|
|||||||
profileName,
|
profileName,
|
||||||
sharedGroupNames,
|
sharedGroupNames,
|
||||||
title,
|
title,
|
||||||
unblurredAvatarUrl,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const safe: AssertProps<PropsData['author'], typeof unsafe> = unsafe;
|
const safe: AssertProps<PropsData['author'], typeof unsafe> = unsafe;
|
||||||
|
@@ -166,11 +166,13 @@ export function getStoryView(
|
|||||||
const sender = pick(
|
const sender = pick(
|
||||||
conversationSelector(story.sourceServiceId || story.source),
|
conversationSelector(story.sourceServiceId || story.source),
|
||||||
[
|
[
|
||||||
|
'avatarPlaceholderGradient',
|
||||||
'acceptedMessageRequest',
|
'acceptedMessageRequest',
|
||||||
'avatarUrl',
|
'avatarUrl',
|
||||||
'badges',
|
'badges',
|
||||||
'color',
|
'color',
|
||||||
'firstName',
|
'firstName',
|
||||||
|
'hasAvatar',
|
||||||
'hideStory',
|
'hideStory',
|
||||||
'id',
|
'id',
|
||||||
'isMe',
|
'isMe',
|
||||||
|
@@ -6,7 +6,10 @@ import { AboutContactModal } from '../../components/conversation/AboutContactMod
|
|||||||
import { isSignalConnection } from '../../util/getSignalConnections';
|
import { isSignalConnection } from '../../util/getSignalConnections';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import { getGlobalModalsState } from '../selectors/globalModals';
|
import { getGlobalModalsState } from '../selectors/globalModals';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import {
|
||||||
|
getConversationSelector,
|
||||||
|
getPendingAvatarDownloadSelector,
|
||||||
|
} from '../selectors/conversations';
|
||||||
import type { ConversationType } from '../ducks/conversations';
|
import type { ConversationType } from '../ducks/conversations';
|
||||||
import { useConversationsActions } from '../ducks/conversations';
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
@@ -35,8 +38,9 @@ export const SmartAboutContactModal = memo(function SmartAboutContactModal() {
|
|||||||
const globalModals = useSelector(getGlobalModalsState);
|
const globalModals = useSelector(getGlobalModalsState);
|
||||||
const { aboutContactModalContactId: contactId } = globalModals;
|
const { aboutContactModalContactId: contactId } = globalModals;
|
||||||
const getConversation = useSelector(getConversationSelector);
|
const getConversation = useSelector(getConversationSelector);
|
||||||
|
const isPendingAvatarDownload = useSelector(getPendingAvatarDownloadSelector);
|
||||||
|
|
||||||
const { updateSharedGroups, unblurAvatar } = useConversationsActions();
|
const { startAvatarDownload, updateSharedGroups } = useConversationsActions();
|
||||||
|
|
||||||
const conversation = getConversation(contactId);
|
const conversation = getConversation(contactId);
|
||||||
const { id: conversationId } = conversation ?? {};
|
const { id: conversationId } = conversation ?? {};
|
||||||
@@ -63,7 +67,6 @@ export const SmartAboutContactModal = memo(function SmartAboutContactModal() {
|
|||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
conversation={conversation}
|
conversation={conversation}
|
||||||
updateSharedGroups={updateSharedGroups}
|
updateSharedGroups={updateSharedGroups}
|
||||||
unblurAvatar={unblurAvatar}
|
|
||||||
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
||||||
toggleSafetyNumberModal={toggleSafetyNumberModal}
|
toggleSafetyNumberModal={toggleSafetyNumberModal}
|
||||||
isSignalConnection={isSignalConnection(conversation)}
|
isSignalConnection={isSignalConnection(conversation)}
|
||||||
@@ -71,6 +74,12 @@ export const SmartAboutContactModal = memo(function SmartAboutContactModal() {
|
|||||||
onClose={toggleAboutContactModal}
|
onClose={toggleAboutContactModal}
|
||||||
onOpenNotePreviewModal={handleOpenNotePreviewModal}
|
onOpenNotePreviewModal={handleOpenNotePreviewModal}
|
||||||
toggleProfileNameWarningModal={toggleProfileNameWarningModal}
|
toggleProfileNameWarningModal={toggleProfileNameWarningModal}
|
||||||
|
pendingAvatarDownload={
|
||||||
|
conversationId ? isPendingAvatarDownload(conversationId) : false
|
||||||
|
}
|
||||||
|
startAvatarDownload={
|
||||||
|
conversationId ? () => startAvatarDownload(conversationId) : undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@@ -54,6 +54,7 @@ export const SmartContactModal = memo(function SmartContactModal() {
|
|||||||
updateConversationModelSharedGroups,
|
updateConversationModelSharedGroups,
|
||||||
toggleAdmin,
|
toggleAdmin,
|
||||||
blockConversation,
|
blockConversation,
|
||||||
|
startAvatarDownload,
|
||||||
} = useConversationsActions();
|
} = useConversationsActions();
|
||||||
const { viewUserStories } = useStoriesActions();
|
const { viewUserStories } = useStoriesActions();
|
||||||
const {
|
const {
|
||||||
@@ -94,6 +95,7 @@ export const SmartContactModal = memo(function SmartContactModal() {
|
|||||||
onOutgoingVideoCallInConversation={onOutgoingVideoCallInConversation}
|
onOutgoingVideoCallInConversation={onOutgoingVideoCallInConversation}
|
||||||
removeMemberFromGroup={removeMemberFromGroup}
|
removeMemberFromGroup={removeMemberFromGroup}
|
||||||
showConversation={showConversation}
|
showConversation={showConversation}
|
||||||
|
startAvatarDownload={() => startAvatarDownload(contact.id)}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
toggleAboutContactModal={toggleAboutContactModal}
|
toggleAboutContactModal={toggleAboutContactModal}
|
||||||
toggleAddUserToAnotherGroupModal={toggleAddUserToAnotherGroupModal}
|
toggleAddUserToAnotherGroupModal={toggleAddUserToAnotherGroupModal}
|
||||||
|
@@ -23,6 +23,7 @@ import {
|
|||||||
getAllComposableConversations,
|
getAllComposableConversations,
|
||||||
getConversationByIdSelector,
|
getConversationByIdSelector,
|
||||||
getConversationByServiceIdSelector,
|
getConversationByServiceIdSelector,
|
||||||
|
getPendingAvatarDownloadSelector,
|
||||||
} from '../selectors/conversations';
|
} from '../selectors/conversations';
|
||||||
import {
|
import {
|
||||||
getAreWeASubscriber,
|
getAreWeASubscriber,
|
||||||
@@ -98,6 +99,7 @@ export const SmartConversationDetails = memo(function SmartConversationDetails({
|
|||||||
const conversationSelector = useSelector(getConversationByIdSelector);
|
const conversationSelector = useSelector(getConversationByIdSelector);
|
||||||
const defaultConversationColor = useSelector(getDefaultConversationColor);
|
const defaultConversationColor = useSelector(getDefaultConversationColor);
|
||||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
|
const isPendingAvatarDownload = useSelector(getPendingAvatarDownloadSelector);
|
||||||
const selectedNavTab = useSelector(getSelectedNavTab);
|
const selectedNavTab = useSelector(getSelectedNavTab);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -114,6 +116,7 @@ export const SmartConversationDetails = memo(function SmartConversationDetails({
|
|||||||
setDisappearingMessages,
|
setDisappearingMessages,
|
||||||
setMuteExpiration,
|
setMuteExpiration,
|
||||||
showConversation,
|
showConversation,
|
||||||
|
startAvatarDownload,
|
||||||
updateGroupAttributes,
|
updateGroupAttributes,
|
||||||
updateNicknameAndNote,
|
updateNicknameAndNote,
|
||||||
} = useConversationsActions();
|
} = useConversationsActions();
|
||||||
@@ -205,6 +208,7 @@ export const SmartConversationDetails = memo(function SmartConversationDetails({
|
|||||||
onOutgoingAudioCallInConversation={onOutgoingAudioCallInConversation}
|
onOutgoingAudioCallInConversation={onOutgoingAudioCallInConversation}
|
||||||
onOutgoingVideoCallInConversation={onOutgoingVideoCallInConversation}
|
onOutgoingVideoCallInConversation={onOutgoingVideoCallInConversation}
|
||||||
pendingApprovalMemberships={pendingApprovalMemberships}
|
pendingApprovalMemberships={pendingApprovalMemberships}
|
||||||
|
pendingAvatarDownload={isPendingAvatarDownload(conversationId)}
|
||||||
pendingMemberships={pendingMemberships}
|
pendingMemberships={pendingMemberships}
|
||||||
pushPanelForConversation={pushPanelForConversation}
|
pushPanelForConversation={pushPanelForConversation}
|
||||||
renderChooseGroupMembersModal={renderChooseGroupMembersModal}
|
renderChooseGroupMembersModal={renderChooseGroupMembersModal}
|
||||||
@@ -218,6 +222,7 @@ export const SmartConversationDetails = memo(function SmartConversationDetails({
|
|||||||
showContactModal={showContactModal}
|
showContactModal={showContactModal}
|
||||||
showConversation={showConversation}
|
showConversation={showConversation}
|
||||||
showLightbox={showLightbox}
|
showLightbox={showLightbox}
|
||||||
|
startAvatarDownload={() => startAvatarDownload(conversationId)}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
toggleAboutContactModal={toggleAboutContactModal}
|
toggleAboutContactModal={toggleAboutContactModal}
|
||||||
toggleAddUserToAnotherGroupModal={toggleAddUserToAnotherGroupModal}
|
toggleAddUserToAnotherGroupModal={toggleAddUserToAnotherGroupModal}
|
||||||
|
@@ -8,7 +8,10 @@ import { getPreferredBadgeSelector } from '../selectors/badges';
|
|||||||
import { getIntl, getTheme } from '../selectors/user';
|
import { getIntl, getTheme } from '../selectors/user';
|
||||||
import { getHasStoriesSelector } from '../selectors/stories2';
|
import { getHasStoriesSelector } from '../selectors/stories2';
|
||||||
import { isSignalConversation } from '../../util/isSignalConversation';
|
import { isSignalConversation } from '../../util/isSignalConversation';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import {
|
||||||
|
getConversationSelector,
|
||||||
|
getPendingAvatarDownloadSelector,
|
||||||
|
} from '../selectors/conversations';
|
||||||
import {
|
import {
|
||||||
type ConversationType,
|
type ConversationType,
|
||||||
useConversationsActions,
|
useConversationsActions,
|
||||||
@@ -46,6 +49,7 @@ export const SmartHeroRow = memo(function SmartHeroRow({
|
|||||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
const hasStoriesSelector = useSelector(getHasStoriesSelector);
|
const hasStoriesSelector = useSelector(getHasStoriesSelector);
|
||||||
const conversationSelector = useSelector(getConversationSelector);
|
const conversationSelector = useSelector(getConversationSelector);
|
||||||
|
const isPendingAvatarDownload = useSelector(getPendingAvatarDownloadSelector);
|
||||||
const conversation = conversationSelector(id);
|
const conversation = conversationSelector(id);
|
||||||
if (conversation == null) {
|
if (conversation == null) {
|
||||||
throw new Error(`Did not find conversation ${id} in state!`);
|
throw new Error(`Did not find conversation ${id} in state!`);
|
||||||
@@ -55,7 +59,7 @@ export const SmartHeroRow = memo(function SmartHeroRow({
|
|||||||
const isSignalConversationValue = isSignalConversation(conversation);
|
const isSignalConversationValue = isSignalConversation(conversation);
|
||||||
const fromOrAddedByTrustedContact =
|
const fromOrAddedByTrustedContact =
|
||||||
isFromOrAddedByTrustedContact(conversation);
|
isFromOrAddedByTrustedContact(conversation);
|
||||||
const { pushPanelForConversation, unblurAvatar, updateSharedGroups } =
|
const { pushPanelForConversation, startAvatarDownload, updateSharedGroups } =
|
||||||
useConversationsActions();
|
useConversationsActions();
|
||||||
const { toggleAboutContactModal, toggleProfileNameWarningModal } =
|
const { toggleAboutContactModal, toggleProfileNameWarningModal } =
|
||||||
useGlobalModalActions();
|
useGlobalModalActions();
|
||||||
@@ -64,10 +68,12 @@ export const SmartHeroRow = memo(function SmartHeroRow({
|
|||||||
}, [pushPanelForConversation]);
|
}, [pushPanelForConversation]);
|
||||||
const { viewUserStories } = useStoriesActions();
|
const { viewUserStories } = useStoriesActions();
|
||||||
const {
|
const {
|
||||||
|
avatarPlaceholderGradient,
|
||||||
about,
|
about,
|
||||||
acceptedMessageRequest,
|
acceptedMessageRequest,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
groupDescription,
|
groupDescription,
|
||||||
|
hasAvatar,
|
||||||
isMe,
|
isMe,
|
||||||
membersCount,
|
membersCount,
|
||||||
nicknameGivenName,
|
nicknameGivenName,
|
||||||
@@ -77,7 +83,6 @@ export const SmartHeroRow = memo(function SmartHeroRow({
|
|||||||
sharedGroupNames,
|
sharedGroupNames,
|
||||||
title,
|
title,
|
||||||
type,
|
type,
|
||||||
unblurredAvatarUrl,
|
|
||||||
} = conversation;
|
} = conversation;
|
||||||
|
|
||||||
const isDirectConvoAndHasNickname =
|
const isDirectConvoAndHasNickname =
|
||||||
@@ -85,6 +90,7 @@ export const SmartHeroRow = memo(function SmartHeroRow({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ConversationHero
|
<ConversationHero
|
||||||
|
avatarPlaceholderGradient={avatarPlaceholderGradient}
|
||||||
about={about}
|
about={about}
|
||||||
acceptedMessageRequest={acceptedMessageRequest}
|
acceptedMessageRequest={acceptedMessageRequest}
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
@@ -92,6 +98,7 @@ export const SmartHeroRow = memo(function SmartHeroRow({
|
|||||||
conversationType={type}
|
conversationType={type}
|
||||||
fromOrAddedByTrustedContact={fromOrAddedByTrustedContact}
|
fromOrAddedByTrustedContact={fromOrAddedByTrustedContact}
|
||||||
groupDescription={groupDescription}
|
groupDescription={groupDescription}
|
||||||
|
hasAvatar={hasAvatar}
|
||||||
hasStories={hasStories}
|
hasStories={hasStories}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
id={id}
|
id={id}
|
||||||
@@ -100,15 +107,15 @@ export const SmartHeroRow = memo(function SmartHeroRow({
|
|||||||
isSignalConversation={isSignalConversationValue}
|
isSignalConversation={isSignalConversationValue}
|
||||||
membersCount={membersCount}
|
membersCount={membersCount}
|
||||||
openConversationDetails={openConversationDetails}
|
openConversationDetails={openConversationDetails}
|
||||||
|
pendingAvatarDownload={isPendingAvatarDownload(id)}
|
||||||
phoneNumber={phoneNumber}
|
phoneNumber={phoneNumber}
|
||||||
profileName={profileName}
|
profileName={profileName}
|
||||||
sharedGroupNames={sharedGroupNames}
|
sharedGroupNames={sharedGroupNames}
|
||||||
|
startAvatarDownload={() => startAvatarDownload(id)}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
title={title}
|
title={title}
|
||||||
toggleAboutContactModal={toggleAboutContactModal}
|
toggleAboutContactModal={toggleAboutContactModal}
|
||||||
toggleProfileNameWarningModal={toggleProfileNameWarningModal}
|
toggleProfileNameWarningModal={toggleProfileNameWarningModal}
|
||||||
unblurAvatar={unblurAvatar}
|
|
||||||
unblurredAvatarUrl={unblurredAvatarUrl}
|
|
||||||
updateSharedGroups={updateSharedGroups}
|
updateSharedGroups={updateSharedGroups}
|
||||||
viewUserStories={viewUserStories}
|
viewUserStories={viewUserStories}
|
||||||
/>
|
/>
|
||||||
|
@@ -12,6 +12,7 @@ import type { GroupListItemConversationType } from '../../components/conversatio
|
|||||||
import { getRandomColor } from './getRandomColor';
|
import { getRandomColor } from './getRandomColor';
|
||||||
import { ConversationColors } from '../../types/Colors';
|
import { ConversationColors } from '../../types/Colors';
|
||||||
import { StorySendMode } from '../../types/Stories';
|
import { StorySendMode } from '../../types/Stories';
|
||||||
|
import { getAvatarPlaceholderGradient } from '../../utils/getAvatarPlaceholderGradient';
|
||||||
|
|
||||||
export const getAvatarPath = (): string =>
|
export const getAvatarPath = (): string =>
|
||||||
sample([
|
sample([
|
||||||
@@ -27,6 +28,7 @@ export function getDefaultConversation(
|
|||||||
const lastName = casual.last_name;
|
const lastName = casual.last_name;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
avatarPlaceholderGradient: getAvatarPlaceholderGradient(0),
|
||||||
acceptedMessageRequest: true,
|
acceptedMessageRequest: true,
|
||||||
avatarUrl: getAvatarPath(),
|
avatarUrl: getAvatarPath(),
|
||||||
badges: [],
|
badges: [],
|
||||||
|
@@ -1,97 +0,0 @@
|
|||||||
// Copyright 2021 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import { assert } from 'chai';
|
|
||||||
|
|
||||||
import { shouldBlurAvatar } from '../../util/shouldBlurAvatar';
|
|
||||||
|
|
||||||
describe('shouldBlurAvatar', () => {
|
|
||||||
it('returns false for me', () => {
|
|
||||||
assert.isFalse(
|
|
||||||
shouldBlurAvatar({
|
|
||||||
isMe: true,
|
|
||||||
acceptedMessageRequest: false,
|
|
||||||
avatarUrl: '/path/to/avatar.jpg',
|
|
||||||
sharedGroupNames: [],
|
|
||||||
unblurredAvatarUrl: undefined,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns false if the message request has been accepted', () => {
|
|
||||||
assert.isFalse(
|
|
||||||
shouldBlurAvatar({
|
|
||||||
acceptedMessageRequest: true,
|
|
||||||
avatarUrl: '/path/to/avatar.jpg',
|
|
||||||
isMe: false,
|
|
||||||
sharedGroupNames: [],
|
|
||||||
unblurredAvatarUrl: undefined,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns false if there are any shared groups', () => {
|
|
||||||
assert.isFalse(
|
|
||||||
shouldBlurAvatar({
|
|
||||||
sharedGroupNames: ['Tahoe Trip'],
|
|
||||||
acceptedMessageRequest: false,
|
|
||||||
avatarUrl: '/path/to/avatar.jpg',
|
|
||||||
isMe: false,
|
|
||||||
unblurredAvatarUrl: undefined,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns false if there is no avatar', () => {
|
|
||||||
assert.isFalse(
|
|
||||||
shouldBlurAvatar({
|
|
||||||
acceptedMessageRequest: false,
|
|
||||||
isMe: false,
|
|
||||||
sharedGroupNames: [],
|
|
||||||
unblurredAvatarUrl: undefined,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
assert.isFalse(
|
|
||||||
shouldBlurAvatar({
|
|
||||||
avatarUrl: undefined,
|
|
||||||
acceptedMessageRequest: false,
|
|
||||||
isMe: false,
|
|
||||||
sharedGroupNames: [],
|
|
||||||
unblurredAvatarUrl: undefined,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
assert.isFalse(
|
|
||||||
shouldBlurAvatar({
|
|
||||||
avatarUrl: undefined,
|
|
||||||
unblurredAvatarUrl: '/some/other/path',
|
|
||||||
acceptedMessageRequest: false,
|
|
||||||
isMe: false,
|
|
||||||
sharedGroupNames: [],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns false if the avatar was unblurred', () => {
|
|
||||||
assert.isFalse(
|
|
||||||
shouldBlurAvatar({
|
|
||||||
avatarUrl: '/path/to/avatar.jpg',
|
|
||||||
unblurredAvatarUrl: '/path/to/avatar.jpg',
|
|
||||||
acceptedMessageRequest: false,
|
|
||||||
isMe: false,
|
|
||||||
sharedGroupNames: [],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns true if the stars align (i.e., not everything above)', () => {
|
|
||||||
assert.isTrue(
|
|
||||||
shouldBlurAvatar({
|
|
||||||
avatarUrl: '/path/to/avatar.jpg',
|
|
||||||
unblurredAvatarUrl: '/different/path.jpg',
|
|
||||||
acceptedMessageRequest: false,
|
|
||||||
isMe: false,
|
|
||||||
sharedGroupNames: [],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -6,7 +6,11 @@ import { assert } from 'chai';
|
|||||||
import * as durations from '../../util/durations';
|
import * as durations from '../../util/durations';
|
||||||
import type { App, Bootstrap } from './fixtures';
|
import type { App, Bootstrap } from './fixtures';
|
||||||
import { initStorage, debug } from './fixtures';
|
import { initStorage, debug } from './fixtures';
|
||||||
import { typeIntoInput, waitForEnabledComposer } from '../helpers';
|
import {
|
||||||
|
acceptConversation,
|
||||||
|
typeIntoInput,
|
||||||
|
waitForEnabledComposer,
|
||||||
|
} from '../helpers';
|
||||||
|
|
||||||
describe('storage service', function (this: Mocha.Suite) {
|
describe('storage service', function (this: Mocha.Suite) {
|
||||||
this.timeout(durations.MINUTE);
|
this.timeout(durations.MINUTE);
|
||||||
@@ -50,7 +54,6 @@ describe('storage service', function (this: Mocha.Suite) {
|
|||||||
const window = await app.getWindow();
|
const window = await app.getWindow();
|
||||||
|
|
||||||
const leftPane = window.locator('#LeftPane');
|
const leftPane = window.locator('#LeftPane');
|
||||||
const conversationStack = window.locator('.Inbox__conversation-stack');
|
|
||||||
|
|
||||||
debug('Opening conversation with a stranger');
|
debug('Opening conversation with a stranger');
|
||||||
debug(stranger.toContact().aci);
|
debug(stranger.toContact().aci);
|
||||||
@@ -77,14 +80,7 @@ describe('storage service', function (this: Mocha.Suite) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
debug('Accept conversation from a stranger');
|
debug('Accept conversation from a stranger');
|
||||||
await conversationStack
|
await acceptConversation(window);
|
||||||
.locator('.module-message-request-actions button >> "Accept"')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
await window
|
|
||||||
.locator('.MessageRequestActionsConfirmation')
|
|
||||||
.getByRole('button', { name: 'Accept' })
|
|
||||||
.click();
|
|
||||||
|
|
||||||
debug('Verify that storage state was updated');
|
debug('Verify that storage state was updated');
|
||||||
{
|
{
|
||||||
|
2
ts/types/Storage.d.ts
vendored
2
ts/types/Storage.d.ts
vendored
@@ -233,6 +233,8 @@ export type StorageAccessType = {
|
|||||||
|
|
||||||
postRegistrationSyncsStatus: 'incomplete' | 'complete';
|
postRegistrationSyncsStatus: 'incomplete' | 'complete';
|
||||||
|
|
||||||
|
avatarsHaveBeenMigrated: boolean;
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
'challenge:retry-message-ids': never;
|
'challenge:retry-message-ids': never;
|
||||||
nextSignedKeyRotationTime: number;
|
nextSignedKeyRotationTime: number;
|
||||||
|
@@ -82,11 +82,13 @@ export type StoryViewType = {
|
|||||||
readAt?: number;
|
readAt?: number;
|
||||||
sender: Pick<
|
sender: Pick<
|
||||||
ConversationType,
|
ConversationType,
|
||||||
|
| 'avatarPlaceholderGradient'
|
||||||
| 'acceptedMessageRequest'
|
| 'acceptedMessageRequest'
|
||||||
| 'avatarUrl'
|
| 'avatarUrl'
|
||||||
| 'badges'
|
| 'badges'
|
||||||
| 'color'
|
| 'color'
|
||||||
| 'firstName'
|
| 'firstName'
|
||||||
|
| 'hasAvatar'
|
||||||
| 'hideStory'
|
| 'hideStory'
|
||||||
| 'id'
|
| 'id'
|
||||||
| 'isMe'
|
| 'isMe'
|
||||||
|
@@ -7,6 +7,14 @@ import { isMe } from './whatTypeOfConversation';
|
|||||||
import { isSignalConversation } from './isSignalConversation';
|
import { isSignalConversation } from './isSignalConversation';
|
||||||
import { getLocalAttachmentUrl } from './getLocalAttachmentUrl';
|
import { getLocalAttachmentUrl } from './getLocalAttachmentUrl';
|
||||||
|
|
||||||
|
export function hasAvatar(
|
||||||
|
conversationAttrs: ConversationAttributesType
|
||||||
|
): boolean {
|
||||||
|
return Boolean(
|
||||||
|
getAvatar(conversationAttrs) || conversationAttrs.remoteAvatarUrl
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function getAvatarHash(
|
export function getAvatarHash(
|
||||||
conversationAttrs: ConversationAttributesType
|
conversationAttrs: ConversationAttributesType
|
||||||
): undefined | string {
|
): undefined | string {
|
||||||
@@ -65,29 +73,3 @@ export function getLocalProfileAvatarUrl(
|
|||||||
const avatar = conversationAttrs.profileAvatar || conversationAttrs.avatar;
|
const avatar = conversationAttrs.profileAvatar || conversationAttrs.avatar;
|
||||||
return avatar?.path ? getLocalAttachmentUrl(avatar) : undefined;
|
return avatar?.path ? getLocalAttachmentUrl(avatar) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLocalUnblurredAvatarUrl(
|
|
||||||
conversationAttrs: ConversationAttributesType
|
|
||||||
): string | undefined {
|
|
||||||
const { unblurredAvatarPath, unblurredAvatarUrl } = conversationAttrs;
|
|
||||||
if (unblurredAvatarUrl != null) {
|
|
||||||
return unblurredAvatarUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unblurredAvatarPath == null) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compatibility mode
|
|
||||||
const avatar = getAvatar(conversationAttrs);
|
|
||||||
|
|
||||||
// Since we use `unblurredAvatarUrl` only for equality checks - if the path
|
|
||||||
// is the same - return equivalent url
|
|
||||||
if (avatar?.path === unblurredAvatarPath) {
|
|
||||||
return getLocalAvatarUrl(conversationAttrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise generate some valid url, but it will never be the same because of
|
|
||||||
// absent "size".
|
|
||||||
return getLocalAttachmentUrl({ path: unblurredAvatarPath });
|
|
||||||
}
|
|
||||||
|
@@ -17,11 +17,11 @@ import { canEditGroupInfo } from './canEditGroupInfo';
|
|||||||
import { dropNull } from './dropNull';
|
import { dropNull } from './dropNull';
|
||||||
import { getAboutText } from './getAboutText';
|
import { getAboutText } from './getAboutText';
|
||||||
import {
|
import {
|
||||||
getLocalUnblurredAvatarUrl,
|
|
||||||
getAvatarHash,
|
getAvatarHash,
|
||||||
getLocalAvatarUrl,
|
getLocalAvatarUrl,
|
||||||
getLocalProfileAvatarUrl,
|
getLocalProfileAvatarUrl,
|
||||||
getRawAvatarPath,
|
getRawAvatarPath,
|
||||||
|
hasAvatar,
|
||||||
} from './avatarUtils';
|
} from './avatarUtils';
|
||||||
import { getAvatarData } from './getAvatarData';
|
import { getAvatarData } from './getAvatarData';
|
||||||
import { getConversationMembers } from './getConversationMembers';
|
import { getConversationMembers } from './getConversationMembers';
|
||||||
@@ -46,16 +46,17 @@ import {
|
|||||||
isMe,
|
isMe,
|
||||||
} from './whatTypeOfConversation';
|
} from './whatTypeOfConversation';
|
||||||
import {
|
import {
|
||||||
|
areWePending,
|
||||||
getBannedMemberships,
|
getBannedMemberships,
|
||||||
getMembersCount,
|
getMembersCount,
|
||||||
getMemberships,
|
getMemberships,
|
||||||
getPendingApprovalMemberships,
|
getPendingApprovalMemberships,
|
||||||
getPendingMemberships,
|
getPendingMemberships,
|
||||||
isMember,
|
|
||||||
isMemberAwaitingApproval,
|
isMemberAwaitingApproval,
|
||||||
isMemberPending,
|
|
||||||
} from './groupMembershipUtils';
|
} from './groupMembershipUtils';
|
||||||
import { isNotNil } from './isNotNil';
|
import { isNotNil } from './isNotNil';
|
||||||
|
import { getIdentifierHash } from '../Crypto';
|
||||||
|
import { getAvatarPlaceholderGradient } from '../utils/getAvatarPlaceholderGradient';
|
||||||
|
|
||||||
const EMPTY_ARRAY: Readonly<[]> = [];
|
const EMPTY_ARRAY: Readonly<[]> = [];
|
||||||
const EMPTY_GROUP_COLLISIONS: GroupNameCollisionsWithIdsByTitle = {};
|
const EMPTY_GROUP_COLLISIONS: GroupNameCollisionsWithIdsByTitle = {};
|
||||||
@@ -88,7 +89,13 @@ export function getConversation(model: ConversationModel): ConversationType {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const ourAci = window.textsecure.storage.user.getAci();
|
const ourAci = window.textsecure.storage.user.getAci();
|
||||||
const ourPni = window.textsecure.storage.user.getPni();
|
|
||||||
|
const identifierHash = getIdentifierHash({
|
||||||
|
aci: isAciString(attributes.serviceId) ? attributes.serviceId : undefined,
|
||||||
|
e164: attributes.e164,
|
||||||
|
pni: attributes.pni,
|
||||||
|
groupId: attributes.groupId,
|
||||||
|
});
|
||||||
|
|
||||||
const color = migrateColor(attributes.color, {
|
const color = migrateColor(attributes.color, {
|
||||||
aci: isAciString(attributes.serviceId) ? attributes.serviceId : undefined,
|
aci: isAciString(attributes.serviceId) ? attributes.serviceId : undefined,
|
||||||
@@ -97,6 +104,11 @@ export function getConversation(model: ConversationModel): ConversationType {
|
|||||||
groupId: attributes.groupId,
|
groupId: attributes.groupId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const avatarPlaceholderGradient =
|
||||||
|
hasAvatar(attributes) && identifierHash != null
|
||||||
|
? getAvatarPlaceholderGradient(identifierHash)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const { draftTimestamp, draftEditMessage, timestamp } = attributes;
|
const { draftTimestamp, draftEditMessage, timestamp } = attributes;
|
||||||
const draftPreview = getDraftPreview(attributes);
|
const draftPreview = getDraftPreview(attributes);
|
||||||
const draftText = dropNull(attributes.draft);
|
const draftText = dropNull(attributes.draft);
|
||||||
@@ -142,20 +154,14 @@ export function getConversation(model: ConversationModel): ConversationType {
|
|||||||
acceptedMessageRequest: isConversationAccepted(attributes),
|
acceptedMessageRequest: isConversationAccepted(attributes),
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
activeAt: attributes.active_at!,
|
activeAt: attributes.active_at!,
|
||||||
areWePending:
|
areWePending: areWePending(attributes),
|
||||||
ourAci &&
|
|
||||||
(isMemberPending(attributes, ourAci) ||
|
|
||||||
Boolean(
|
|
||||||
ourPni &&
|
|
||||||
!isMember(attributes, ourAci) &&
|
|
||||||
isMemberPending(attributes, ourPni)
|
|
||||||
)),
|
|
||||||
areWePendingApproval: Boolean(
|
areWePendingApproval: Boolean(
|
||||||
ourConversationId &&
|
ourConversationId &&
|
||||||
ourAci &&
|
ourAci &&
|
||||||
isMemberAwaitingApproval(attributes, ourAci)
|
isMemberAwaitingApproval(attributes, ourAci)
|
||||||
),
|
),
|
||||||
areWeAdmin: areWeAdmin(attributes),
|
areWeAdmin: areWeAdmin(attributes),
|
||||||
|
avatarPlaceholderGradient,
|
||||||
avatars: getAvatarData(attributes),
|
avatars: getAvatarData(attributes),
|
||||||
badges: attributes.badges ?? EMPTY_ARRAY,
|
badges: attributes.badges ?? EMPTY_ARRAY,
|
||||||
canChangeTimer: canChangeTimer(attributes),
|
canChangeTimer: canChangeTimer(attributes),
|
||||||
@@ -164,8 +170,8 @@ export function getConversation(model: ConversationModel): ConversationType {
|
|||||||
avatarUrl: getLocalAvatarUrl(attributes),
|
avatarUrl: getLocalAvatarUrl(attributes),
|
||||||
rawAvatarPath: getRawAvatarPath(attributes),
|
rawAvatarPath: getRawAvatarPath(attributes),
|
||||||
avatarHash: getAvatarHash(attributes),
|
avatarHash: getAvatarHash(attributes),
|
||||||
unblurredAvatarUrl: getLocalUnblurredAvatarUrl(attributes),
|
|
||||||
profileAvatarUrl: getLocalProfileAvatarUrl(attributes),
|
profileAvatarUrl: getLocalProfileAvatarUrl(attributes),
|
||||||
|
hasAvatar: hasAvatar(attributes),
|
||||||
color,
|
color,
|
||||||
conversationColor: attributes.conversationColor,
|
conversationColor: attributes.conversationColor,
|
||||||
customColor,
|
customColor,
|
||||||
|
@@ -184,3 +184,22 @@ export function getMemberships(
|
|||||||
aci: member.aci,
|
aci: member.aci,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function areWePending(
|
||||||
|
conversationAttrs: Pick<
|
||||||
|
ConversationAttributesType,
|
||||||
|
'groupId' | 'groupVersion' | 'pendingMembersV2'
|
||||||
|
>
|
||||||
|
): boolean {
|
||||||
|
const ourAci = window.textsecure.storage.user.getAci();
|
||||||
|
const ourPni = window.textsecure.storage.user.getPni();
|
||||||
|
return Boolean(
|
||||||
|
ourAci &&
|
||||||
|
(isMemberPending(conversationAttrs, ourAci) ||
|
||||||
|
Boolean(
|
||||||
|
ourPni &&
|
||||||
|
!isMember(conversationAttrs, ourAci) &&
|
||||||
|
isMemberPending(conversationAttrs, ourPni)
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@@ -1,28 +0,0 @@
|
|||||||
// Copyright 2021 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
|
||||||
|
|
||||||
export const shouldBlurAvatar = ({
|
|
||||||
acceptedMessageRequest,
|
|
||||||
avatarUrl,
|
|
||||||
isMe,
|
|
||||||
sharedGroupNames,
|
|
||||||
unblurredAvatarUrl,
|
|
||||||
}: Readonly<
|
|
||||||
Pick<
|
|
||||||
ConversationType,
|
|
||||||
| 'acceptedMessageRequest'
|
|
||||||
| 'avatarUrl'
|
|
||||||
| 'isMe'
|
|
||||||
| 'sharedGroupNames'
|
|
||||||
| 'unblurredAvatarUrl'
|
|
||||||
>
|
|
||||||
>): boolean =>
|
|
||||||
Boolean(
|
|
||||||
!isMe &&
|
|
||||||
!acceptedMessageRequest &&
|
|
||||||
!sharedGroupNames.length &&
|
|
||||||
avatarUrl &&
|
|
||||||
avatarUrl !== unblurredAvatarUrl
|
|
||||||
);
|
|
33
ts/utils/getAvatarPlaceholderGradient.ts
Normal file
33
ts/utils/getAvatarPlaceholderGradient.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2025 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
const GRADIENTS = [
|
||||||
|
['#252568', '#9C8F8F'],
|
||||||
|
['#2A4275', '#9D9EA1'],
|
||||||
|
['#2E4B5F', '#8AA9B1'],
|
||||||
|
['#2E426C', '#7A9377'],
|
||||||
|
['#1A341A', '#807F6E'],
|
||||||
|
['#464E42', '#D5C38F'],
|
||||||
|
['#595643', '#93A899'],
|
||||||
|
['#2C2F36', '#687466'],
|
||||||
|
['#2B1E18', '#968980'],
|
||||||
|
['#7B7067', '#A5A893'],
|
||||||
|
['#706359', '#BDA194'],
|
||||||
|
['#383331', '#A48788'],
|
||||||
|
['#924F4F', '#897A7A'],
|
||||||
|
['#663434', '#C58D77'],
|
||||||
|
['#8F4B02', '#AA9274'],
|
||||||
|
['#784747', '#8C8F6F'],
|
||||||
|
['#747474', '#ACACAC'],
|
||||||
|
['#49484C', '#A5A6B5'],
|
||||||
|
['#4A4E4D', '#ABAFAE'],
|
||||||
|
['#3A3A3A', '#929887'],
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export function getAvatarPlaceholderGradient(
|
||||||
|
identifierHash: number
|
||||||
|
): Readonly<[string, string]> {
|
||||||
|
const colorIndex = identifierHash % GRADIENTS.length;
|
||||||
|
|
||||||
|
return GRADIENTS[colorIndex];
|
||||||
|
}
|
Reference in New Issue
Block a user