Introduce TitleTransition notification
This commit is contained in:
parent
09b5e6ef50
commit
3469a748fb
16 changed files with 336 additions and 51 deletions
|
@ -720,6 +720,7 @@ export class ConversationController {
|
|||
(targetOldServiceIds.pni !== pni ||
|
||||
(aci && targetOldServiceIds.aci !== aci))
|
||||
) {
|
||||
targetConversation.unset('needsTitleTransition');
|
||||
mergePromises.push(
|
||||
targetConversation.addPhoneNumberDiscoveryIfNeeded(
|
||||
targetOldServiceIds.pni
|
||||
|
@ -1056,6 +1057,8 @@ export class ConversationController {
|
|||
}
|
||||
current.set('active_at', activeAt);
|
||||
|
||||
const currentHadMessages = (current.get('messageCount') ?? 0) > 0;
|
||||
|
||||
const dataToCopy: Partial<ConversationAttributesType> = pick(
|
||||
obsolete.attributes,
|
||||
[
|
||||
|
@ -1067,6 +1070,7 @@ export class ConversationController {
|
|||
'draftTimestamp',
|
||||
'messageCount',
|
||||
'messageRequestResponseType',
|
||||
'needsTitleTransition',
|
||||
'profileSharing',
|
||||
'quotedMessageId',
|
||||
'sentMessageCount',
|
||||
|
@ -1196,7 +1200,15 @@ export class ConversationController {
|
|||
const titleIsUseful = Boolean(
|
||||
obsoleteTitleInfo && getTitleNoDefault(obsoleteTitleInfo)
|
||||
);
|
||||
if (obsoleteTitleInfo && titleIsUseful && obsoleteHadMessages) {
|
||||
// If both conversations had messages - add merge
|
||||
if (
|
||||
titleIsUseful &&
|
||||
conversationType === 'private' &&
|
||||
currentHadMessages &&
|
||||
obsoleteHadMessages
|
||||
) {
|
||||
assertDev(obsoleteTitleInfo, 'part of titleIsUseful boolean');
|
||||
|
||||
drop(current.addConversationMerge(obsoleteTitleInfo));
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,39 @@ export enum SystemMessageKind {
|
|||
}
|
||||
|
||||
export type PropsType = {
|
||||
icon: string;
|
||||
icon:
|
||||
| 'audio-incoming'
|
||||
| 'audio-missed'
|
||||
| 'audio-outgoing'
|
||||
| 'group'
|
||||
| 'group-access'
|
||||
| 'group-add'
|
||||
| 'group-approved'
|
||||
| 'group-avatar'
|
||||
| 'group-decline'
|
||||
| 'group-edit'
|
||||
| 'group-leave'
|
||||
| 'group-remove'
|
||||
| 'group-summary'
|
||||
| 'info'
|
||||
| 'phone'
|
||||
| 'profile'
|
||||
| 'safety-number'
|
||||
| 'session-refresh'
|
||||
| 'thread'
|
||||
| 'timer'
|
||||
| 'timer-disabled'
|
||||
| 'unsupported'
|
||||
| 'unsupported--can-process'
|
||||
| 'verified'
|
||||
| 'verified-not'
|
||||
| 'video'
|
||||
| 'video-incoming'
|
||||
| 'video-missed'
|
||||
| 'video-outgoing'
|
||||
| 'warning'
|
||||
| 'payment-event'
|
||||
| 'merge';
|
||||
contents: ReactNode;
|
||||
button?: ReactNode;
|
||||
kind?: SystemMessageKind;
|
||||
|
|
|
@ -193,6 +193,12 @@ export function Notification(): JSX.Element {
|
|||
timestamp: Date.now(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'titleTransitionNotification',
|
||||
data: {
|
||||
oldTitle: 'alice.01',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'callHistory',
|
||||
data: {
|
||||
|
|
|
@ -20,6 +20,8 @@ import type { PropsDataType as DeliveryIssueProps } from './DeliveryIssueNotific
|
|||
import { DeliveryIssueNotification } from './DeliveryIssueNotification';
|
||||
import type { PropsData as ChangeNumberNotificationProps } from './ChangeNumberNotification';
|
||||
import { ChangeNumberNotification } from './ChangeNumberNotification';
|
||||
import type { PropsData as TitleTransitionNotificationProps } from './TitleTransitionNotification';
|
||||
import { TitleTransitionNotification } from './TitleTransitionNotification';
|
||||
import type { CallingNotificationType } from '../../util/callingNotification';
|
||||
import { InlineNotificationWrapper } from './InlineNotificationWrapper';
|
||||
import type { PropsData as UnsupportedMessageProps } from './UnsupportedMessage';
|
||||
|
@ -91,6 +93,10 @@ type ChangeNumberNotificationType = {
|
|||
type: 'changeNumberNotification';
|
||||
data: ChangeNumberNotificationProps;
|
||||
};
|
||||
type TitleTransitionNotificationType = {
|
||||
type: 'titleTransitionNotification';
|
||||
data: TitleTransitionNotificationProps;
|
||||
};
|
||||
type SafetyNumberNotificationType = {
|
||||
type: 'safetyNumberNotification';
|
||||
data: SafetyNumberNotificationProps;
|
||||
|
@ -148,6 +154,7 @@ export type TimelineItemType = (
|
|||
| SafetyNumberNotificationType
|
||||
| TimerNotificationType
|
||||
| UniversalTimerNotificationType
|
||||
| TitleTransitionNotificationType
|
||||
| ContactRemovedNotificationType
|
||||
| UnsupportedMessageType
|
||||
| VerificationNotificationType
|
||||
|
@ -296,6 +303,14 @@ export const TimelineItem = memo(function TimelineItem({
|
|||
i18n={i18n}
|
||||
/>
|
||||
);
|
||||
} else if (item.type === 'titleTransitionNotification') {
|
||||
notification = (
|
||||
<TitleTransitionNotification
|
||||
{...reducedProps}
|
||||
{...item.data}
|
||||
i18n={i18n}
|
||||
/>
|
||||
);
|
||||
} else if (item.type === 'safetyNumberNotification') {
|
||||
notification = (
|
||||
<SafetyNumberNotification
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as React from 'react';
|
||||
import type { Meta } from '@storybook/react';
|
||||
import { setupI18n } from '../../util/setupI18n';
|
||||
import enMessages from '../../../_locales/en/messages.json';
|
||||
import type { Props } from './TitleTransitionNotification';
|
||||
import { TitleTransitionNotification } from './TitleTransitionNotification';
|
||||
|
||||
export default {
|
||||
title: 'Components/Conversation/TitleTransitionNotification',
|
||||
} satisfies Meta<Props>;
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
export function Default(): JSX.Element {
|
||||
return <TitleTransitionNotification oldTitle="alice.01" i18n={i18n} />;
|
||||
}
|
39
ts/components/conversation/TitleTransitionNotification.tsx
Normal file
39
ts/components/conversation/TitleTransitionNotification.tsx
Normal file
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import { Intl } from '../Intl';
|
||||
|
||||
import { SystemMessage } from './SystemMessage';
|
||||
import { UserText } from '../UserText';
|
||||
|
||||
export type PropsData = {
|
||||
oldTitle: string;
|
||||
};
|
||||
|
||||
export type PropsHousekeeping = {
|
||||
i18n: LocalizerType;
|
||||
};
|
||||
|
||||
export type Props = PropsData & PropsHousekeeping;
|
||||
|
||||
export function TitleTransitionNotification(props: Props): JSX.Element {
|
||||
const { i18n, oldTitle } = props;
|
||||
|
||||
return (
|
||||
<SystemMessage
|
||||
contents={
|
||||
<Intl
|
||||
id="icu:TitleTransition--notification"
|
||||
components={{
|
||||
oldTitle: <UserText text={oldTitle} />,
|
||||
}}
|
||||
i18n={i18n}
|
||||
/>
|
||||
}
|
||||
icon="thread"
|
||||
/>
|
||||
);
|
||||
}
|
5
ts/model-types.d.ts
vendored
5
ts/model-types.d.ts
vendored
|
@ -191,6 +191,7 @@ export type MessageAttributesType = {
|
|||
| 'timer-notification'
|
||||
| 'universal-timer-notification'
|
||||
| 'contact-removed-notification'
|
||||
| 'title-transition-notification'
|
||||
| 'verified-change';
|
||||
body?: string;
|
||||
attachments?: Array<AttachmentType>;
|
||||
|
@ -225,6 +226,9 @@ export type MessageAttributesType = {
|
|||
conversationMerge?: {
|
||||
renderInfo: ConversationRenderInfoType;
|
||||
};
|
||||
titleTransition?: {
|
||||
renderInfo: ConversationRenderInfoType;
|
||||
};
|
||||
|
||||
// Legacy fields for timer update notification only
|
||||
flags?: number;
|
||||
|
@ -338,6 +342,7 @@ export type ConversationAttributesType = {
|
|||
profileKeyCredential?: string | null;
|
||||
profileKeyCredentialExpiration?: number | null;
|
||||
lastProfile?: ConversationLastProfileType;
|
||||
needsTitleTransition?: boolean;
|
||||
quotedMessageId?: string | null;
|
||||
sealedSender?: unknown;
|
||||
sentMessageCount?: number;
|
||||
|
|
|
@ -95,6 +95,8 @@ import {
|
|||
getProfileName,
|
||||
getTitle,
|
||||
getTitleNoDefault,
|
||||
hasNumberTitle,
|
||||
hasUsernameTitle,
|
||||
canHaveUsername,
|
||||
} from '../util/getTitle';
|
||||
import { markConversationRead } from '../util/markConversationRead';
|
||||
|
@ -3717,10 +3719,13 @@ export class ConversationModel extends window.Backbone
|
|||
};
|
||||
|
||||
const isEditMessage = Boolean(message.get('editHistory'));
|
||||
const needsTitleTransition =
|
||||
hasNumberTitle(this.attributes) || hasUsernameTitle(this.attributes);
|
||||
|
||||
this.set({
|
||||
...draftProperties,
|
||||
...(enabledProfileSharing ? { profileSharing: true } : {}),
|
||||
...(needsTitleTransition ? { needsTitleTransition: true } : {}),
|
||||
...(dontAddMessage
|
||||
? {}
|
||||
: this.incrementSentMessageCount({ dry: true })),
|
||||
|
@ -4013,17 +4018,38 @@ export class ConversationModel extends window.Backbone
|
|||
const ourConversationId =
|
||||
window.ConversationController.getOurConversationId();
|
||||
|
||||
const oldUsername = this.get('username');
|
||||
|
||||
// Clear username once we have other information about the contact
|
||||
if (
|
||||
canHaveUsername(this.attributes, ourConversationId) ||
|
||||
!this.get('username')
|
||||
) {
|
||||
if (canHaveUsername(this.attributes, ourConversationId) || !oldUsername) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info(`maybeClearUsername(${this.idForLogging()}): clearing username`);
|
||||
|
||||
this.unset('username');
|
||||
|
||||
if (this.get('needsTitleTransition') && getProfileName(this.attributes)) {
|
||||
log.info(
|
||||
`maybeClearUsername(${this.idForLogging()}): adding a notification`
|
||||
);
|
||||
const { type, e164, username } = this.attributes;
|
||||
|
||||
this.unset('needsTitleTransition');
|
||||
|
||||
await this.addNotification('title-transition-notification', {
|
||||
readStatus: ReadStatus.Read,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
titleTransition: {
|
||||
renderInfo: {
|
||||
type,
|
||||
e164,
|
||||
username,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
window.Signal.Data.updateConversation(this.attributes);
|
||||
this.captureChange('clearUsername');
|
||||
}
|
||||
|
@ -4684,37 +4710,63 @@ export class ConversationModel extends window.Backbone
|
|||
const oldProfileKey = this.get('profileKey');
|
||||
|
||||
// profileKey is a string so we can compare it directly
|
||||
if (oldProfileKey !== profileKey) {
|
||||
log.info(
|
||||
`Setting sealedSender to UNKNOWN for conversation ${this.idForLogging()}`
|
||||
);
|
||||
this.set({
|
||||
profileKeyCredential: null,
|
||||
profileKeyCredentialExpiration: null,
|
||||
accessKey: null,
|
||||
sealedSender: SEALED_SENDER.UNKNOWN,
|
||||
});
|
||||
|
||||
// Don't trigger immediate profile fetches when syncing to remote storage
|
||||
this.set({ profileKey }, { silent: viaStorageServiceSync });
|
||||
|
||||
// If our profile key was cleared above, we don't tell our linked devices about it.
|
||||
// We want linked devices to tell us what it should be, instead of telling them to
|
||||
// erase their local value.
|
||||
if (!viaStorageServiceSync && profileKey) {
|
||||
this.captureChange('profileKey');
|
||||
}
|
||||
|
||||
this.deriveAccessKeyIfNeeded();
|
||||
|
||||
// We will update the conversation during storage service sync
|
||||
if (!viaStorageServiceSync) {
|
||||
window.Signal.Data.updateConversation(this.attributes);
|
||||
}
|
||||
|
||||
return true;
|
||||
if (oldProfileKey === profileKey) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
log.info(
|
||||
`Setting sealedSender to UNKNOWN for conversation ${this.idForLogging()}`
|
||||
);
|
||||
this.set({
|
||||
profileKeyCredential: null,
|
||||
profileKeyCredentialExpiration: null,
|
||||
accessKey: null,
|
||||
sealedSender: SEALED_SENDER.UNKNOWN,
|
||||
});
|
||||
|
||||
// We messaged the contact when it had either phone number or username
|
||||
// title.
|
||||
if (this.get('needsTitleTransition')) {
|
||||
log.info(
|
||||
`setProfileKey(${this.idForLogging()}): adding a ` +
|
||||
'title transition notification'
|
||||
);
|
||||
|
||||
const { type, e164, username } = this.attributes;
|
||||
|
||||
this.unset('needsTitleTransition');
|
||||
|
||||
await this.addNotification('title-transition-notification', {
|
||||
readStatus: ReadStatus.Read,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
titleTransition: {
|
||||
renderInfo: {
|
||||
type,
|
||||
e164,
|
||||
username,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Don't trigger immediate profile fetches when syncing to remote storage
|
||||
this.set({ profileKey }, { silent: viaStorageServiceSync });
|
||||
|
||||
// If our profile key was cleared above, we don't tell our linked devices about it.
|
||||
// We want linked devices to tell us what it should be, instead of telling them to
|
||||
// erase their local value.
|
||||
if (!viaStorageServiceSync && profileKey) {
|
||||
this.captureChange('profileKey');
|
||||
}
|
||||
|
||||
this.deriveAccessKeyIfNeeded();
|
||||
|
||||
// We will update the conversation during storage service sync
|
||||
if (!viaStorageServiceSync) {
|
||||
window.Signal.Data.updateConversation(this.attributes);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
hasProfileKeyCredentialExpired(): boolean {
|
||||
|
|
|
@ -95,6 +95,7 @@ import {
|
|||
isVerifiedChange,
|
||||
isConversationMerge,
|
||||
isPhoneNumberDiscovery,
|
||||
isTitleTransitionNotification,
|
||||
} from '../state/selectors/message';
|
||||
import type { ReactionAttributesType } from '../messageModifiers/Reactions';
|
||||
import { isInCall } from '../state/selectors/calling';
|
||||
|
@ -287,6 +288,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
!isGroupV2Change(attributes) &&
|
||||
!isKeyChange(attributes) &&
|
||||
!isPhoneNumberDiscovery(attributes) &&
|
||||
!isTitleTransitionNotification(attributes) &&
|
||||
!isProfileChange(attributes) &&
|
||||
!isUniversalTimerNotification(attributes) &&
|
||||
!isUnsupportedMessage(attributes) &&
|
||||
|
@ -616,6 +618,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
isUniversalTimerNotification(attributes);
|
||||
const isConversationMergeValue = isConversationMerge(attributes);
|
||||
const isPhoneNumberDiscoveryValue = isPhoneNumberDiscovery(attributes);
|
||||
const isTitleTransitionNotificationValue =
|
||||
isTitleTransitionNotification(attributes);
|
||||
|
||||
const isPayment = messageHasPaymentEvent(attributes);
|
||||
|
||||
|
@ -648,7 +652,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
isProfileChangeValue ||
|
||||
isUniversalTimerNotificationValue ||
|
||||
isConversationMergeValue ||
|
||||
isPhoneNumberDiscoveryValue;
|
||||
isPhoneNumberDiscoveryValue ||
|
||||
isTitleTransitionNotificationValue;
|
||||
|
||||
return !hasSomethingToDisplay;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import type { PropsData as TimerNotificationProps } from '../../components/conve
|
|||
import type { PropsData as ChangeNumberNotificationProps } from '../../components/conversation/ChangeNumberNotification';
|
||||
import type { PropsData as SafetyNumberNotificationProps } from '../../components/conversation/SafetyNumberNotification';
|
||||
import type { PropsData as VerificationNotificationProps } from '../../components/conversation/VerificationNotification';
|
||||
import type { PropsData as TitleTransitionNotificationProps } from '../../components/conversation/TitleTransitionNotification';
|
||||
import type { PropsDataType as GroupsV2Props } from '../../components/conversation/GroupV2Change';
|
||||
import type { PropsDataType as GroupV1MigrationPropsType } from '../../components/conversation/GroupV1Migration';
|
||||
import type { PropsDataType as DeliveryIssuePropsType } from '../../components/conversation/DeliveryIssueNotification';
|
||||
|
@ -129,6 +130,7 @@ import type { AnyPaymentEvent } from '../../types/Payment';
|
|||
import { isPaymentNotificationEvent } from '../../types/Payment';
|
||||
import {
|
||||
getTitleNoDefault,
|
||||
getTitle,
|
||||
getNumber,
|
||||
renderNumber,
|
||||
} from '../../util/getTitle';
|
||||
|
@ -922,6 +924,13 @@ export function getPropsForBubble(
|
|||
timestamp,
|
||||
};
|
||||
}
|
||||
if (isTitleTransitionNotification(message)) {
|
||||
return {
|
||||
type: 'titleTransitionNotification',
|
||||
data: getPropsForTitleTransitionNotification(message),
|
||||
timestamp,
|
||||
};
|
||||
}
|
||||
if (isChatSessionRefreshed(message)) {
|
||||
return {
|
||||
type: 'chatSessionRefreshed',
|
||||
|
@ -1490,6 +1499,31 @@ function getPropsForChangeNumberNotification(
|
|||
};
|
||||
}
|
||||
|
||||
// Title Transition Notification
|
||||
|
||||
export function isTitleTransitionNotification(
|
||||
message: MessageWithUIFieldsType
|
||||
): boolean {
|
||||
return (
|
||||
message.type === 'title-transition-notification' &&
|
||||
message.titleTransition != null
|
||||
);
|
||||
}
|
||||
|
||||
function getPropsForTitleTransitionNotification(
|
||||
message: MessageWithUIFieldsType
|
||||
): TitleTransitionNotificationProps {
|
||||
strictAssert(
|
||||
message.titleTransition != null,
|
||||
'Invalid attributes for title-transition-notification'
|
||||
);
|
||||
const { renderInfo } = message.titleTransition;
|
||||
const oldTitle = getTitle(renderInfo);
|
||||
return {
|
||||
oldTitle,
|
||||
};
|
||||
}
|
||||
|
||||
// Chat Session Refreshed
|
||||
|
||||
export function isChatSessionRefreshed(
|
||||
|
|
|
@ -58,18 +58,12 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
|||
whitelisted: true,
|
||||
serviceE164: pniContact.device.number,
|
||||
identityKey: pniContact.getPublicKey(ServiceIdKind.PNI).serialize(),
|
||||
givenName: 'PNI Contact',
|
||||
givenName: undefined,
|
||||
familyName: undefined,
|
||||
},
|
||||
ServiceIdKind.PNI
|
||||
);
|
||||
|
||||
state = state.addContact(pniContact, {
|
||||
whitelisted: true,
|
||||
serviceE164: undefined,
|
||||
identityKey: pniContact.publicKey.serialize(),
|
||||
profileKey: pniContact.profileKey.serialize(),
|
||||
});
|
||||
|
||||
// Just to make PNI Contact visible in the left pane
|
||||
state = state.pin(pniContact, ServiceIdKind.PNI);
|
||||
|
||||
|
@ -319,6 +313,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
|||
await pniContact.sendText(desktop, 'Hello Desktop!', {
|
||||
timestamp: bootstrap.getTimestamp(),
|
||||
withPniSignature: true,
|
||||
withProfileKey: true,
|
||||
});
|
||||
|
||||
debug('Wait for merge to happen');
|
||||
|
@ -377,15 +372,12 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
|||
const messages = window.locator('.module-message__text');
|
||||
assert.strictEqual(await messages.count(), 3, 'messages');
|
||||
|
||||
// Merge notification
|
||||
// Title transition notification
|
||||
const notifications = window.locator('.SystemMessage');
|
||||
assert.strictEqual(await notifications.count(), 1, 'notifications');
|
||||
|
||||
const first = await notifications.first();
|
||||
assert.match(
|
||||
await first.innerText(),
|
||||
/Your message history with ACI Contact and their number .* has been merged./
|
||||
);
|
||||
assert.match(await first.innerText(), /You started this chat with/);
|
||||
|
||||
assert.isEmpty(await phone.getOrphanedStorageKeys());
|
||||
}
|
||||
|
|
|
@ -93,7 +93,15 @@ describe('pnp/username', function (this: Mocha.Suite) {
|
|||
.locator(
|
||||
`[data-testid="${usernameContact.device.aci}"] >> "${USERNAME}"`
|
||||
)
|
||||
.waitFor();
|
||||
.click();
|
||||
|
||||
debug('Send message to username');
|
||||
{
|
||||
const compositionInput = await app.waitForEnabledComposer();
|
||||
|
||||
await compositionInput.type('Hello username');
|
||||
await compositionInput.press('Enter');
|
||||
}
|
||||
|
||||
let state = await phone.expectStorageState('consistency check');
|
||||
|
||||
|
@ -141,6 +149,31 @@ describe('pnp/username', function (this: Mocha.Suite) {
|
|||
assert.strictEqual(removed[0].contact?.aci, usernameContact.device.aci);
|
||||
assert.strictEqual(removed[0].contact?.username, USERNAME);
|
||||
}
|
||||
|
||||
if (type === 'system') {
|
||||
// No notifications
|
||||
const notifications = window.locator('.SystemMessage');
|
||||
assert.strictEqual(
|
||||
await notifications.count(),
|
||||
0,
|
||||
'notification count'
|
||||
);
|
||||
} else {
|
||||
// One notification - the username transition
|
||||
const notifications = window.locator('.SystemMessage');
|
||||
await notifications.waitFor();
|
||||
assert.strictEqual(
|
||||
await notifications.count(),
|
||||
1,
|
||||
'notification count'
|
||||
);
|
||||
|
||||
const first = await notifications.first();
|
||||
assert.strictEqual(
|
||||
await first.innerText(),
|
||||
`You started this chat with ${USERNAME}`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -143,3 +143,30 @@ export function renderNumber(e164: string): string | undefined {
|
|||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function hasNumberTitle(
|
||||
attributes: Pick<
|
||||
ConversationAttributesType,
|
||||
'e164' | 'type' | 'sharingPhoneNumber' | 'profileKey'
|
||||
>
|
||||
): boolean {
|
||||
return (
|
||||
!getSystemName(attributes) &&
|
||||
!getProfileName(attributes) &&
|
||||
Boolean(getNumber(attributes))
|
||||
);
|
||||
}
|
||||
|
||||
export function hasUsernameTitle(
|
||||
attributes: Pick<
|
||||
ConversationAttributesType,
|
||||
'e164' | 'type' | 'sharingPhoneNumber' | 'profileKey' | 'username'
|
||||
>
|
||||
): boolean {
|
||||
return (
|
||||
!getSystemName(attributes) &&
|
||||
!getProfileName(attributes) &&
|
||||
!getNumber(attributes) &&
|
||||
Boolean(attributes.username)
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue