Display proper text when quoting view once message

This commit is contained in:
Fedor Indutny 2021-06-02 09:42:19 -07:00 committed by GitHub
parent 81227066ce
commit b009967a83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 102 additions and 12 deletions

View file

@ -2226,15 +2226,15 @@
}, },
"message--getDescription--disappearing-media": { "message--getDescription--disappearing-media": {
"message": "View-once Media", "message": "View-once Media",
"description": "Shown in notifications and in the left pane after view-once message is deleted." "description": "Shown in notifications and in the left pane after view-once message is deleted. Also shown when quoting a view once media."
}, },
"message--getDescription--disappearing-photo": { "message--getDescription--disappearing-photo": {
"message": "View-once Photo", "message": "View-once Photo",
"description": "Shown in notifications and in the left pane when a message is a view once photo." "description": "Shown in notifications and in the left pane when a message is a view once photo. Also shown when quoting a view once photo."
}, },
"message--getDescription--disappearing-video": { "message--getDescription--disappearing-video": {
"message": "View-once Video", "message": "View-once Video",
"description": "Shown in notifications and in the left pane when a message is a view once video." "description": "Shown in notifications and in the left pane when a message is a view once video. Also shown when quoting a view once video."
}, },
"message--deletedForEveryone": { "message--deletedForEveryone": {
"message": "This message was deleted.", "message": "This message was deleted.",

View file

@ -1789,6 +1789,9 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
.module-quote__icon-container__icon--movie { .module-quote__icon-container__icon--movie {
@include color-svg('../images/movie.svg', $color-ultramarine); @include color-svg('../images/movie.svg', $color-ultramarine);
} }
.module-quote__icon-container__icon--view-once {
@include color-svg('../images/icons/v2/view-once-24.svg', $color-ultramarine);
}
.module-quote__generic-file { .module-quote__generic-file {
display: flex; display: flex;

View file

@ -149,6 +149,7 @@ export type PropsData = {
authorName?: string; authorName?: string;
bodyRanges?: BodyRangesType; bodyRanges?: BodyRangesType;
referencedMessageNotFound: boolean; referencedMessageNotFound: boolean;
isViewOnce: boolean;
}; };
previews: Array<LinkPreviewType>; previews: Array<LinkPreviewType>;
isExpired?: boolean; isExpired?: boolean;
@ -1062,7 +1063,7 @@ export class Message extends React.Component<Props, State> {
const withContentAbove = const withContentAbove =
conversationType === 'group' && direction === 'incoming'; conversationType === 'group' && direction === 'incoming';
const { referencedMessageNotFound } = quote; const { isViewOnce, referencedMessageNotFound } = quote;
const clickHandler = disableScroll const clickHandler = disableScroll
? undefined ? undefined
@ -1087,6 +1088,7 @@ export class Message extends React.Component<Props, State> {
bodyRanges={quote.bodyRanges} bodyRanges={quote.bodyRanges}
conversationColor={conversationColor} conversationColor={conversationColor}
customColor={customColor} customColor={customColor}
isViewOnce={isViewOnce}
referencedMessageNotFound={referencedMessageNotFound} referencedMessageNotFound={referencedMessageNotFound}
isFromMe={quote.isFromMe} isFromMe={quote.isFromMe}
withContentAbove={withContentAbove} withContentAbove={withContentAbove}

View file

@ -81,6 +81,7 @@ const renderInMessage = ({
conversationColor, conversationColor,
isFromMe, isFromMe,
rawAttachment, rawAttachment,
isViewOnce,
referencedMessageNotFound, referencedMessageNotFound,
text: quoteText, text: quoteText,
}: Props) => { }: Props) => {
@ -96,6 +97,7 @@ const renderInMessage = ({
conversationColor, conversationColor,
isFromMe, isFromMe,
rawAttachment, rawAttachment,
isViewOnce,
referencedMessageNotFound, referencedMessageNotFound,
sentAt: Date.now() - 30 * 1000, sentAt: Date.now() - 30 * 1000,
text: quoteText, text: quoteText,
@ -133,6 +135,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
'referencedMessageNotFound', 'referencedMessageNotFound',
overrideProps.referencedMessageNotFound || false overrideProps.referencedMessageNotFound || false
), ),
isViewOnce: boolean('isViewOnce', overrideProps.isViewOnce || false),
text: text( text: text(
'text', 'text',
isString(overrideProps.text) isString(overrideProps.text)
@ -247,6 +250,20 @@ story.add('Image Attachment w/o Thumbnail', () => {
return <Quote {...props} />; return <Quote {...props} />;
}); });
story.add('Image Tap-to-View', () => {
const props = createProps({
text: '',
isViewOnce: true,
rawAttachment: {
contentType: IMAGE_PNG,
fileName: 'sax.png',
isVoiceMessage: false,
},
});
return <Quote {...props} />;
});
story.add('Video Only', () => { story.add('Video Only', () => {
const props = createProps({ const props = createProps({
rawAttachment: { rawAttachment: {
@ -293,6 +310,20 @@ story.add('Video Attachment w/o Thumbnail', () => {
return <Quote {...props} />; return <Quote {...props} />;
}); });
story.add('Video Tap-to-View', () => {
const props = createProps({
text: '',
isViewOnce: true,
rawAttachment: {
contentType: VIDEO_MP4,
fileName: 'great-video.mp4',
isVoiceMessage: false,
},
});
return <Quote {...props} />;
});
story.add('Audio Only', () => { story.add('Audio Only', () => {
const props = createProps({ const props = createProps({
rawAttachment: { rawAttachment: {
@ -359,6 +390,20 @@ story.add('Other File Only', () => {
return <Quote {...props} />; return <Quote {...props} />;
}); });
story.add('Media Tap-to-View', () => {
const props = createProps({
text: '',
isViewOnce: true,
rawAttachment: {
contentType: AUDIO_MP3,
fileName: 'great-video.mp3',
isVoiceMessage: false,
},
});
return <Quote {...props} />;
});
story.add('Other File Attachment', () => { story.add('Other File Attachment', () => {
const props = createProps({ const props = createProps({
rawAttachment: { rawAttachment: {

View file

@ -31,6 +31,7 @@ export type Props = {
onClose?: () => void; onClose?: () => void;
text: string; text: string;
rawAttachment?: QuotedAttachmentType; rawAttachment?: QuotedAttachmentType;
isViewOnce: boolean;
referencedMessageNotFound: boolean; referencedMessageNotFound: boolean;
}; };
@ -83,19 +84,32 @@ function getObjectUrl(thumbnail: Attachment | undefined): string | undefined {
function getTypeLabel({ function getTypeLabel({
i18n, i18n,
isViewOnce = false,
contentType, contentType,
isVoiceMessage, isVoiceMessage,
}: { }: {
i18n: LocalizerType; i18n: LocalizerType;
isViewOnce?: boolean;
contentType: MIME.MIMEType; contentType: MIME.MIMEType;
isVoiceMessage: boolean; isVoiceMessage: boolean;
}): string | undefined { }): string | undefined {
if (GoogleChrome.isVideoTypeSupported(contentType)) { if (GoogleChrome.isVideoTypeSupported(contentType)) {
if (isViewOnce) {
return i18n('message--getDescription--disappearing-video');
}
return i18n('video'); return i18n('video');
} }
if (GoogleChrome.isImageTypeSupported(contentType)) { if (GoogleChrome.isImageTypeSupported(contentType)) {
if (isViewOnce) {
return i18n('message--getDescription--disappearing-photo');
}
return i18n('photo'); return i18n('photo');
} }
if (isViewOnce) {
return i18n('message--getDescription--disappearing-media');
}
if (MIME.isAudio(contentType) && isVoiceMessage) { if (MIME.isAudio(contentType) && isVoiceMessage) {
return i18n('voiceMessage'); return i18n('voiceMessage');
} }
@ -217,7 +231,7 @@ export class Quote extends React.Component<Props, State> {
} }
public renderIconContainer(): JSX.Element | null { public renderIconContainer(): JSX.Element | null {
const { rawAttachment } = this.props; const { rawAttachment, isViewOnce } = this.props;
const { imageBroken } = this.state; const { imageBroken } = this.state;
const attachment = getAttachment(rawAttachment); const attachment = getAttachment(rawAttachment);
@ -228,6 +242,10 @@ export class Quote extends React.Component<Props, State> {
const { contentType, thumbnail } = attachment; const { contentType, thumbnail } = attachment;
const objectUrl = getObjectUrl(thumbnail); const objectUrl = getObjectUrl(thumbnail);
if (isViewOnce) {
return this.renderIcon('view-once');
}
if (GoogleChrome.isVideoTypeSupported(contentType)) { if (GoogleChrome.isVideoTypeSupported(contentType)) {
return objectUrl && !imageBroken return objectUrl && !imageBroken
? this.renderImage(objectUrl, 'play') ? this.renderImage(objectUrl, 'play')
@ -246,7 +264,14 @@ export class Quote extends React.Component<Props, State> {
} }
public renderText(): JSX.Element | null { public renderText(): JSX.Element | null {
const { bodyRanges, i18n, text, rawAttachment, isIncoming } = this.props; const {
bodyRanges,
i18n,
text,
rawAttachment,
isIncoming,
isViewOnce,
} = this.props;
if (text) { if (text) {
const quoteText = bodyRanges const quoteText = bodyRanges
@ -274,7 +299,12 @@ export class Quote extends React.Component<Props, State> {
const { contentType, isVoiceMessage } = attachment; const { contentType, isVoiceMessage } = attachment;
const typeLabel = getTypeLabel({ i18n, contentType, isVoiceMessage }); const typeLabel = getTypeLabel({
i18n,
isViewOnce,
contentType,
isVoiceMessage,
});
if (typeLabel) { if (typeLabel) {
return ( return (
<div <div

1
ts/model-types.d.ts vendored
View file

@ -64,6 +64,7 @@ export type QuotedMessageType = {
bodyRanges: BodyRangesType; bodyRanges: BodyRangesType;
id: string; id: string;
referencedMessageNotFound: boolean; referencedMessageNotFound: boolean;
isViewOnce: boolean;
text: string; text: string;
}; };

View file

@ -41,7 +41,7 @@ import {
trimForDisplay, trimForDisplay,
verifyAccessKey, verifyAccessKey,
} from '../Crypto'; } from '../Crypto';
import { GroupChangeClass } from '../textsecure.d'; import { GroupChangeClass, DataMessageClass } from '../textsecure.d';
import { BodyRangesType } from '../types/Util'; import { BodyRangesType } from '../types/Util';
import { getTextWithMentions } from '../util'; import { getTextWithMentions } from '../util';
import { migrateColor } from '../util/migrateColor'; import { migrateColor } from '../util/migrateColor';
@ -3130,7 +3130,7 @@ export class ConversationModel extends window.Backbone
async makeQuote( async makeQuote(
quotedMessage: typeof window.Whisper.MessageType quotedMessage: typeof window.Whisper.MessageType
): Promise<WhatIsThis> { ): Promise<DataMessageClass.Quote> {
const { getName } = Contact; const { getName } = Contact;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const contact = quotedMessage.getContact()!; const contact = quotedMessage.getContact()!;
@ -3150,6 +3150,7 @@ export class ConversationModel extends window.Backbone
bodyRanges: quotedMessage.get('bodyRanges'), bodyRanges: quotedMessage.get('bodyRanges'),
id: quotedMessage.get('sent_at'), id: quotedMessage.get('sent_at'),
text: body || embeddedContactName, text: body || embeddedContactName,
isViewOnce: quotedMessage.isTapToView(),
attachments: quotedMessage.isTapToView() attachments: quotedMessage.isTapToView()
? [{ contentType: 'image/jpeg', fileName: null }] ? [{ contentType: 'image/jpeg', fileName: null }]
: await this.getQuoteAttachment(attachments, preview, sticker), : await this.getQuoteAttachment(attachments, preview, sticker),

View file

@ -1243,6 +1243,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
authorUuid, authorUuid,
bodyRanges, bodyRanges,
id: sentAt, id: sentAt,
isViewOnce,
referencedMessageNotFound, referencedMessageNotFound,
text, text,
} = quote; } = quote;
@ -1342,6 +1343,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
rawAttachment: firstAttachment rawAttachment: firstAttachment
? this.processQuoteAttachment(firstAttachment) ? this.processQuoteAttachment(firstAttachment)
: undefined, : undefined,
isViewOnce,
referencedMessageNotFound: !foundReference, referencedMessageNotFound: !foundReference,
sentAt: Number(sentAt), sentAt: Number(sentAt),
text: this.createNonBreakingLastSeparator(text), text: this.createNonBreakingLastSeparator(text),
@ -3363,10 +3365,15 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
contentType: 'image/jpeg', contentType: 'image/jpeg',
}, },
]; ];
// eslint-disable-next-line no-param-reassign
quote.isViewOnce = true;
return; return;
} }
// eslint-disable-next-line no-param-reassign
quote.isViewOnce = false;
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
quote.text = originalMessage.get('body'); quote.text = originalMessage.get('body');
if (firstAttachment) { if (firstAttachment) {

7
ts/textsecure.d.ts vendored
View file

@ -653,14 +653,15 @@ export declare namespace DataMessageClass {
// Note: deep nesting // Note: deep nesting
class Quote { class Quote {
id: ProtoBigNumberType | null; id?: ProtoBigNumberType | null;
authorUuid: string | null; authorUuid?: string | null;
text: string | null; text?: string | null;
attachments?: Array<DataMessageClass.Quote.QuotedAttachment>; attachments?: Array<DataMessageClass.Quote.QuotedAttachment>;
bodyRanges?: Array<DataMessageClass.BodyRange>; bodyRanges?: Array<DataMessageClass.BodyRange>;
// Added later during processing // Added later during processing
referencedMessageNotFound?: boolean; referencedMessageNotFound?: boolean;
isViewOnce?: boolean;
} }
class BodyRange { class BodyRange {