Modern profile sharing in 1:1 and GroupV1 groups

This commit is contained in:
Scott Nonnenberg 2020-10-16 11:31:57 -07:00
parent 60f2422e2a
commit 04b7a29229
22 changed files with 371 additions and 115 deletions

View file

@ -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<ConversationType, 'title' | 'type' | 'id'> = {
id: 'placeholder-contact',
type: 'direct',
title: window.i18n('unknownContact'),
};
@ -694,26 +697,26 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
.map(attachment => this.getPropsForAttachment(attachment));
}
getPropsForMessage(): WhatIsThis {
// Note: interactionMode is mixed in via selectors/conversations._messageSelector
getPropsForMessage(): Omit<PropsData, 'interactionMode'> {
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<MessageAttributesType> {
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<MessageAttributesType> {
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<MessageAttributesType> {
// Dependencies of prop-generation functions
findAndFormatContact(
identifier?: string
): Partial<ConversationType> & Pick<ConversationType, 'title'> {
): Partial<ConversationType> &
Pick<ConversationType, 'title' | 'id' | 'type'> {
if (!identifier) {
return PLACEHOLDER_CONTACT;
}
@ -824,6 +828,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
});
return {
id: 'phone-only',
type: 'direct',
title: phoneNumber,
phoneNumber,
};
@ -839,9 +845,9 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
}
// 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<MessageAttributesType> {
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<MessageAttributesType> {
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<MessageAttributesType> {
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;
}