Modern profile sharing in 1:1 and GroupV1 groups
This commit is contained in:
parent
60f2422e2a
commit
04b7a29229
22 changed files with 371 additions and 115 deletions
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue