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,
+ },
+ ]);
+ });
});