diff --git a/images/icons/v2/lock-unlock-outline-12.svg b/images/icons/v2/lock-unlock-outline-12.svg new file mode 100644 index 00000000000..3c0a418926f --- /dev/null +++ b/images/icons/v2/lock-unlock-outline-12.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index 2dc5a06e7ac..2051fac27ba 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -1205,6 +1205,29 @@ $message-padding-horizontal: 12px; } } +.module-message__metadata__sms { + width: 12px; + height: 12px; + display: inline-block; + margin-inline-start: 6px; + // High margin to leave space for the increase when we go to two checks + margin-bottom: 2px; + + @include color-svg( + '../images/icons/v2/lock-unlock-outline-12.svg', + $color-white + ); +} + +.module-message__metadata__sms--incoming { + @include light-theme { + background-color: $color-gray-60; + } + @include dark-theme { + background-color: $color-gray-25; + } +} + .module-message__container--outgoing .module-message__metadata__edited { color: $color-white-alpha-80; } diff --git a/ts/components/StoryViewsNRepliesModal.tsx b/ts/components/StoryViewsNRepliesModal.tsx index 4f92ca12845..a1cfeea2631 100644 --- a/ts/components/StoryViewsNRepliesModal.tsx +++ b/ts/components/StoryViewsNRepliesModal.tsx @@ -51,6 +51,7 @@ const MESSAGE_DEFAULT_PROPS = { isMessageRequestAccepted: true, isSelected: false, isSelectMode: false, + isSMS: false, onToggleSelect: shouldNeverBeCalled, onReplyToMessage: shouldNeverBeCalled, kickOffAttachmentDownload: shouldNeverBeCalled, diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index 6373b460b53..6265d7a7dc2 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -99,6 +99,7 @@ import { UserText } from '../UserText'; const GUESS_METADATA_WIDTH_TIMESTAMP_SIZE = 16; const GUESS_METADATA_WIDTH_EXPIRE_TIMER_SIZE = 18; +const GUESS_METADATA_WIDTH_SMS_SIZE = 18; const GUESS_METADATA_WIDTH_EDITED_SIZE = 40; const GUESS_METADATA_WIDTH_OUTGOING_SIZE: Record = { delivered: 24, @@ -218,6 +219,7 @@ export type PropsData = { isTargetedCounter?: number; isSelected: boolean; isSelectMode: boolean; + isSMS: boolean; isSpoilerExpanded?: Record; direction: DirectionType; timestamp: number; @@ -628,7 +630,8 @@ export class Message extends React.PureComponent { * because it can reduce layout jumpiness. */ private guessMetadataWidth(): number { - const { direction, expirationLength, status, isEditedMessage } = this.props; + const { direction, expirationLength, isSMS, status, isEditedMessage } = + this.props; let result = GUESS_METADATA_WIDTH_TIMESTAMP_SIZE; @@ -641,6 +644,10 @@ export class Message extends React.PureComponent { result += GUESS_METADATA_WIDTH_EXPIRE_TIMER_SIZE; } + if (isSMS) { + result += GUESS_METADATA_WIDTH_SMS_SIZE; + } + if (direction === 'outgoing' && status) { result += GUESS_METADATA_WIDTH_OUTGOING_SIZE[status]; } @@ -811,6 +818,7 @@ export class Message extends React.PureComponent { i18n, id, isEditedMessage, + isSMS, isSticker, isTapToViewExpired, retryMessageSend, @@ -834,6 +842,7 @@ export class Message extends React.PureComponent { i18n={i18n} id={id} isEditedMessage={isEditedMessage} + isSMS={isSMS} isInline={isInline} isOutlineOnlyBubble={ deletedForEveryone || (attachmentDroppedDueToSize && !text) diff --git a/ts/components/conversation/MessageDetail.stories.tsx b/ts/components/conversation/MessageDetail.stories.tsx index 6d9f01bff55..d0992d57c35 100644 --- a/ts/components/conversation/MessageDetail.stories.tsx +++ b/ts/components/conversation/MessageDetail.stories.tsx @@ -36,6 +36,7 @@ const defaultMessage: MessageDataPropsType = { isMessageRequestAccepted: true, isSelected: false, isSelectMode: false, + isSMS: false, isSpoilerExpanded: {}, previews: [], readStatus: ReadStatus.Read, diff --git a/ts/components/conversation/MessageMetadata.tsx b/ts/components/conversation/MessageMetadata.tsx index 26c4c7a6aa3..5aa438e3513 100644 --- a/ts/components/conversation/MessageMetadata.tsx +++ b/ts/components/conversation/MessageMetadata.tsx @@ -27,6 +27,7 @@ type PropsType = { i18n: LocalizerType; id: string; isEditedMessage?: boolean; + isSMS?: boolean; isInline?: boolean; isOutlineOnlyBubble?: boolean; isShowingImage: boolean; @@ -56,6 +57,7 @@ export const MessageMetadata = forwardRef>( i18n, id, isEditedMessage, + isSMS, isOutlineOnlyBubble, isInline, isShowingImage, @@ -211,6 +213,11 @@ export const MessageMetadata = forwardRef>( )} {timestampNode} + {isSMS ? ( +
+ ) : null} {expirationLength ? ( = {}): Props => ({ isSelectMode: isBoolean(overrideProps.isSelectMode) ? overrideProps.isSelectMode : false, + isSMS: isBoolean(overrideProps.isSMS) ? overrideProps.isSMS : false, isSpoilerExpanded: overrideProps.isSpoilerExpanded || {}, isTapToView: overrideProps.isTapToView, isTapToViewError: overrideProps.isTapToViewError, @@ -2060,6 +2061,12 @@ PaymentNotification.args = { }, }; +export const SMS = Template.bind({}); +SMS.args = { + isSMS: true, + text: 'hello', +}; + function MultiSelectMessage() { const [selected, setSelected] = React.useState(false); diff --git a/ts/model-types.d.ts b/ts/model-types.d.ts index 320343fc244..723ebf5df36 100644 --- a/ts/model-types.d.ts +++ b/ts/model-types.d.ts @@ -198,6 +198,7 @@ export type MessageAttributesType = { quote?: QuotedMessageType; reactions?: ReadonlyArray; requiredProtocolVersion?: number; + sms?: boolean; sourceDevice?: number; storyDistributionListId?: StoryDistributionIdString; storyId?: string; diff --git a/ts/services/backups/export.ts b/ts/services/backups/export.ts index cffadd1e2d0..2a0ab6c6385 100644 --- a/ts/services/backups/export.ts +++ b/ts/services/backups/export.ts @@ -747,7 +747,7 @@ export class BackupExportStream extends Readable { expireStartDate, expiresInMs, revisions: [], - sms: false, + sms: message.sms === true, }; if (!isNormalBubble(message)) { diff --git a/ts/services/backups/import.ts b/ts/services/backups/import.ts index 49ca1e9c989..72b65533539 100644 --- a/ts/services/backups/import.ts +++ b/ts/services/backups/import.ts @@ -881,6 +881,7 @@ export class BackupImportStream extends Writable { item.expiresInMs && !item.expiresInMs.isZero() ? DurationInSeconds.fromMillis(item.expiresInMs.toNumber()) : undefined, + sms: item.sms === true ? true : undefined, ...directionDetails, }; const additionalMessages: Array = []; diff --git a/ts/state/selectors/message.ts b/ts/state/selectors/message.ts index 27f1afb6e23..2ca8621c956 100644 --- a/ts/state/selectors/message.ts +++ b/ts/state/selectors/message.ts @@ -761,6 +761,7 @@ export const getPropsForMessage = ( isMessageRequestAccepted: conversation?.acceptedMessageRequest ?? true, isSelected, isSelectMode, + isSMS: message.sms === true, isSpoilerExpanded: message.isSpoilerExpanded, isSticker: Boolean(sticker), isTargeted, diff --git a/ts/test-electron/backup/bubble_test.ts b/ts/test-electron/backup/bubble_test.ts index 9197d8d1379..79edbacc7eb 100644 --- a/ts/test-electron/backup/bubble_test.ts +++ b/ts/test-electron/backup/bubble_test.ts @@ -363,4 +363,24 @@ describe('backup/bubble messages', () => { }, ]); }); + + it('roundtrips sms messages', async () => { + await symmetricRoundtripHarness([ + { + conversationId: contactA.id, + id: generateGuid(), + type: 'incoming', + received_at: 3, + received_at_ms: 3, + sent_at: 3, + timestamp: 3, + sourceServiceId: CONTACT_A, + body: 'd', + readStatus: ReadStatus.Unread, + seenStatus: SeenStatus.Unseen, + unidentifiedDeliveryReceived: true, + sms: true, + }, + ]); + }); });