diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 5f631b8989..db5f4757e4 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ 04441DDE2F115CAF0095E1F4 /* chat_item_pin_message_04.txtproto in Resources */ = {isa = PBXBuildFile; fileRef = 04441DA02F115CAF0095E1F4 /* chat_item_pin_message_04.txtproto */; }; 04441DEA2F17EF6C0095E1F4 /* CVMemberLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04441DE92F17EF690095E1F4 /* CVMemberLabel.swift */; }; 04441DED2F1816BB0095E1F4 /* MemberLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04441DEB2F18121C0095E1F4 /* MemberLabel.swift */; }; + 04441DEF2F194B800095E1F4 /* MemberLabelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04441DEE2F194B770095E1F4 /* MemberLabelViewController.swift */; }; 04472D5E2E8AD74200D69EE0 /* OutgoingPollVote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04472D5D2E8AD73A00D69EE0 /* OutgoingPollVote.swift */; }; 044A2C6E2EEB444E00AA3CA0 /* BackupArchivePinMessageChatUpdateArchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 044A2C6D2EEB443E00AA3CA0 /* BackupArchivePinMessageChatUpdateArchiver.swift */; }; 044D77782E5E6D750048C21A /* PollVoteRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 044D77772E5E6D700048C21A /* PollVoteRecord.swift */; }; @@ -4108,6 +4109,7 @@ 04441DBA2F115CAF0095E1F4 /* chat_item_pin_message_update_02.txtproto */ = {isa = PBXFileReference; lastKnownFileType = text; path = chat_item_pin_message_update_02.txtproto; sourceTree = ""; }; 04441DE92F17EF690095E1F4 /* CVMemberLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CVMemberLabel.swift; sourceTree = ""; }; 04441DEB2F18121C0095E1F4 /* MemberLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberLabel.swift; sourceTree = ""; }; + 04441DEE2F194B770095E1F4 /* MemberLabelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberLabelViewController.swift; sourceTree = ""; }; 04472D5D2E8AD73A00D69EE0 /* OutgoingPollVote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingPollVote.swift; sourceTree = ""; }; 044A2C6D2EEB443E00AA3CA0 /* BackupArchivePinMessageChatUpdateArchiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupArchivePinMessageChatUpdateArchiver.swift; sourceTree = ""; }; 044D77772E5E6D700048C21A /* PollVoteRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollVoteRecord.swift; sourceTree = ""; }; @@ -9172,6 +9174,7 @@ 76C87FE028BE8E2400BD8709 /* LinkPreviewAttachmentViewController.swift */, 88A9729322FB4D02004B4FBF /* LocationPicker.swift */, 3496744E2076ACCE00080B5F /* LongTextViewController.swift */, + 04441DEE2F194B770095E1F4 /* MemberLabelViewController.swift */, 4CFF4C0920F55BBA005DA313 /* MessageActionsToolbar.swift */, 34CA1C261F7156F300E51C51 /* MessageDetailViewController.swift */, 881218EF238CA51600E6F271 /* MessageReactionPicker.swift */, @@ -18134,6 +18137,7 @@ 4C8A6DFC22E5499300469AE7 /* MediaZoomAnimationController.swift in Sources */, 8837F74123DA0B0F00772A32 /* MegaphoneView.swift in Sources */, 88A357B923639384009D6B9A /* MemberActionSheet.swift in Sources */, + 04441DEF2F194B800095E1F4 /* MemberLabelViewController.swift in Sources */, 346EAA14250199A400E8AB6F /* MemberRequestView.swift in Sources */, 4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */, 4CB5F26720F6E1E2004D1B42 /* MessageActionsToolbar.swift in Sources */, diff --git a/Signal/src/ViewControllers/MemberLabelViewController.swift b/Signal/src/ViewControllers/MemberLabelViewController.swift new file mode 100644 index 0000000000..9ad1e4c989 --- /dev/null +++ b/Signal/src/ViewControllers/MemberLabelViewController.swift @@ -0,0 +1,438 @@ +// +// Copyright 2026 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only +// + +import LibSignalClient +import SignalServiceKit +import SignalUI +import SwiftUI + +class MemberLabelViewController: OWSViewController { + override init() { + super.init() + + view.backgroundColor = UIColor.Signal.groupedBackground + navigationItem.title = OWSLocalizedString("MEMBER_LABEL_VIEW_TITLE", comment: "Title for a view where users can edit and preview their member label.") + + // TODO: fade if text field empty + navigationItem.rightBarButtonItem = .init(barButtonSystemItem: .done, target: self, action: #selector(didTapDone)) + navigationItem.leftBarButtonItem = .init(barButtonSystemItem: .cancel, target: self, action: #selector(didTapCancel)) + navigationItem.rightBarButtonItem?.tintColor = UIColor.Signal.ultramarine + } + + override func viewDidLoad() { + super.viewDidLoad() + + createViews() + } + + private func createViews() { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = 20 + + let subtitleLabel = UILabel() + subtitleLabel.text = OWSLocalizedString( + "MEMBER_LABEL_VIEW_SUBTITLE", + comment: "Subtitle for a view where users can edit and preview their member label.", + ) + subtitleLabel.numberOfLines = 0 + subtitleLabel.font = .dynamicTypeCaption1Clamped + subtitleLabel.textColor = UIColor.Signal.secondaryLabel + subtitleLabel.textAlignment = .center + + stackView.addArrangedSubview(subtitleLabel) + + let textFieldStack = UIStackView() + textFieldStack.layer.cornerRadius = 27 + textFieldStack.backgroundColor = UIColor.Signal.tertiaryBackground + textFieldStack.axis = .horizontal + textFieldStack.alignment = .center + textFieldStack.distribution = .fill + textFieldStack.spacing = 8 + textFieldStack.isLayoutMarginsRelativeArrangement = true + textFieldStack.layoutMargins = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) + + let addEmojiButton = UIButton(type: .system) + addEmojiButton.setImage(UIImage(named: "emoji-plus"), for: .normal) + addEmojiButton.tintColor = UIColor.Signal.secondaryLabel + addEmojiButton.setContentHuggingPriority(.required, for: .horizontal) + addEmojiButton.setContentCompressionResistancePriority(.required, for: .horizontal) + + let textField = UITextField() + textField.placeholder = OWSLocalizedString( + "MEMBER_LABEL_VIEW_PLACEHOLDER_TEXT", + comment: "Placeholder text in text field where user can edit their member label.", + ) + textField.font = .dynamicTypeBodyClamped + + textFieldStack.addArrangedSubview(addEmojiButton) + textFieldStack.addArrangedSubview(textField) + stackView.addArrangedSubview(textFieldStack) + stackView.setCustomSpacing(34, after: textFieldStack) + + textFieldStack.translatesAutoresizingMaskIntoConstraints = false + textFieldStack.heightAnchor.constraint(equalToConstant: 52).isActive = true + + guard + let mockConversationItem = buildMockConversationItem(), + let previewContainer = messageBubblePreviewContainer(renderItem: mockConversationItem) + else { + return + } + + stackView.addArrangedSubview(previewContainer) + + view.addSubview(stackView) + stackView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16), + stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 22), + stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -22), + ]) + + } + + private func buildMockConversationItem() -> CVRenderItem? { + let db = DependenciesBridge.shared.db + let attachmentContentValidator = DependenciesBridge.shared.attachmentContentValidator + let tsAccountManager = DependenciesBridge.shared.tsAccountManager + + let messageBody = db.write { tx in + attachmentContentValidator.truncatedMessageBodyForInlining( + MessageBody(text: OWSLocalizedString( + "MEMBER_LABEL_VIEW_MESSAGE_PREVIEW_TEXT", + comment: "Text shown in the preview message bubble when a user is editing their member label.", + ), ranges: .empty), + tx: tx, + ) + } + + guard + let localAci = db.read(block: { tx in + tsAccountManager.localIdentifiers(tx: tx)?.aci + }), + let secretParams = try? GroupSecretParams.generate() + else { + return nil + } + + var groupModelBuilder = TSGroupModelBuilder(secretParams: secretParams) + var groupMembershipBuilder = groupModelBuilder.groupMembership.asBuilder + groupMembershipBuilder.setMemberLabel(label: "test123", aci: localAci) + groupModelBuilder.groupMembership = groupMembershipBuilder.build() + + guard let groupModel = try? groupModelBuilder.buildAsV2() else { + return nil + } + + let mockGroupThread = MockGroupThread(groupModel: groupModel) + let mockMessage = MockIncomingMessage(messageBody: messageBody, thread: mockGroupThread, authorAci: localAci) + + let renderItem = db.read { tx in + let threadAssociatedData = ThreadAssociatedData.fetchOrDefault(for: mockGroupThread, ignoreMissing: true, transaction: tx) + + let conversationStyle = ConversationStyle( + type: .`default`, + thread: mockGroupThread, + viewWidth: view.width - 44, // stack view padding + hasWallpaper: false, + isWallpaperPhoto: false, + chatColor: PaletteChatColor.ultramarine.colorSetting, + ) + + return CVLoader.buildStandaloneRenderItem( + interaction: mockMessage, + thread: mockGroupThread, + threadAssociatedData: threadAssociatedData, + conversationStyle: conversationStyle, + spoilerState: SpoilerRenderState(), + transaction: tx, + ) + } + return renderItem + } + + func messageBubblePreviewContainer(renderItem: CVRenderItem) -> UIView? { + + let previewTitle = UILabel() + previewTitle.text = OWSLocalizedString( + "MEMBER_LABEL_PREVIEW_HEADING", + comment: "Heading shown above the preview of a message bubble with the edited member label.", + ) + previewTitle.font = .dynamicTypeBodyClamped.semibold() + + let cellView = CVCellView() + cellView.configure(renderItem: renderItem, componentDelegate: self) + cellView.isCellVisible = true + cellView.autoSetDimension(.height, toSize: renderItem.cellMeasurement.cellSize.height) + cellView.autoSetDimension(.width, toSize: renderItem.cellMeasurement.cellSize.width) + + let cellContainer = UIView() + cellContainer.layer.cornerRadius = 27 + cellContainer.layer.masksToBounds = true + cellContainer.backgroundColor = UIColor.Signal.tertiaryBackground + + cellContainer.addSubview(cellView) + cellView.autoPinEdge(toSuperviewEdge: .top, withInset: 20) + cellView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 20) + + let previewContainer = UIStackView() + previewContainer.axis = .vertical + previewContainer.spacing = 8 + previewContainer.addArrangedSubview(previewTitle) + previewContainer.addArrangedSubview(cellContainer) + + return previewContainer + } + + @objc + private func didTapDone() { + dismiss(animated: true) + } + + @objc + private func didTapCancel() { + dismiss(animated: true) + } +} + +// MARK: - + +extension MemberLabelViewController: CVComponentDelegate { + var spoilerState: SignalUI.SpoilerRenderState { + return SpoilerRenderState() + } + + func enqueueReload() {} + + func enqueueReloadWithoutCaches() {} + + func didTapBodyTextItem(_ item: CVTextLabel.Item) {} + + func didLongPressBodyTextItem(_ item: CVTextLabel.Item) {} + + func didTapSystemMessageItem(_ item: CVTextLabel.Item) {} + + func didDoubleTapTextViewItem(_ itemViewModel: CVItemViewModelImpl) {} + + func didLongPressTextViewItem( + _ cell: CVCell, + itemViewModel: CVItemViewModelImpl, + shouldAllowReply: Bool, + ) {} + + func didLongPressMediaViewItem( + _ cell: CVCell, + itemViewModel: CVItemViewModelImpl, + shouldAllowReply: Bool, + ) {} + + func didLongPressQuote( + _ cell: CVCell, + itemViewModel: CVItemViewModelImpl, + shouldAllowReply: Bool, + ) {} + + func didLongPressSystemMessage( + _ cell: CVCell, + itemViewModel: CVItemViewModelImpl, + ) {} + + func didLongPressSticker( + _ cell: CVCell, + itemViewModel: CVItemViewModelImpl, + shouldAllowReply: Bool, + ) {} + + func didLongPressPaymentMessage( + _ cell: CVCell, + itemViewModel: CVItemViewModelImpl, + shouldAllowReply: Bool, + ) {} + + func didLongPressPoll( + _ cell: CVCell, + itemViewModel: CVItemViewModelImpl, + shouldAllowReply: Bool, + ) {} + + func didTapPayment(_ payment: PaymentsHistoryItem) {} + + func didChangeLongPress(_ itemViewModel: CVItemViewModelImpl) {} + + func didEndLongPress(_ itemViewModel: CVItemViewModelImpl) {} + + func didCancelLongPress(_ itemViewModel: CVItemViewModelImpl) {} + + // MARK: - + + func willBecomeVisibleWithFailedOrPendingDownloads(_ message: TSMessage) {} + + func didTapFailedOrPendingDownloads(_ message: TSMessage) {} + + func didCancelDownload(_ message: TSMessage, attachmentId: Attachment.IDType) {} + + // MARK: - + + func didTapReplyToItem(_ itemViewModel: CVItemViewModelImpl) {} + + func didTapSenderAvatar(_ interaction: TSInteraction) {} + + func shouldAllowReplyForItem(_ itemViewModel: CVItemViewModelImpl) -> Bool { false } + + func didTapReactions( + reactionState: InteractionReactionState, + message: TSMessage, + ) {} + + func didTapTruncatedTextMessage(_ itemViewModel: CVItemViewModelImpl) {} + + func didTapShowEditHistory(_ itemViewModel: CVItemViewModelImpl) {} + + var hasPendingMessageRequest: Bool { false } + + func didTapUndownloadableMedia() {} + + func didTapUndownloadableGenericFile() {} + + func didTapUndownloadableOversizeText() {} + + func didTapUndownloadableAudio() {} + + func didTapUndownloadableSticker() {} + + func didTapBrokenVideo() {} + + func didTapBodyMedia( + itemViewModel: CVItemViewModelImpl, + attachmentStream: ReferencedAttachmentStream, + imageView: UIView, + ) {} + + func didTapGenericAttachment( + _ attachment: CVComponentGenericAttachment, + ) -> CVAttachmentTapAction { .default } + + func didTapQuotedReply(_ quotedReply: QuotedReplyModel) {} + + func didTapLinkPreview(_ linkPreview: OWSLinkPreview) {} + + func didTapContactShare(_ contactShare: ContactShareViewModel) {} + + func didTapSendMessage(to phoneNumbers: [String]) {} + + func didTapSendInvite(toContactShare contactShare: ContactShareViewModel) {} + + func didTapAddToContacts(contactShare: ContactShareViewModel) {} + + func didTapStickerPack(_ stickerPackInfo: StickerPackInfo) {} + + func didTapGroupInviteLink(url: URL) {} + + func didTapProxyLink(url: URL) {} + + func didTapShowMessageDetail(_ itemViewModel: CVItemViewModelImpl) {} + + func willWrapGift(_ messageUniqueId: String) -> Bool { false } + + func willShakeGift(_ messageUniqueId: String) -> Bool { false } + + func willUnwrapGift(_ itemViewModel: CVItemViewModelImpl) {} + + func didTapGiftBadge( + _ itemViewModel: CVItemViewModelImpl, + profileBadge: ProfileBadge, + isExpired: Bool, + isRedeemed: Bool, + ) {} + + func prepareMessageDetailForInteractivePresentation(_ itemViewModel: CVItemViewModelImpl) {} + + func beginCellAnimation(maximumDuration: TimeInterval) -> EndCellAnimation { + return {} + } + + var wallpaperBlurProvider: WallpaperBlurProvider? { nil } + + var selectionState: CVSelectionState { CVSelectionState() } + + func didTapPreviouslyVerifiedIdentityChange(_ address: SignalServiceAddress) {} + + func didTapUnverifiedIdentityChange(_ address: SignalServiceAddress) {} + + func didTapCorruptedMessage(_ message: TSErrorMessage) {} + + func didTapSessionRefreshMessage(_ message: TSErrorMessage) {} + + func didTapResendGroupUpdateForErrorMessage(_ errorMessage: TSErrorMessage) {} + + func didTapShowFingerprint(_ address: SignalServiceAddress) {} + + func didTapIndividualCall(_ call: TSCall) {} + + func didTapLearnMoreMissedCallFromBlockedContact(_ call: TSCall) {} + + func didTapGroupCall() {} + + func didTapPendingOutgoingMessage(_ message: TSOutgoingMessage) {} + + func didTapFailedOutgoingMessage(_ message: TSOutgoingMessage) {} + + func didTapGroupMigrationLearnMore() {} + + func didTapGroupInviteLinkPromotion(groupModel: TSGroupModel) {} + + func didTapViewGroupDescription(newGroupDescription: String) {} + + func didTapNameEducation(type: SafetyTipsType) {} + + func didTapShowConversationSettings() {} + + func didTapShowConversationSettingsAndShowMemberRequests() {} + + func didTapBlockRequest( + groupModel: TSGroupModelV2, + requesterName: String, + requesterAci: Aci, + ) {} + + func didTapShowUpgradeAppUI() {} + + func didTapUpdateSystemContact( + _ address: SignalServiceAddress, + newNameComponents: PersonNameComponents, + ) {} + + func didTapPhoneNumberChange(aci: Aci, phoneNumberOld: String, phoneNumberNew: String) {} + + func didTapViewOnceAttachment(_ interaction: TSInteraction) {} + + func didTapViewOnceExpired(_ interaction: TSInteraction) {} + + func didTapContactName(thread: TSContactThread) {} + + func didTapUnknownThreadWarningGroup() {} + func didTapUnknownThreadWarningContact() {} + func didTapDeliveryIssueWarning(_ message: TSErrorMessage) {} + + func didTapActivatePayments() {} + func didTapSendPayment() {} + + func didTapThreadMergeLearnMore(phoneNumber: String) {} + + func didTapReportSpamLearnMore() {} + + func didTapMessageRequestAcceptedOptions() {} + + func didTapJoinCallLinkCall(callLink: CallLink) {} + + func didTapViewVotes(poll: OWSPoll) {} + + func didTapViewPoll(pollInteractionUniqueId: String) {} + + func didTapVoteOnPoll(poll: OWSPoll, optionIndex: UInt32, isUnvote: Bool) {} + + func didTapViewPinnedMessage(pinnedMessageUniqueId: String) {} +} diff --git a/Signal/src/ViewControllers/ThreadSettings/ConversationSettingsViewController+Contents.swift b/Signal/src/ViewControllers/ThreadSettings/ConversationSettingsViewController+Contents.swift index 518b9a0ff2..3a2eb8cd77 100644 --- a/Signal/src/ViewControllers/ThreadSettings/ConversationSettingsViewController+Contents.swift +++ b/Signal/src/ViewControllers/ThreadSettings/ConversationSettingsViewController+Contents.swift @@ -896,8 +896,9 @@ extension ConversationSettingsViewController { "CONVERSATION_SETTINGS_MEMBER_TAG", comment: "Label for 'member label' action in conversation settings view.", ), - actionBlock: { - print("Unimplemented!") + actionBlock: { [weak self] in + let memberLabelViewController = MemberLabelViewController() + self?.present(OWSNavigationController(rootViewController: memberLabelViewController), animated: true) }, ), ) diff --git a/Signal/src/views/MockConversationView.swift b/Signal/src/views/MockConversationView.swift index 839649978a..f748ebf577 100644 --- a/Signal/src/views/MockConversationView.swift +++ b/Signal/src/views/MockConversationView.swift @@ -4,7 +4,7 @@ // import LibSignalClient -import SignalServiceKit +public import SignalServiceKit import SignalUI protocol MockConversationDelegate: AnyObject { @@ -215,10 +215,23 @@ private class MockThread: TSContactThread { } } +public class MockGroupThread: TSGroupThread { + override public var shouldBeSaved: Bool { + return false + } + + override public var uniqueId: String { "MockGroupThread" } + + override public func anyWillInsert(with transaction: DBWriteTransaction) { + // no - op + owsFailDebug("shouldn't save mock thread") + } +} + // MARK: - -private class MockIncomingMessage: TSIncomingMessage { - init(messageBody: ValidatedInlineMessageBody, thread: MockThread) { +public class MockIncomingMessage: TSIncomingMessage { + fileprivate init(messageBody: ValidatedInlineMessageBody, thread: MockThread) { let builder: TSIncomingMessageBuilder = .withDefaultValues( thread: thread, authorAci: thread.contactAddress.aci!, @@ -227,18 +240,27 @@ private class MockIncomingMessage: TSIncomingMessage { super.init(incomingMessageWithBuilder: builder) } - override var shouldBeSaved: Bool { + init(messageBody: ValidatedInlineMessageBody, thread: MockGroupThread, authorAci: Aci) { + let builder: TSIncomingMessageBuilder = .withDefaultValues( + thread: thread, + authorAci: authorAci, + messageBody: messageBody, + ) + super.init(incomingMessageWithBuilder: builder) + } + + override public var shouldBeSaved: Bool { return false } - override func anyWillInsert(with transaction: DBWriteTransaction) { + override public func anyWillInsert(with transaction: DBWriteTransaction) { owsFailDebug("shouldn't save mock message") } } // MARK: - -private class MockOutgoingMessage: TSOutgoingMessage { +public class MockOutgoingMessage: TSOutgoingMessage { init(messageBody: ValidatedInlineMessageBody, thread: TSThread, transaction: DBReadTransaction) { let builder: TSOutgoingMessageBuilder = .withDefaultValues(thread: thread, messageBody: messageBody) super.init( @@ -254,22 +276,22 @@ private class MockOutgoingMessage: TSOutgoingMessage { fatalError("init(coder:) has not been implemented") } - override var shouldBeSaved: Bool { + override public var shouldBeSaved: Bool { return false } - override func anyWillInsert(with transaction: DBWriteTransaction) { + override public func anyWillInsert(with transaction: DBWriteTransaction) { owsFailDebug("shouldn't save mock message") } - override var messageState: TSOutgoingMessageState { .sent } + override public var messageState: TSOutgoingMessageState { .sent } - override func readRecipientAddresses() -> [SignalServiceAddress] { + override public func readRecipientAddresses() -> [SignalServiceAddress] { // makes message appear as read return [MockConversationView.mockAddress] } - override func recipientState(for recipientAddress: SignalServiceAddress) -> TSOutgoingMessageRecipientState? { + override public func recipientState(for recipientAddress: SignalServiceAddress) -> TSOutgoingMessageRecipientState? { return TSOutgoingMessageRecipientState( status: .read, statusTimestamp: Date().ows_millisecondsSince1970, diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 85f2ccf82b..8649b0f42f 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -4987,6 +4987,21 @@ /* Label for an action to add a member label */ "MEMBER_LABEL_ADD" = "Add a Member Label"; +/* Heading shown above the preview of a message bubble with the edited member label. */ +"MEMBER_LABEL_PREVIEW_HEADING" = "Preview"; + +/* Text shown in the preview message bubble when a user is editing their member label. */ +"MEMBER_LABEL_VIEW_MESSAGE_PREVIEW_TEXT" = "Hello!"; + +/* Placeholder text in text field where user can edit their member label. */ +"MEMBER_LABEL_VIEW_PLACEHOLDER_TEXT" = "Add your label"; + +/* Subtitle for a view where users can edit and preview their member label. */ +"MEMBER_LABEL_VIEW_SUBTITLE" = "Set a member label to describe yourself or your role in this group. Member labels are only visible within this group."; + +/* Title for a view where users can edit and preview their member label. */ +"MEMBER_LABEL_VIEW_TITLE" = "Member Label"; + /* Action sheet button title */ "MESSAGE_ACTION_COPY_TEXT" = "Copy Message Text"; diff --git a/Signal/util/TSMessage+RenderableContent.swift b/Signal/util/TSMessage+RenderableContent.swift index 764735ada6..f601ac6461 100644 --- a/Signal/util/TSMessage+RenderableContent.swift +++ b/Signal/util/TSMessage+RenderableContent.swift @@ -9,9 +9,10 @@ extension TSMessage { /// Dangerous to use for uninserted messages; exposed only in the Signal target because most rendering /// uses already-inserted messages, obviating the concern. + /// There are some exceptions to this, such as mock messages we insert for purely UI previewing purposes. public func hasRenderableContent(tx: DBReadTransaction) -> Bool { guard let rowId = self.sqliteRowId else { - owsFailDebug("Checking renderable content for uninserted message!") + owsAssertDebug(((self as? MockIncomingMessage) != nil) || ((self as? MockOutgoingMessage) != nil), "Checking renderable content for uninserted message") return TSMessageBuilder.hasRenderableContent( hasNonemptyBody: body?.nilIfEmpty != nil, hasBodyAttachmentsOrOversizeText: false, diff --git a/SignalServiceKit/Contacts/ThreadAssociatedData.swift b/SignalServiceKit/Contacts/ThreadAssociatedData.swift index a8ccd55175..ff3e5d51a6 100644 --- a/SignalServiceKit/Contacts/ThreadAssociatedData.swift +++ b/SignalServiceKit/Contacts/ThreadAssociatedData.swift @@ -71,7 +71,7 @@ public class ThreadAssociatedData: NSObject, Codable, FetchableRecord, Persistab ) -> ThreadAssociatedData { DependenciesBridge.shared.threadAssociatedDataStore.fetchOrDefault( for: threadUniqueId, - ignoreMissing: ignoreMissing || CurrentAppContext().isRunningTests || threadUniqueId == "MockThread", + ignoreMissing: ignoreMissing || CurrentAppContext().isRunningTests || threadUniqueId == "MockThread" || threadUniqueId == "MockGroupThread", tx: transaction, ) } diff --git a/SignalUI/RecipientPickers/ThreadViewModel.swift b/SignalUI/RecipientPickers/ThreadViewModel.swift index fead880c42..e46d12a037 100644 --- a/SignalUI/RecipientPickers/ThreadViewModel.swift +++ b/SignalUI/RecipientPickers/ThreadViewModel.swift @@ -111,7 +111,7 @@ public class ThreadViewModel: NSObject { if let threadId = thread.grdbId?.int64Value { pinnedMessages = DependenciesBridge.shared.pinnedMessageManager.fetchPinnedMessagesForThread(threadId: threadId, tx: transaction) } else { - owsFailDebug("missing thread Id") + owsAssertDebug(thread.uniqueId == "MockThread" || thread.uniqueId == "MockGroupThread", "missing thread Id") pinnedMessages = [] } }