diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 7f699102a3a1..68ccc0bef2ff 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -2727,6 +2727,38 @@
"message": "Accept",
"description": "Shown as a button to let the user accept a message request"
},
+ "MessageRequests--continue": {
+ "message": "Continue",
+ "description": "Shown as a button to share your profile, necessary to continue messaging in a conversation"
+ },
+ "MessageRequests--profile-sharing--group": {
+ "message": "Continue your conversation with this group and share your name and photo with its members? $learnMore$",
+ "description": "Shown when user hasn't shared their profile in a group yet",
+ "placeholders": {
+ "learnMore": {
+ "content": "$1",
+ "example": "Learn More."
+ }
+ }
+ },
+ "MessageRequests--profile-sharing--direct": {
+ "message": "Continue this conversation with $firstName$ and share your name and photo with them? $learnMore$",
+ "description": "Shown when user hasn't shared their profile in a 1:1 conversation yet",
+ "placeholders": {
+ "firstName": {
+ "content": "$1",
+ "example": "Alice"
+ },
+ "learnMore": {
+ "content": "$2",
+ "example": "Learn More."
+ }
+ }
+ },
+ "MessageRequests--learn-more": {
+ "message": "Learn more.",
+ "description": "Shown at the end of profile sharing messages as a link."
+ },
"ConversationHero--members": {
"message": "$count$ members",
"description": "Specifies the number of members in a group conversation",
diff --git a/js/views/safety_number_change_dialog_view.js b/js/views/safety_number_change_dialog_view.js
index 6ad41c71e453..f27920901539 100644
--- a/js/views/safety_number_change_dialog_view.js
+++ b/js/views/safety_number_change_dialog_view.js
@@ -11,7 +11,7 @@
Component: window.Signal.Components.SafetyNumberChangeDialog,
props: {
confirmText: options.confirmText,
- contacts: options.contacts.map(contact => contact.cachedProps),
+ contacts: options.contacts.map(contact => contact.format()),
i18n: window.i18n,
onCancel: () => {
dialog.remove();
diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss
index 93189ddd9f4d..9556d6f851e3 100644
--- a/stylesheets/_modules.scss
+++ b/stylesheets/_modules.scss
@@ -3597,6 +3597,10 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
&__name {
@include font-body-2-bold;
}
+
+ &__learn-more {
+ text-decoration: none;
+ }
}
&__buttons {
diff --git a/ts/RemoteConfig.ts b/ts/RemoteConfig.ts
index 595a602bb7b5..952199520715 100644
--- a/ts/RemoteConfig.ts
+++ b/ts/RemoteConfig.ts
@@ -2,12 +2,13 @@ import { get, throttle } from 'lodash';
import { WebAPIType } from './textsecure/WebAPI';
type ConfigKeyType =
- | 'desktop.messageRequests'
- | 'desktop.gv2'
| 'desktop.cds'
+ | 'desktop.clientExpiration'
+ | 'desktop.gv2'
+ | 'desktop.mandatoryProfileSharing'
+ | 'desktop.messageRequests'
| 'desktop.storage'
- | 'desktop.storageWrite'
- | 'desktop.clientExpiration';
+ | 'desktop.storageWrite';
type ConfigValueType = {
name: ConfigKeyType;
enabled: boolean;
diff --git a/ts/background.ts b/ts/background.ts
index 5021b0cf880e..d0e0834dca2a 100644
--- a/ts/background.ts
+++ b/ts/background.ts
@@ -652,12 +652,13 @@ type WhatIsThis = typeof window.WhatIsThis;
function initializeRedux() {
// Here we set up a full redux store with initial state for our LeftPane Root
const convoCollection = window.getConversations();
- const conversations = convoCollection.map(
- conversation => conversation.cachedProps
+ const conversations = convoCollection.map(conversation =>
+ conversation.format()
);
const ourNumber = window.textsecure.storage.user.getNumber();
const ourUuid = window.textsecure.storage.user.getUuid();
const ourConversationId = window.ConversationController.getOurConversationId();
+
const initialState = {
conversations: {
conversationLookup: window.Signal.Util.makeLookup(conversations, 'id'),
@@ -1580,7 +1581,7 @@ type WhatIsThis = typeof window.WhatIsThis;
'desktop.clientExpiration',
({ value }) => {
const remoteBuildExpirationTimestamp = window.Signal.Util.parseRemoteClientExpiration(
- value
+ value as string
);
if (remoteBuildExpirationTimestamp) {
window.storage.put(
diff --git a/ts/components/CompositionArea.tsx b/ts/components/CompositionArea.tsx
index f853cba7c842..d4a9a167136a 100644
--- a/ts/components/CompositionArea.tsx
+++ b/ts/components/CompositionArea.tsx
@@ -16,12 +16,15 @@ import {
MessageRequestActions,
Props as MessageRequestActionsProps,
} from './conversation/MessageRequestActions';
+import { MandatoryProfileSharingActions } from './conversation/MandatoryProfileSharingActions';
import { countStickers } from './stickers/lib';
import { LocalizerType } from '../types/Util';
import { EmojiPickDataType } from './emoji/EmojiPicker';
export type OwnProps = {
readonly i18n: LocalizerType;
+ readonly groupVersion?: 1 | 2;
+ readonly isMissingMandatoryProfileSharing?: boolean;
readonly messageRequestsEnabled?: boolean;
readonly acceptedMessageRequest?: boolean;
readonly compositionApi?: React.MutableRefObject<{
@@ -113,7 +116,9 @@ export const CompositionArea = ({
// Message Requests
acceptedMessageRequest,
conversationType,
+ groupVersion,
isBlocked,
+ isMissingMandatoryProfileSharing,
messageRequestsEnabled,
name,
onAccept,
@@ -326,7 +331,7 @@ export const CompositionArea = ({
};
}, [setLarge]);
- if ((!acceptedMessageRequest || isBlocked) && messageRequestsEnabled) {
+ if (messageRequestsEnabled && (!acceptedMessageRequest || isBlocked)) {
return (
+ );
+ }
+
return (
diff --git a/ts/components/conversation/MandatoryProfileSharingActions.stories.tsx b/ts/components/conversation/MandatoryProfileSharingActions.stories.tsx
new file mode 100644
index 000000000000..6464ac5a1fea
--- /dev/null
+++ b/ts/components/conversation/MandatoryProfileSharingActions.stories.tsx
@@ -0,0 +1,47 @@
+import * as React from 'react';
+import { storiesOf } from '@storybook/react';
+import { text } from '@storybook/addon-knobs';
+import { action } from '@storybook/addon-actions';
+
+import {
+ MandatoryProfileSharingActions,
+ Props as MandatoryProfileSharingActionsProps,
+} from './MandatoryProfileSharingActions';
+import { setup as setupI18n } from '../../../js/modules/i18n';
+import enMessages from '../../../_locales/en/messages.json';
+
+const i18n = setupI18n('en', enMessages);
+
+const getBaseProps = (
+ isGroup = false
+): MandatoryProfileSharingActionsProps => ({
+ i18n,
+ conversationType: isGroup ? 'group' : 'direct',
+ firstName: text('firstName', 'Cayce'),
+ title: isGroup
+ ? text('title', 'NYC Rock Climbers')
+ : text('title', 'Cayce Bollard'),
+ name: isGroup
+ ? text('name', 'NYC Rock Climbers')
+ : text('name', 'Cayce Bollard'),
+ onBlock: action('block'),
+ onBlockAndDelete: action('onBlockAndDelete'),
+ onDelete: action('delete'),
+ onAccept: action('accept'),
+});
+
+storiesOf('Components/Conversation/MandatoryProfileSharingActions', module)
+ .add('Direct', () => {
+ return (
+
+
+
+ );
+ })
+ .add('Group', () => {
+ return (
+
+
+
+ );
+ });
diff --git a/ts/components/conversation/MandatoryProfileSharingActions.tsx b/ts/components/conversation/MandatoryProfileSharingActions.tsx
new file mode 100644
index 000000000000..f5966c1982e5
--- /dev/null
+++ b/ts/components/conversation/MandatoryProfileSharingActions.tsx
@@ -0,0 +1,134 @@
+import * as React from 'react';
+import classNames from 'classnames';
+import { ContactName, PropsType as ContactNameProps } from './ContactName';
+import {
+ MessageRequestActionsConfirmation,
+ MessageRequestState,
+ Props as MessageRequestActionsConfirmationProps,
+} from './MessageRequestActionsConfirmation';
+import { Intl } from '../Intl';
+import { LocalizerType } from '../../types/Util';
+
+export type Props = {
+ i18n: LocalizerType;
+ firstName?: string;
+ onAccept(): unknown;
+} & Omit
&
+ Pick<
+ MessageRequestActionsConfirmationProps,
+ 'conversationType' | 'onBlock' | 'onBlockAndDelete' | 'onDelete'
+ >;
+
+export const MandatoryProfileSharingActions = ({
+ conversationType,
+ firstName,
+ i18n,
+ name,
+ onAccept,
+ onBlock,
+ onBlockAndDelete,
+ onDelete,
+ phoneNumber,
+ profileName,
+ title,
+}: Props): JSX.Element => {
+ const [mrState, setMrState] = React.useState(MessageRequestState.default);
+
+ return (
+ <>
+ {mrState !== MessageRequestState.default ? (
+ {
+ throw new Error(
+ 'Should not be able to unblock from MandatoryProfileSharingActions'
+ );
+ }}
+ onDelete={onDelete}
+ name={name}
+ profileName={profileName}
+ phoneNumber={phoneNumber}
+ title={title}
+ conversationType={conversationType}
+ state={mrState}
+ onChangeState={setMrState}
+ />
+ ) : null}
+
+
+
+
+
+ ),
+ learnMore: (
+
+ {i18n('MessageRequests--learn-more')}
+
+ ),
+ }}
+ />
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/ts/components/conversation/Message.stories.tsx b/ts/components/conversation/Message.stories.tsx
index 077e6e8b9bad..fe015136c68d 100644
--- a/ts/components/conversation/Message.stories.tsx
+++ b/ts/components/conversation/Message.stories.tsx
@@ -45,6 +45,7 @@ const createProps = (overrideProps: Partial = {}): Props => ({
authorTitle: text('authorTitle', overrideProps.authorTitle || ''),
bodyRanges: overrideProps.bodyRanges,
canReply: true,
+ canDownload: true,
canDeleteForEveryone: overrideProps.canDeleteForEveryone || false,
clearSelectedMessage: action('clearSelectedMessage'),
collapseMetadata: overrideProps.collapseMetadata,
diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx
index 4da81c99b5c7..0a4f39620dad 100644
--- a/ts/components/conversation/Message.tsx
+++ b/ts/components/conversation/Message.tsx
@@ -137,6 +137,7 @@ export type PropsData = {
deletedForEveryone?: boolean;
canReply: boolean;
+ canDownload: boolean;
canDeleteForEveryone: boolean;
bodyRanges?: BodyRangesType;
};
@@ -1159,6 +1160,7 @@ export class Message extends React.PureComponent {
): JSX.Element | null {
const {
attachments,
+ canDownload,
canReply,
direction,
disableMenu,
@@ -1294,7 +1296,7 @@ export class Message extends React.PureComponent {
)}
>
{canReply ? reactButton : null}
- {canReply ? downloadButton : null}
+ {canDownload ? downloadButton : null}
{canReply ? replyButton : null}
{menuButton}
@@ -1328,6 +1330,7 @@ export class Message extends React.PureComponent
{
public renderContextMenu(triggerId: string): JSX.Element {
const {
attachments,
+ canDownload,
canReply,
deleteMessage,
deleteMessageForEveryone,
@@ -1349,7 +1352,8 @@ export class Message extends React.PureComponent {
const menu = (
- {!isSticker &&
+ {canDownload &&
+ !isSticker &&
!multipleAttachments &&
!isTapToView &&
attachments &&
diff --git a/ts/components/conversation/MessageDetail.stories.tsx b/ts/components/conversation/MessageDetail.stories.tsx
index 4e98224cffb2..9cca2eb99f6e 100644
--- a/ts/components/conversation/MessageDetail.stories.tsx
+++ b/ts/components/conversation/MessageDetail.stories.tsx
@@ -17,6 +17,7 @@ const defaultMessage: MessageProps = {
authorTitle: 'Max',
canReply: true,
canDeleteForEveryone: true,
+ canDownload: true,
clearSelectedMessage: () => null,
conversationId: 'my-convo',
conversationType: 'direct',
diff --git a/ts/components/conversation/Quote.stories.tsx b/ts/components/conversation/Quote.stories.tsx
index fd8c919c22f4..950a13ac43e7 100644
--- a/ts/components/conversation/Quote.stories.tsx
+++ b/ts/components/conversation/Quote.stories.tsx
@@ -20,6 +20,7 @@ const defaultMessageProps: MessagesProps = {
authorTitle: 'Person X',
canReply: true,
canDeleteForEveryone: true,
+ canDownload: true,
clearSelectedMessage: () => null,
conversationId: 'conversationId',
conversationType: 'direct', // override
diff --git a/ts/model-types.d.ts b/ts/model-types.d.ts
index 5ab5a0afd278..71bbfeb5aeeb 100644
--- a/ts/model-types.d.ts
+++ b/ts/model-types.d.ts
@@ -84,7 +84,7 @@ export type MessageAttributesType = {
referencedMessageNotFound: boolean;
text: string;
} | null;
- reactions: Array<{ fromId: string; emoji: unknown; timestamp: unknown }>;
+ reactions: Array<{ fromId: string; emoji: string; timestamp: number }>;
read_by: Array;
requiredProtocolVersion: number;
sent: boolean;
@@ -141,7 +141,7 @@ export type ConversationAttributesType = {
accessKey: string | null;
addedBy?: string;
capabilities: { uuid: string };
- color?: ColorType;
+ color?: string;
discoveredUnregisteredAt: number;
draftAttachments: Array;
draftTimestamp: number | null;
diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts
index f31298ecd740..2febf59d6b67 100644
--- a/ts/models/conversations.ts
+++ b/ts/models/conversations.ts
@@ -239,9 +239,16 @@ export class ConversationModel extends window.Backbone.Model<
// Keep props ready
this.generateProps = () => {
+ // This is to prevent race conditions on startup; Conversation models are created
+ // but the full window.ConversationController.load() sequence isn't complete.
+ if (!window.ConversationController.isFetchComplete()) {
+ return;
+ }
+
this.cachedProps = this.getProps();
};
this.on('change', this.generateProps);
+
this.generateProps();
}
@@ -1027,19 +1034,13 @@ export class ConversationModel extends window.Backbone.Model<
});
}
- format(): ConversationType | null | undefined {
+ format(): ConversationType {
+ this.cachedProps = this.cachedProps || this.getProps();
+
return this.cachedProps;
}
- getProps(): ConversationType | null {
- // This is to prevent race conditions on startup; Conversation models are created
- // but the full window.ConversationController.load() sequence isn't complete. So, we
- // don't cache props on create, but we do later when load() calls generateProps()
- // for us.
- if (!window.ConversationController.isFetchComplete()) {
- return null;
- }
-
+ getProps(): ConversationType {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const color = this.getColor()!;
@@ -1064,6 +1065,13 @@ export class ConversationModel extends window.Backbone.Model<
'desktop.messageRequests'
);
+ let groupVersion: undefined | 1 | 2;
+ if (this.isGroupV1()) {
+ groupVersion = 1;
+ } else if (this.isGroupV2()) {
+ groupVersion = 2;
+ }
+
// TODO: DESKTOP-720
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const result = {
@@ -1078,12 +1086,14 @@ export class ConversationModel extends window.Backbone.Model<
draftPreview,
draftText,
firstName: this.get('profileName')!,
+ groupVersion,
inboxPosition,
isAccepted: this.getAccepted(),
isArchived: this.get('isArchived')!,
isBlocked: this.isBlocked(),
isMe: this.isMe(),
isPinned: this.get('isPinned'),
+ isMissingMandatoryProfileSharing: this.isMissingRequiredProfileSharing(),
isVerified: this.isVerified(),
lastMessage: {
status: this.get('lastMessageStatus')!,
@@ -1091,6 +1101,7 @@ export class ConversationModel extends window.Backbone.Model<
deletedForEveryone: this.get('lastMessageDeletedForEveryone')!,
},
lastUpdated: this.get('timestamp')!,
+
membersCount: this.isPrivate()
? undefined
: (this.get('membersV2')! || this.get('members')! || []).length,
@@ -1693,6 +1704,18 @@ export class ConversationModel extends window.Backbone.Model<
return this.get('messageRequestResponseType') || 0;
}
+ isMissingRequiredProfileSharing(): boolean {
+ const mandatoryProfileSharingEnabled = window.Signal.RemoteConfig.isEnabled(
+ 'desktop.mandatoryProfileSharing'
+ );
+
+ if (!mandatoryProfileSharingEnabled) {
+ return false;
+ }
+
+ return !this.get('profileSharing');
+ }
+
/**
* Determine if this conversation should be considered "accepted" in terms
* of message requests
@@ -3864,20 +3887,19 @@ export class ConversationModel extends window.Backbone.Model<
}
const { migrateColor } = Util;
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- return migrateColor(this.get('color')!);
+ return migrateColor(this.get('color'));
}
- getAvatarPath(): string | null {
+ getAvatarPath(): string | undefined {
const avatar = this.isMe()
? this.get('profileAvatar') || this.get('avatar')
: this.get('avatar') || this.get('profileAvatar');
- if (avatar && avatar.path) {
- return getAbsoluteAttachmentPath(avatar.path);
+ if (!avatar || !avatar.path) {
+ return undefined;
}
- return null;
+ return getAbsoluteAttachmentPath(avatar.path);
}
canChangeTimer(): boolean {
diff --git a/ts/models/messages.ts b/ts/models/messages.ts
index d829ac7f3200..95ef6cb2bf00 100644
--- a/ts/models/messages.ts
+++ b/ts/models/messages.ts
@@ -8,6 +8,7 @@ import {
LastMessageStatus,
ConversationType,
} from '../state/ducks/conversations';
+import { PropsData } from '../components/conversation/Message';
import { CallbackResultType } from '../textsecure/SendMessage';
import { BodyRangesType } from '../types/Util';
import { PropsDataType as GroupsV2Props } from '../components/conversation/GroupV2Change';
@@ -59,7 +60,9 @@ const { getTextWithMentions, GoogleChrome } = window.Signal.Util;
const { addStickerPackReference, getMessageBySender } = window.Signal.Data;
const { bytesFromString } = window.Signal.Crypto;
-const PLACEHOLDER_CONTACT = {
+const PLACEHOLDER_CONTACT: Pick = {
+ id: 'placeholder-contact',
+ type: 'direct',
title: window.i18n('unknownContact'),
};
@@ -694,26 +697,26 @@ export class MessageModel extends window.Backbone.Model {
.map(attachment => this.getPropsForAttachment(attachment));
}
- getPropsForMessage(): WhatIsThis {
+ // Note: interactionMode is mixed in via selectors/conversations._messageSelector
+ getPropsForMessage(): Omit {
const sourceId = this.getContactId();
const contact = this.findAndFormatContact(sourceId);
const contactModel = this.findContact(sourceId);
- const authorColor = contactModel ? contactModel.getColor() : null;
- const authorAvatarPath = contactModel ? contactModel.getAvatarPath() : null;
+ const authorColor = contactModel ? contactModel.getColor() : undefined;
+ const authorAvatarPath = contactModel
+ ? contactModel.getAvatarPath()
+ : undefined;
const expirationLength = this.get('expireTimer') * 1000;
const expireTimerStart = this.get('expirationStartTimestamp');
const expirationTimestamp =
expirationLength && expireTimerStart
? expireTimerStart + expirationLength
- : null;
+ : undefined;
const conversation = this.getConversation();
const isGroup = conversation && !conversation.isPrivate();
- const conversationAccepted = Boolean(
- conversation && conversation.getAccepted()
- );
const sticker = this.get('sticker');
const isTapToView = this.isTapToView();
@@ -739,7 +742,6 @@ export class MessageModel extends window.Backbone.Model {
textPending: this.get('bodyPending'),
id: this.id,
conversationId: this.get('conversationId'),
- conversationAccepted,
isSticker: Boolean(sticker),
direction: this.isIncoming() ? 'incoming' : 'outgoing',
timestamp: this.get('sent_at'),
@@ -747,6 +749,7 @@ export class MessageModel extends window.Backbone.Model {
contact: this.getPropsForEmbeddedContact(),
canReply: this.canReply(),
canDeleteForEveryone: this.canDeleteForEveryone(),
+ canDownload: this.canDownload(),
authorTitle: contact.title,
authorColor,
authorName: contact.name,
@@ -801,7 +804,8 @@ export class MessageModel extends window.Backbone.Model {
// Dependencies of prop-generation functions
findAndFormatContact(
identifier?: string
- ): Partial & Pick {
+ ): Partial &
+ Pick {
if (!identifier) {
return PLACEHOLDER_CONTACT;
}
@@ -824,6 +828,8 @@ export class MessageModel extends window.Backbone.Model {
});
return {
+ id: 'phone-only',
+ type: 'direct',
title: phoneNumber,
phoneNumber,
};
@@ -839,9 +845,9 @@ export class MessageModel extends window.Backbone.Model {
}
// eslint-disable-next-line class-methods-use-this
- createNonBreakingLastSeparator(text: string): string | null {
+ createNonBreakingLastSeparator(text: string): string | undefined {
if (!text) {
- return null;
+ return undefined;
}
const nbsp = '\xa0';
@@ -859,7 +865,7 @@ export class MessageModel extends window.Backbone.Model {
return this.get('type') === 'incoming';
}
- getMessagePropStatus(): LastMessageStatus | null {
+ getMessagePropStatus(): LastMessageStatus | undefined {
const sent = this.get('sent');
const sentTo = this.get('sent_to') || [];
@@ -870,7 +876,7 @@ export class MessageModel extends window.Backbone.Model {
return 'error';
}
if (!this.isOutgoing()) {
- return null;
+ return undefined;
}
const readBy = this.get('read_by') || [];
@@ -2010,34 +2016,50 @@ export class MessageModel extends window.Backbone.Model {
return true;
}
+ canDownload(): boolean {
+ const conversation = this.getConversation();
+ const isAccepted = Boolean(conversation && conversation.getAccepted());
+
+ if (this.isOutgoing()) {
+ return true;
+ }
+
+ return isAccepted;
+ }
+
canReply(): boolean {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const isAccepted = this.getConversation()!.getAccepted();
+ const conversation = this.getConversation();
const errors = this.get('errors');
const isOutgoing = this.get('type') === 'outgoing';
const numDelivered = this.get('delivered');
- // Case 1: We cannot reply if we have accepted the message request
- if (!isAccepted) {
+ // Case 1: If mandatory profile sharing is enabled, and we haven't shared yet, then
+ // we can't reply.
+ if (conversation?.isMissingRequiredProfileSharing()) {
return false;
}
- // Case 2: We cannot reply if this message is deleted for everyone
+ // Case 2: We cannot reply if we have accepted the message request
+ if (!conversation?.getAccepted()) {
+ return false;
+ }
+
+ // Case 3: We cannot reply if this message is deleted for everyone
if (this.get('deletedForEveryone')) {
return false;
}
- // Case 3: We can reply if this is outgoing and delievered to at least one recipient
+ // Case 4: We can reply if this is outgoing and delievered to at least one recipient
if (isOutgoing && numDelivered > 0) {
return true;
}
- // Case 4: We can reply if there are no errors
+ // Case 5: We can reply if there are no errors
if (!errors || (errors && errors.length === 0)) {
return true;
}
- // Case 5: default
+ // Case 6: default
return false;
}
diff --git a/ts/services/calling.ts b/ts/services/calling.ts
index 2f4b6faeef39..ba8a2f299943 100644
--- a/ts/services/calling.ts
+++ b/ts/services/calling.ts
@@ -117,14 +117,7 @@ export class CallingClass {
return;
}
- const conversationProps = conversation.cachedProps;
-
- if (!conversationProps) {
- window.log.error(
- 'CallingClass.startCallingLobby(): No conversation props?'
- );
- return;
- }
+ const conversationProps = conversation.format();
window.log.info('CallingClass.startCallingLobby(): Starting lobby');
this.uxActions.showCallLobby({
@@ -829,10 +822,7 @@ export class CallingClass {
conversation: ConversationModel,
call: Call
): CallDetailsType {
- const conversationProps = conversation.cachedProps;
- if (!conversationProps) {
- throw new Error('getAcceptedCallDetails: No conversation props?');
- }
+ const conversationProps = conversation.format();
return {
...conversationProps,
diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts
index 169dd858469f..08fb2870a2d6 100644
--- a/ts/state/ducks/conversations.ts
+++ b/ts/state/ducks/conversations.ts
@@ -75,6 +75,8 @@ export type ConversationType = {
draftText?: string | null;
draftPreview?: string;
+ groupVersion?: 1 | 2;
+ isMissingMandatoryProfileSharing?: boolean;
messageRequestsEnabled?: boolean;
acceptedMessageRequest?: boolean;
};
diff --git a/ts/util/getStringForProfileChange.ts b/ts/util/getStringForProfileChange.ts
index 62f2b0338bce..ad864c4bb3d3 100644
--- a/ts/util/getStringForProfileChange.ts
+++ b/ts/util/getStringForProfileChange.ts
@@ -1,15 +1,18 @@
import { LocalizerType } from '../types/Util';
-import { ConversationType } from '../state/ducks/conversations';
export type ProfileNameChangeType = {
type: 'name';
oldName: string;
newName: string;
};
+type ContactType = {
+ title: string;
+ name?: string;
+};
export function getStringForProfileChange(
change: ProfileNameChangeType,
- changedContact: ConversationType,
+ changedContact: ContactType,
i18n: LocalizerType
): string {
if (change.type === 'name') {
diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json
index 473ee30880c6..4fb52ec0c92e 100644
--- a/ts/util/lint/exceptions.json
+++ b/ts/util/lint/exceptions.json
@@ -13120,7 +13120,7 @@
"rule": "DOM-innerHTML",
"path": "ts/components/CompositionArea.js",
"line": " el.innerHTML = '';",
- "lineNumber": 24,
+ "lineNumber": 25,
"reasonCategory": "usageTrusted",
"updated": "2020-05-20T20:10:43.540Z",
"reasonDetail": "Our code, no user input, only clearing out the dom"
@@ -13129,7 +13129,7 @@
"rule": "DOM-innerHTML",
"path": "ts/components/CompositionArea.tsx",
"line": " el.innerHTML = '';",
- "lineNumber": 78,
+ "lineNumber": 81,
"reasonCategory": "usageTrusted",
"updated": "2020-06-03T19:23:21.195Z",
"reasonDetail": "Our code, no user input, only clearing out the dom"
@@ -13305,7 +13305,7 @@
"rule": "React-createRef",
"path": "ts/components/conversation/Message.tsx",
"line": " public audioRef: React.RefObject = React.createRef();",
- "lineNumber": 217,
+ "lineNumber": 218,
"reasonCategory": "usageTrusted",
"updated": "2020-09-08T20:19:01.913Z"
},
@@ -13313,7 +13313,7 @@
"rule": "React-createRef",
"path": "ts/components/conversation/Message.tsx",
"line": " public focusRef: React.RefObject = React.createRef();",
- "lineNumber": 219,
+ "lineNumber": 220,
"reasonCategory": "usageTrusted",
"updated": "2020-09-08T20:19:01.913Z"
},
@@ -13321,7 +13321,7 @@
"rule": "React-createRef",
"path": "ts/components/conversation/Message.tsx",
"line": " > = React.createRef();",
- "lineNumber": 223,
+ "lineNumber": 224,
"reasonCategory": "usageTrusted",
"updated": "2020-08-28T19:36:40.817Z"
},
@@ -13548,4 +13548,4 @@
"reasonCategory": "falseMatch",
"updated": "2020-09-08T23:07:22.682Z"
}
-]
\ No newline at end of file
+]
diff --git a/ts/util/migrateColor.ts b/ts/util/migrateColor.ts
index 75ef94729b85..83af4c1cea28 100644
--- a/ts/util/migrateColor.ts
+++ b/ts/util/migrateColor.ts
@@ -1,7 +1,5 @@
import { ColorType } from '../types/Colors';
-// import { missingCaseError } from './missingCaseError';
-
type OldColorType =
| 'amber'
| 'blue'
@@ -22,9 +20,11 @@ type OldColorType =
| 'red'
| 'teal'
| 'yellow'
- | 'ultramarine';
+ | 'ultramarine'
+ | string
+ | undefined;
-export function migrateColor(color: OldColorType): ColorType {
+export function migrateColor(color?: OldColorType): ColorType {
switch (color) {
// These colors no longer exist
case 'orange':
@@ -62,10 +62,6 @@ export function migrateColor(color: OldColorType): ColorType {
case 'ultramarine':
return color;
- // Can uncomment this to ensure that we've covered all potential cases
- // default:
- // throw missingCaseError(color);
-
default:
return 'grey';
}
diff --git a/ts/views/conversation_view.ts b/ts/views/conversation_view.ts
index c55496c9786e..77ac8cdf3f69 100644
--- a/ts/views/conversation_view.ts
+++ b/ts/views/conversation_view.ts
@@ -436,11 +436,12 @@ Whisper.ConversationView = Whisper.View.extend({
: null;
return {
- ...this.model.cachedProps,
+ ...this.model.format(),
leftGroup: this.model.get('left'),
disableTimerChanges:
+ this.model.isMissingRequiredProfileSharing() ||
this.model.get('left') ||
!this.model.getAccepted() ||
!this.model.canChangeTimer(),
diff --git a/ts/window.d.ts b/ts/window.d.ts
index 7da4eb3e3e68..d18c75eb1914 100644
--- a/ts/window.d.ts
+++ b/ts/window.d.ts
@@ -3,6 +3,7 @@
import * as Backbone from 'backbone';
import * as Underscore from 'underscore';
import { Ref } from 'react';
+import * as Util from './util';
import {
ConversationModelCollectionType,
MessageModelCollectionType,
@@ -356,41 +357,7 @@ declare global {
};
VisualAttachment: any;
};
- Util: {
- isFileDangerous: any;
- GoogleChrome: {
- isImageTypeSupported: (contentType: string) => unknown;
- isVideoTypeSupported: (contentType: string) => unknown;
- };
- downloadAttachment: (attachment: WhatIsThis) => WhatIsThis;
- getStringForProfileChange: (
- change: unknown,
- changedContact: unknown,
- i18n: unknown
- ) => string;
- getTextWithMentions: (
- bodyRanges: BodyRangesType,
- text: string
- ) => string;
- deleteForEveryone: (
- message: unknown,
- del: unknown,
- bool: boolean
- ) => void;
- zkgroup: typeof zkgroup;
- combineNames: typeof combineNames;
- migrateColor: (color: string) => ColorType;
- createBatcher: (options: WhatIsThis) => WhatIsThis;
- Registration: {
- everDone: () => boolean;
- markDone: () => void;
- markEverDone: () => void;
- remove: () => void;
- };
- hasExpired: () => boolean;
- makeLookup: (conversations: WhatIsThis, key: string) => void;
- parseRemoteClientExpiration: (value: WhatIsThis) => WhatIsThis;
- };
+ Util: typeof Util;
LinkPreviews: {
isMediaLinkInWhitelist: any;
getTitleMetaTag: any;