diff --git a/Signal/Images.xcassets/message_send_failure.imageset/Contents.json b/Signal/Images.xcassets/message_send_failed.imageset/Contents.json similarity index 64% rename from Signal/Images.xcassets/message_send_failure.imageset/Contents.json rename to Signal/Images.xcassets/message_send_failed.imageset/Contents.json index c1ca74cf9..6bd09fe32 100644 --- a/Signal/Images.xcassets/message_send_failure.imageset/Contents.json +++ b/Signal/Images.xcassets/message_send_failed.imageset/Contents.json @@ -2,17 +2,17 @@ "images" : [ { "idiom" : "universal", - "filename" : "message_send_failure@1x.png", + "filename" : "error-20@1x.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "message_send_failure@2x.png", + "filename" : "error-20@2x.png", "scale" : "2x" }, { "idiom" : "universal", - "filename" : "message_send_failure@3x.png", + "filename" : "error-20@3x.png", "scale" : "3x" } ], diff --git a/Signal/Images.xcassets/message_send_failed.imageset/error-20@1x.png b/Signal/Images.xcassets/message_send_failed.imageset/error-20@1x.png new file mode 100644 index 000000000..d385cefad Binary files /dev/null and b/Signal/Images.xcassets/message_send_failed.imageset/error-20@1x.png differ diff --git a/Signal/Images.xcassets/message_send_failed.imageset/error-20@2x.png b/Signal/Images.xcassets/message_send_failed.imageset/error-20@2x.png new file mode 100644 index 000000000..391b7f3bc Binary files /dev/null and b/Signal/Images.xcassets/message_send_failed.imageset/error-20@2x.png differ diff --git a/Signal/Images.xcassets/message_send_failed.imageset/error-20@3x.png b/Signal/Images.xcassets/message_send_failed.imageset/error-20@3x.png new file mode 100644 index 000000000..35a740157 Binary files /dev/null and b/Signal/Images.xcassets/message_send_failed.imageset/error-20@3x.png differ diff --git a/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@1x.png b/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@1x.png deleted file mode 100644 index faa7e18f0..000000000 Binary files a/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@1x.png and /dev/null differ diff --git a/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@2x.png b/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@2x.png deleted file mode 100644 index 1e540f14b..000000000 Binary files a/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@2x.png and /dev/null differ diff --git a/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@3x.png b/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@3x.png deleted file mode 100644 index 141311227..000000000 Binary files a/Signal/Images.xcassets/message_send_failure.imageset/message_send_failure@3x.png and /dev/null differ diff --git a/Signal/src/ViewControllers/ContactViewController.swift b/Signal/src/ViewControllers/ContactViewController.swift index 0b02147af..1f420462a 100644 --- a/Signal/src/ViewControllers/ContactViewController.swift +++ b/Signal/src/ViewControllers/ContactViewController.swift @@ -273,7 +273,7 @@ class ContactViewController: OWSViewController, ContactShareViewHelperDelegate { stackView.axis = .horizontal stackView.distribution = .fillEqually stackView.addArrangedSubview(createCircleActionButton(text: NSLocalizedString("ACTION_SEND_MESSAGE", - comment: "Label for 'sent message' button in contact view."), + comment: "Label for 'send message' button in contact view."), imageName: "contact_view_message", actionBlock: { [weak self] in guard let strongSelf = self else { return } @@ -539,7 +539,7 @@ class ContactViewController: OWSViewController, ContactShareViewHelperDelegate { if let e164 = phoneNumber.tryToConvertToE164() { if contactShare.systemContactsWithSignalAccountPhoneNumbers(contactsManager).contains(e164) { actionSheet.addAction(UIAlertAction(title: NSLocalizedString("ACTION_SEND_MESSAGE", - comment: "Label for 'sent message' button in contact view."), + comment: "Label for 'send message' button in contact view."), style: .default) { _ in SignalApp.shared().presentConversation(forRecipientId: e164, action: .compose) }) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m index b6e43c341..03bbc823b 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m @@ -20,12 +20,15 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) UIView *dateStrokeView; @property (nonatomic) UILabel *dateHeaderLabel; @property (nonatomic) AvatarImageView *avatarView; +@property (nonatomic, nullable) UIImageView *sendFailureBadgeView; @property (nonatomic, nullable) NSMutableArray *viewConstraints; @property (nonatomic) BOOL isPresentingMenuController; @end +#pragma mark - + @implementation OWSMessageCell // `[UIView init]` invokes `[self initWithFrame:...]`. @@ -131,6 +134,15 @@ NS_ASSUME_NONNULL_BEGIN return self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage; } +- (BOOL)shouldHaveSendFailureBadge +{ + if (![self.viewItem.interaction isKindOfClass:[TSOutgoingMessage class]]) { + return NO; + } + TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction; + return outgoingMessage.messageState == TSOutgoingMessageStateFailed; +} + #pragma mark - Load - (void)loadForDisplayWithTransaction:(YapDatabaseReadTransaction *)transaction @@ -158,13 +170,42 @@ NS_ASSUME_NONNULL_BEGIN relation:NSLayoutRelationGreaterThanOrEqual], ]]; } else { - [self.viewConstraints addObjectsFromArray:@[ - [self.messageBubbleView autoPinEdgeToSuperviewEdge:ALEdgeLeading - withInset:self.conversationStyle.gutterLeading - relation:NSLayoutRelationGreaterThanOrEqual], - [self.messageBubbleView autoPinEdgeToSuperviewEdge:ALEdgeTrailing - withInset:self.conversationStyle.gutterTrailing], - ]]; + if (self.shouldHaveSendFailureBadge) { + self.sendFailureBadgeView = [UIImageView new]; + self.sendFailureBadgeView.image = + [self.sendFailureBadge imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + self.sendFailureBadgeView.tintColor = [UIColor ows_destructiveRedColor]; + [self.contentView addSubview:self.sendFailureBadgeView]; + + CGFloat sendFailureBadgeBottomMargin + = round(self.conversationStyle.lastTextLineAxis - self.sendFailureBadgeSize * 0.5f); + [self.viewConstraints addObjectsFromArray:@[ + [self.messageBubbleView autoPinEdgeToSuperviewEdge:ALEdgeLeading + withInset:self.conversationStyle.gutterLeading + relation:NSLayoutRelationGreaterThanOrEqual], + [self.sendFailureBadgeView autoPinLeadingToTrailingEdgeOfView:self.messageBubbleView + offset:self.sendFailureBadgeSpacing], + // V-align the "send failure" badge with the + // last line of the text (if any, or where it + // would be). + [self.messageBubbleView autoPinEdge:ALEdgeBottom + toEdge:ALEdgeBottom + ofView:self.sendFailureBadgeView + withOffset:sendFailureBadgeBottomMargin], + [self.sendFailureBadgeView autoPinEdgeToSuperviewEdge:ALEdgeTrailing + withInset:self.conversationStyle.errorGutterTrailing], + [self.sendFailureBadgeView autoSetDimension:ALDimensionWidth toSize:self.sendFailureBadgeSize], + [self.sendFailureBadgeView autoSetDimension:ALDimensionHeight toSize:self.sendFailureBadgeSize], + ]]; + } else { + [self.viewConstraints addObjectsFromArray:@[ + [self.messageBubbleView autoPinEdgeToSuperviewEdge:ALEdgeLeading + withInset:self.conversationStyle.gutterLeading + relation:NSLayoutRelationGreaterThanOrEqual], + [self.messageBubbleView autoPinEdgeToSuperviewEdge:ALEdgeTrailing + withInset:self.conversationStyle.gutterTrailing], + ]]; + } } [self updateDateHeader]; @@ -184,6 +225,24 @@ NS_ASSUME_NONNULL_BEGIN } } +- (UIImage *)sendFailureBadge +{ + UIImage *image = [UIImage imageNamed:@"message_send_failed"]; + OWSAssert(image); + OWSAssert(image.size.width == self.sendFailureBadgeSize && image.size.height == self.sendFailureBadgeSize); + return image; +} + +- (CGFloat)sendFailureBadgeSize +{ + return 20.f; +} + +- (CGFloat)sendFailureBadgeSpacing +{ + return 8.f; +} + // * If cell is visible, lazy-load (expensive) view contents. // * If cell is not visible, eagerly unload view contents. - (void)ensureMediaLoadState @@ -362,6 +421,10 @@ NS_ASSUME_NONNULL_BEGIN cellSize.height += self.dateHeaderHeight; + if (self.shouldHaveSendFailureBadge) { + cellSize.width += self.sendFailureBadgeSize + self.sendFailureBadgeSpacing; + } + cellSize = CGSizeCeil(cellSize); return cellSize; @@ -404,6 +467,9 @@ NS_ASSUME_NONNULL_BEGIN self.avatarView.image = nil; [self.avatarView removeFromSuperview]; + [self.sendFailureBadgeView removeFromSuperview]; + self.sendFailureBadgeView = nil; + [self hideMenuControllerIfNecessary]; [[NSNotificationCenter defaultCenter] removeObserver:self]; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m index 8676723ad..75f6f6dfb 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageFooterView.m @@ -107,21 +107,24 @@ NS_ASSUME_NONNULL_BEGIN statusIndicatorImage = [UIImage imageNamed:@"message_status_delivered"]; break; case MessageReceiptStatusFailed: - // TODO: - statusIndicatorImage = [UIImage imageNamed:@"message_status_sending"]; + // No status indicator icon. break; } - OWSAssert(statusIndicatorImage); - OWSAssert(statusIndicatorImage.size.width <= self.maxImageWidth); - self.statusIndicatorImageView.image = - [statusIndicatorImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - if (messageStatus == MessageReceiptStatusRead) { - self.statusIndicatorImageView.tintColor = [UIColor ows_signalBlueColor]; + if (statusIndicatorImage) { + OWSAssert(statusIndicatorImage.size.width <= self.maxImageWidth); + self.statusIndicatorImageView.image = + [statusIndicatorImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + if (messageStatus == MessageReceiptStatusRead) { + self.statusIndicatorImageView.tintColor = [UIColor ows_signalBlueColor]; + } else { + self.statusIndicatorImageView.tintColor = textColor; + } + self.statusIndicatorImageView.hidden = NO; } else { - self.statusIndicatorImageView.tintColor = textColor; + self.statusIndicatorImageView.image = nil; + self.statusIndicatorImageView.hidden = YES; } - self.statusIndicatorImageView.hidden = NO; } else { self.statusIndicatorImageView.image = nil; self.statusIndicatorImageView.hidden = YES; @@ -141,13 +144,32 @@ NS_ASSUME_NONNULL_BEGIN [self.statusIndicatorImageView.layer addAnimation:animation forKey:@"animation"]; } +- (BOOL)isFailedOutgoingMessage:(ConversationViewItem *)viewItem +{ + OWSAssert(viewItem); + + if (viewItem.interaction.interactionType != OWSInteractionType_OutgoingMessage) { + return NO; + } + + TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)viewItem.interaction; + MessageReceiptStatus messageStatus = + [MessageRecipientStatusUtils recipientStatusWithOutgoingMessage:outgoingMessage]; + return messageStatus == MessageReceiptStatusFailed; +} + - (void)configureLabelsWithConversationViewItem:(ConversationViewItem *)viewItem { OWSAssert(viewItem); [self configureFonts]; - self.timestampLabel.text = [DateUtil formatTimestampAsTimeShort:viewItem.interaction.timestamp]; + if ([self isFailedOutgoingMessage:viewItem]) { + self.timestampLabel.text + = NSLocalizedString(@"MESSAGE_STATUS_SEND_FAILED", @"Label indicating that a message failed to send."); + } else { + self.timestampLabel.text = [DateUtil formatTimestampAsTimeShort:viewItem.interaction.timestamp]; + } } - (CGSize)measureWithConversationViewItem:(ConversationViewItem *)viewItem @@ -158,10 +180,11 @@ NS_ASSUME_NONNULL_BEGIN CGSize result = CGSizeZero; result.height = MAX(self.timestampLabel.font.lineHeight, self.imageHeight); + result.width = [self.timestampLabel sizeThatFits:CGSizeZero].width; if (viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) { - result.width = ([self.timestampLabel sizeThatFits:CGSizeZero].width + self.maxImageWidth + self.hSpacing); - } else { - result.width = [self.timestampLabel sizeThatFits:CGSizeZero].width; + if (![self isFailedOutgoingMessage:viewItem]) { + result.width += (self.maxImageWidth + self.hSpacing); + } } return CGSizeCeil(result); } diff --git a/Signal/src/ViewControllers/Utils/MessageRecipientStatusUtils.swift b/Signal/src/ViewControllers/Utils/MessageRecipientStatusUtils.swift index f465c1747..8db4071ba 100644 --- a/Signal/src/ViewControllers/Utils/MessageRecipientStatusUtils.swift +++ b/Signal/src/ViewControllers/Utils/MessageRecipientStatusUtils.swift @@ -58,14 +58,14 @@ public class MessageRecipientStatusUtils: NSObject { switch recipientState.state { case .failed: let shortStatusMessage = NSLocalizedString("MESSAGE_STATUS_FAILED_SHORT", comment: "status message for failed messages") - let longStatusMessage = NSLocalizedString("MESSAGE_STATUS_FAILED", comment: "message footer for failed messages") + let longStatusMessage = NSLocalizedString("MESSAGE_STATUS_FAILED", comment: "status message for failed messages") return (status:.failed, shortStatusMessage:shortStatusMessage, longStatusMessage:longStatusMessage) case .sending: if outgoingMessage.hasAttachments() { assert(outgoingMessage.messageState == .sending) let statusMessage = NSLocalizedString("MESSAGE_STATUS_UPLOADING", - comment: "message footer while attachment is uploading") + comment: "status message while attachment is uploading") return (status:.uploading, shortStatusMessage:statusMessage, longStatusMessage:statusMessage) } else { assert(outgoingMessage.messageState == .sending) @@ -78,7 +78,7 @@ public class MessageRecipientStatusUtils: NSObject { if let readTimestamp = recipientState.readTimestamp { let timestampString = DateUtil.formatPastTimestampRelativeToNow(readTimestamp.uint64Value) let shortStatusMessage = timestampString - let longStatusMessage = NSLocalizedString("MESSAGE_STATUS_READ", comment: "message footer for read messages").rtlSafeAppend(" ") + let longStatusMessage = NSLocalizedString("MESSAGE_STATUS_READ", comment: "status message for read messages").rtlSafeAppend(" ") .rtlSafeAppend(timestampString) return (status:.read, shortStatusMessage:shortStatusMessage, longStatusMessage:longStatusMessage) } @@ -92,7 +92,7 @@ public class MessageRecipientStatusUtils: NSObject { } let statusMessage = NSLocalizedString("MESSAGE_STATUS_SENT", - comment: "message footer for sent messages") + comment: "status message for sent messages") return (status:.sent, shortStatusMessage:statusMessage, longStatusMessage:statusMessage) case .skipped: let statusMessage = NSLocalizedString("MESSAGE_STATUS_RECIPIENT_SKIPPED", @@ -107,29 +107,29 @@ public class MessageRecipientStatusUtils: NSObject { switch outgoingMessage.messageState { case .failed: // Use the "long" version of this message here. - return (.failed, NSLocalizedString("MESSAGE_STATUS_FAILED", comment: "message footer for failed messages")) + return (.failed, NSLocalizedString("MESSAGE_STATUS_FAILED", comment: "status message for failed messages")) case .sending: if outgoingMessage.hasAttachments() { return (.uploading, NSLocalizedString("MESSAGE_STATUS_UPLOADING", - comment: "message footer while attachment is uploading")) + comment: "status message while attachment is uploading")) } else { return (.sending, NSLocalizedString("MESSAGE_STATUS_SENDING", comment: "message status while message is sending.")) } case .sent: if outgoingMessage.readRecipientIds().count > 0 { - return (.read, NSLocalizedString("MESSAGE_STATUS_READ", comment: "message footer for read messages")) + return (.read, NSLocalizedString("MESSAGE_STATUS_READ", comment: "status message for read messages")) } if outgoingMessage.wasDeliveredToAnyRecipient { return (.delivered, NSLocalizedString("MESSAGE_STATUS_DELIVERED", comment: "message status for message delivered to their recipient.")) } return (.sent, NSLocalizedString("MESSAGE_STATUS_SENT", - comment: "message footer for sent messages")) + comment: "status message for sent messages")) default: owsFail("\(self.logTag) Message has unexpected status: \(outgoingMessage.messageState).") return (.sent, NSLocalizedString("MESSAGE_STATUS_SENT", - comment: "message footer for sent messages")) + comment: "status message for sent messages")) } } diff --git a/Signal/translations/ar.lproj/Localizable.strings b/Signal/translations/ar.lproj/Localizable.strings index ca573c23e..af202f0e4 100644 --- a/Signal/translations/ar.lproj/Localizable.strings +++ b/Signal/translations/ar.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "ارسال دعوة"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "إرسال رسالة"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/az_AZ.lproj/Localizable.strings b/Signal/translations/az_AZ.lproj/Localizable.strings index 08136c13f..e2658a0ee 100644 --- a/Signal/translations/az_AZ.lproj/Localizable.strings +++ b/Signal/translations/az_AZ.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invite to Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send Message"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/bg.lproj/Localizable.strings b/Signal/translations/bg.lproj/Localizable.strings index a7691046d..a1a8295a6 100644 --- a/Signal/translations/bg.lproj/Localizable.strings +++ b/Signal/translations/bg.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Покани във Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Изпрати Съобщение"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/bs.lproj/Localizable.strings b/Signal/translations/bs.lproj/Localizable.strings index 9e16030e4..b4aaa00ed 100644 --- a/Signal/translations/bs.lproj/Localizable.strings +++ b/Signal/translations/bs.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invite to Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send Message"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/ca.lproj/Localizable.strings b/Signal/translations/ca.lproj/Localizable.strings index ea90ecdb0..41729b205 100644 --- a/Signal/translations/ca.lproj/Localizable.strings +++ b/Signal/translations/ca.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invite to Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send Message"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/cs.lproj/Localizable.strings b/Signal/translations/cs.lproj/Localizable.strings index 3810e3525..9cdbefaf8 100644 --- a/Signal/translations/cs.lproj/Localizable.strings +++ b/Signal/translations/cs.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invite to Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send Message"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/da.lproj/Localizable.strings b/Signal/translations/da.lproj/Localizable.strings index 81a7c50bf..ee708fd0e 100644 --- a/Signal/translations/da.lproj/Localizable.strings +++ b/Signal/translations/da.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invitér til Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send Besked"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/de.lproj/Localizable.strings b/Signal/translations/de.lproj/Localizable.strings index 3a7d9bcb0..e567be6c1 100644 --- a/Signal/translations/de.lproj/Localizable.strings +++ b/Signal/translations/de.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Zu Signal einladen"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Nachricht senden"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/el_GR.lproj/Localizable.strings b/Signal/translations/el_GR.lproj/Localizable.strings index 106d25c73..6e675d0ed 100644 --- a/Signal/translations/el_GR.lproj/Localizable.strings +++ b/Signal/translations/el_GR.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invite to Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send Message"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 34780cb68..1e6b7624e 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -10,7 +10,8 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invite to Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. + Label for button that lets you send a message to a contact. */ "ACTION_SEND_MESSAGE" = "Send Message"; /* Label for 'share contact' button. */ @@ -511,7 +512,7 @@ /* Navbar title when viewing settings for a 1-on-1 thread */ "CONVERSATION_SETTINGS_CONTACT_INFO_TITLE" = "Contact Info"; -/* Indicates that user's profile has been shared with a group. */ +/* Label for table cell which leads to picking a new conversation color */ "CONVERSATION_SETTINGS_CONVERSATION_COLOR" = "Color"; /* Navbar title when viewing settings for a group thread */ @@ -1205,25 +1206,28 @@ /* message status for message delivered to their recipient. */ "MESSAGE_STATUS_DELIVERED" = "Delivered"; -/* message footer for failed messages */ -"MESSAGE_STATUS_FAILED" = "Sending failed. Tap for info."; +/* status message for failed messages */ +"MESSAGE_STATUS_FAILED" = "Sending failed."; /* status message for failed messages */ "MESSAGE_STATUS_FAILED_SHORT" = "Failed"; -/* message footer for read messages */ +/* status message for read messages */ "MESSAGE_STATUS_READ" = "Read"; /* message status if message delivery to a recipient is skipped. We skip delivering group messages to users who have left the group or unregistered their Signal account. */ "MESSAGE_STATUS_RECIPIENT_SKIPPED" = "Skipped"; +/* Label indicating that a message failed to send. */ +"MESSAGE_STATUS_SEND_FAILED" = "Send Failed"; + /* message status while message is sending. */ "MESSAGE_STATUS_SENDING" = "Sending..."; -/* message footer for sent messages */ +/* status message for sent messages */ "MESSAGE_STATUS_SENT" = "Sent"; -/* message footer while attachment is uploading */ +/* status message while attachment is uploading */ "MESSAGE_STATUS_UPLOADING" = "Uploading…"; /* Indicates that one member of this group conversation is no longer verified. Embeds {{user's name or phone number}}. */ diff --git a/Signal/translations/es.lproj/Localizable.strings b/Signal/translations/es.lproj/Localizable.strings index bbd2bdbfc..49cdde423 100644 --- a/Signal/translations/es.lproj/Localizable.strings +++ b/Signal/translations/es.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invitar a Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Enviar mensaje"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/et.lproj/Localizable.strings b/Signal/translations/et.lproj/Localizable.strings index a4198532b..6b2282ba1 100644 --- a/Signal/translations/et.lproj/Localizable.strings +++ b/Signal/translations/et.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Kutsu Signalisse"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Saada sõnum"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/fa.lproj/Localizable.strings b/Signal/translations/fa.lproj/Localizable.strings index c4eb1679f..be3e801d8 100644 --- a/Signal/translations/fa.lproj/Localizable.strings +++ b/Signal/translations/fa.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "دعوت به سیگنال"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "ارسال پیام"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/fi.lproj/Localizable.strings b/Signal/translations/fi.lproj/Localizable.strings index e8126d663..359046fe4 100644 --- a/Signal/translations/fi.lproj/Localizable.strings +++ b/Signal/translations/fi.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Kutsu Signaliin"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Lähetä viesti"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/fil.lproj/Localizable.strings b/Signal/translations/fil.lproj/Localizable.strings index 142eb7b52..56cbf59ca 100644 --- a/Signal/translations/fil.lproj/Localizable.strings +++ b/Signal/translations/fil.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invite to Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send Message"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/fr.lproj/Localizable.strings b/Signal/translations/fr.lproj/Localizable.strings index a280d4f28..30ef46723 100644 --- a/Signal/translations/fr.lproj/Localizable.strings +++ b/Signal/translations/fr.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Inviter à Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Envoyer un message"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/gl.lproj/Localizable.strings b/Signal/translations/gl.lproj/Localizable.strings index cc59135cc..857f6c6f4 100644 --- a/Signal/translations/gl.lproj/Localizable.strings +++ b/Signal/translations/gl.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invite to Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send Message"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/he.lproj/Localizable.strings b/Signal/translations/he.lproj/Localizable.strings index a24130633..d49fd74ef 100644 --- a/Signal/translations/he.lproj/Localizable.strings +++ b/Signal/translations/he.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "הזמן אל Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "שלח הודעה"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/hr.lproj/Localizable.strings b/Signal/translations/hr.lproj/Localizable.strings index b7c9223bb..5b3d4d0e9 100644 --- a/Signal/translations/hr.lproj/Localizable.strings +++ b/Signal/translations/hr.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invite to Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send Message"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/hu.lproj/Localizable.strings b/Signal/translations/hu.lproj/Localizable.strings index 16cf3f787..390863906 100644 --- a/Signal/translations/hu.lproj/Localizable.strings +++ b/Signal/translations/hu.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invite to Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send Message"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/id.lproj/Localizable.strings b/Signal/translations/id.lproj/Localizable.strings index a6e65d3ae..178da0459 100644 --- a/Signal/translations/id.lproj/Localizable.strings +++ b/Signal/translations/id.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Undang gabung ke Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Kirim Pesan"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/it_IT.lproj/Localizable.strings b/Signal/translations/it_IT.lproj/Localizable.strings index 7b4b3b903..aeea75991 100644 --- a/Signal/translations/it_IT.lproj/Localizable.strings +++ b/Signal/translations/it_IT.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invita a Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Manda messaggio"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/ja_JP.lproj/Localizable.strings b/Signal/translations/ja_JP.lproj/Localizable.strings index 37d4323ab..de46fd7cf 100644 --- a/Signal/translations/ja_JP.lproj/Localizable.strings +++ b/Signal/translations/ja_JP.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "招待する"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "メッセージ送信"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/km.lproj/Localizable.strings b/Signal/translations/km.lproj/Localizable.strings index f1f424874..fe3690f8a 100644 --- a/Signal/translations/km.lproj/Localizable.strings +++ b/Signal/translations/km.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "អញ្ជើញចូលប្រើស៊ីហ្គណល"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "ផ្ញើសារ"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/ko_KR.lproj/Localizable.strings b/Signal/translations/ko_KR.lproj/Localizable.strings index a919df362..5183f708d 100644 --- a/Signal/translations/ko_KR.lproj/Localizable.strings +++ b/Signal/translations/ko_KR.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invite to Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send Message"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/lt.lproj/Localizable.strings b/Signal/translations/lt.lproj/Localizable.strings index bf77e085d..83dcd5440 100644 --- a/Signal/translations/lt.lproj/Localizable.strings +++ b/Signal/translations/lt.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Pakviesti į Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Siųsti žinutę"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/lv.lproj/Localizable.strings b/Signal/translations/lv.lproj/Localizable.strings index 3353347df..39ffddbff 100644 --- a/Signal/translations/lv.lproj/Localizable.strings +++ b/Signal/translations/lv.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invite to Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send Message"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/mk.lproj/Localizable.strings b/Signal/translations/mk.lproj/Localizable.strings index b0ceea84e..1aaec99fb 100644 --- a/Signal/translations/mk.lproj/Localizable.strings +++ b/Signal/translations/mk.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invite to Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send Message"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/my.lproj/Localizable.strings b/Signal/translations/my.lproj/Localizable.strings index a9309ca9f..f94594468 100644 --- a/Signal/translations/my.lproj/Localizable.strings +++ b/Signal/translations/my.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invite to Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send Message"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/nb_NO.lproj/Localizable.strings b/Signal/translations/nb_NO.lproj/Localizable.strings index 23c4071ef..4873c8e27 100644 --- a/Signal/translations/nb_NO.lproj/Localizable.strings +++ b/Signal/translations/nb_NO.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Inviter til Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send melding"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/nl.lproj/Localizable.strings b/Signal/translations/nl.lproj/Localizable.strings index 604737918..048b419d2 100644 --- a/Signal/translations/nl.lproj/Localizable.strings +++ b/Signal/translations/nl.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Nodig uit voor Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Stuur bericht"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/pl.lproj/Localizable.strings b/Signal/translations/pl.lproj/Localizable.strings index 2f4a07be3..ac80d4aa8 100644 --- a/Signal/translations/pl.lproj/Localizable.strings +++ b/Signal/translations/pl.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Zaproś do Signala"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Wyślij wiadomość"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/pt_BR.lproj/Localizable.strings b/Signal/translations/pt_BR.lproj/Localizable.strings index fd5bc8c8e..6caccbf38 100644 --- a/Signal/translations/pt_BR.lproj/Localizable.strings +++ b/Signal/translations/pt_BR.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Convidar para o Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Enviar mensagem"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/pt_PT.lproj/Localizable.strings b/Signal/translations/pt_PT.lproj/Localizable.strings index 9e3a41bce..38b0d3a93 100644 --- a/Signal/translations/pt_PT.lproj/Localizable.strings +++ b/Signal/translations/pt_PT.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Convidar para o Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Enviar Mensagem"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/ro.lproj/Localizable.strings b/Signal/translations/ro.lproj/Localizable.strings index dc123aa5c..9cdb63750 100644 --- a/Signal/translations/ro.lproj/Localizable.strings +++ b/Signal/translations/ro.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invită pe Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Trimite mesaj"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/ru.lproj/Localizable.strings b/Signal/translations/ru.lproj/Localizable.strings index a118b2ebe..9ad28686c 100644 --- a/Signal/translations/ru.lproj/Localizable.strings +++ b/Signal/translations/ru.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Пригласить в Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Отправить сообщение"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/sl.lproj/Localizable.strings b/Signal/translations/sl.lproj/Localizable.strings index 9199d28cd..baafe49e2 100644 --- a/Signal/translations/sl.lproj/Localizable.strings +++ b/Signal/translations/sl.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Povabi k uporabi"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Pošlji sporočilo"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/sn.lproj/Localizable.strings b/Signal/translations/sn.lproj/Localizable.strings index 2e84d954d..1b9119d81 100644 --- a/Signal/translations/sn.lproj/Localizable.strings +++ b/Signal/translations/sn.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invite to Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send Message"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/sq.lproj/Localizable.strings b/Signal/translations/sq.lproj/Localizable.strings index cf4c6cd0c..889ec97da 100644 --- a/Signal/translations/sq.lproj/Localizable.strings +++ b/Signal/translations/sq.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invite to Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send Message"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/sv_SE.lproj/Localizable.strings b/Signal/translations/sv_SE.lproj/Localizable.strings index 9cda68bc2..06f48d985 100644 --- a/Signal/translations/sv_SE.lproj/Localizable.strings +++ b/Signal/translations/sv_SE.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Bjud in till Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Skicka meddelande"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/th_TH.lproj/Localizable.strings b/Signal/translations/th_TH.lproj/Localizable.strings index 2216a82cb..0fee817b7 100644 --- a/Signal/translations/th_TH.lproj/Localizable.strings +++ b/Signal/translations/th_TH.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Invite to Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Send Message"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/tr_TR.lproj/Localizable.strings b/Signal/translations/tr_TR.lproj/Localizable.strings index 4666454ed..3cff17bcc 100644 --- a/Signal/translations/tr_TR.lproj/Localizable.strings +++ b/Signal/translations/tr_TR.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "Signal'e davet et"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "Mesaj Gönder"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/zh_CN.lproj/Localizable.strings b/Signal/translations/zh_CN.lproj/Localizable.strings index d70d7c531..316746f6b 100644 --- a/Signal/translations/zh_CN.lproj/Localizable.strings +++ b/Signal/translations/zh_CN.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "邀请其使用 Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "发送消息"; /* Label for 'share contact' button. */ diff --git a/Signal/translations/zh_TW.lproj/Localizable.strings b/Signal/translations/zh_TW.lproj/Localizable.strings index 0a020aef9..709edeafc 100644 --- a/Signal/translations/zh_TW.lproj/Localizable.strings +++ b/Signal/translations/zh_TW.lproj/Localizable.strings @@ -10,7 +10,7 @@ /* Label for 'invite' button in contact view. */ "ACTION_INVITE" = "邀請來使用 Signal"; -/* Label for 'sent message' button in contact view. */ +/* Label for 'send message' button in contact view. */ "ACTION_SEND_MESSAGE" = "傳送訊息"; /* Label for 'share contact' button. */ diff --git a/SignalMessaging/categories/UIColor+OWS.m b/SignalMessaging/categories/UIColor+OWS.m index d8447eb65..37c1e8586 100644 --- a/SignalMessaging/categories/UIColor+OWS.m +++ b/SignalMessaging/categories/UIColor+OWS.m @@ -79,7 +79,7 @@ NS_ASSUME_NONNULL_BEGIN + (UIColor *)ows_destructiveRedColor { - return [UIColor colorWithRed:0.98639106750488281 green:0.10408364236354828 blue:0.33135244250297546 alpha:1.f]; + return [UIColor colorWithRGBHex:0xF44336]; } + (UIColor *)ows_errorMessageBorderColor diff --git a/SignalMessaging/utils/ConversationStyle.swift b/SignalMessaging/utils/ConversationStyle.swift index 4ad89fa46..4f534d501 100644 --- a/SignalMessaging/utils/ConversationStyle.swift +++ b/SignalMessaging/utils/ConversationStyle.swift @@ -27,6 +27,7 @@ public class ConversationStyle: NSObject { // like "date headers" and "unread indicator". @objc public var fullWidthGutterLeading: CGFloat = 0 @objc public var fullWidthGutterTrailing: CGFloat = 0 + @objc public var errorGutterTrailing: CGFloat = 0 // viewWidth - (gutterLeading + gutterTrailing) @objc public var contentWidth: CGFloat = 0 @@ -87,6 +88,7 @@ public class ConversationStyle: NSObject { } fullWidthGutterLeading = gutterLeading fullWidthGutterTrailing = gutterTrailing + errorGutterTrailing = 16 contentWidth = viewWidth - (gutterLeading + gutterTrailing)