Edit Send UI
This commit is contained in:
parent
0c5916fb3d
commit
1a02a0eff6
15
Signal/Images.xcassets/send-blue-28.imageset/Contents.json
vendored
Normal file
15
Signal/Images.xcassets/send-blue-28.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "send-blue-28.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "original"
|
||||
}
|
||||
}
|
||||
95
Signal/Images.xcassets/send-blue-28.imageset/send-blue-28.pdf
vendored
Normal file
95
Signal/Images.xcassets/send-blue-28.imageset/send-blue-28.pdf
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
|
||||
0.172549 0.419608 0.929412 scn
|
||||
28.000000 14.000000 m
|
||||
28.000000 6.268013 21.731987 0.000000 14.000000 0.000000 c
|
||||
6.268013 0.000000 0.000000 6.268013 0.000000 14.000000 c
|
||||
0.000000 21.731987 6.268013 28.000000 14.000000 28.000000 c
|
||||
21.731987 28.000000 28.000000 21.731987 28.000000 14.000000 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 7.125000 5.979156 cm
|
||||
1.000000 1.000000 1.000000 scn
|
||||
6.874999 15.583286 m
|
||||
7.118114 15.583286 7.351272 15.486710 7.523180 15.314801 c
|
||||
13.481515 9.356466 l
|
||||
13.839495 8.998486 13.839495 8.418084 13.481515 8.060104 c
|
||||
13.123533 7.702124 12.543133 7.702124 12.185152 8.060104 c
|
||||
7.679847 12.565409 l
|
||||
7.791668 10.999953 l
|
||||
7.791668 0.916669 l
|
||||
7.791668 0.410408 7.381263 0.000003 6.875002 0.000003 c
|
||||
6.368741 0.000003 5.958335 0.410409 5.958335 0.916669 c
|
||||
5.958335 10.999953 l
|
||||
6.070151 12.565408 l
|
||||
1.564848 8.060102 l
|
||||
1.206868 7.702122 0.626466 7.702122 0.268486 8.060102 c
|
||||
-0.089495 8.418082 -0.089495 8.998484 0.268485 9.356464 c
|
||||
6.226818 15.314800 l
|
||||
6.398726 15.486710 6.631884 15.583286 6.874999 15.583286 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1135
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 28.000000 28.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001225 00000 n
|
||||
0000001248 00000 n
|
||||
0000001421 00000 n
|
||||
0000001495 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1554
|
||||
%%EOF
|
||||
@ -13,6 +13,7 @@ protocol MessageActionsDelegate: AnyObject {
|
||||
func messageActionsDeleteItem(_ itemViewModel: CVItemViewModelImpl)
|
||||
func messageActionsSpeakItem(_ itemViewModel: CVItemViewModelImpl)
|
||||
func messageActionsStopSpeakingItem(_ itemViewModel: CVItemViewModelImpl)
|
||||
func messageActionsEditItem(_ itemViewModel: CVItemViewModelImpl)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
@ -96,6 +97,18 @@ struct MessageActionBuilder {
|
||||
})
|
||||
}
|
||||
|
||||
static func editMessage(itemViewModel: CVItemViewModelImpl, delegate: MessageActionsDelegate) -> MessageAction {
|
||||
return MessageAction(
|
||||
.edit,
|
||||
accessibilityLabel: NSLocalizedString("MESSAGE_ACTION_EDIT_MESSAGE", comment: "Action sheet edit message accessibility label"),
|
||||
accessibilityIdentifier: UIView.accessibilityIdentifier(containerName: "message_action", name: "edit_message"),
|
||||
contextMenuTitle: NSLocalizedString("CONTEXT_MENU_EDIT_MESSAGE", comment: "Context menu edit button title"),
|
||||
contextMenuAttributes: [],
|
||||
block: { [weak delegate] (_) in
|
||||
delegate?.messageActionsEditItem(itemViewModel)
|
||||
})
|
||||
}
|
||||
|
||||
static func speakMessage(itemViewModel: CVItemViewModelImpl, delegate: MessageActionsDelegate) -> MessageAction {
|
||||
MessageAction(
|
||||
.speak,
|
||||
@ -151,6 +164,11 @@ class MessageActions: NSObject {
|
||||
let selectAction = MessageActionBuilder.selectMessage(itemViewModel: itemViewModel, delegate: delegate)
|
||||
actions.append(selectAction)
|
||||
|
||||
if itemViewModel.canEditMessage {
|
||||
let editAction = MessageActionBuilder.editMessage(itemViewModel: itemViewModel, delegate: delegate)
|
||||
actions.append(editAction)
|
||||
}
|
||||
|
||||
if itemViewModel.canCopyOrShareOrSpeakText {
|
||||
// If the user started speaking a message and then turns of the "speak selection" OS setting,
|
||||
// we still want to let them turn it off.
|
||||
|
||||
@ -155,6 +155,10 @@ public class CVItemViewModelImpl: CVComponentStateWrapper {
|
||||
|
||||
extension CVItemViewModelImpl {
|
||||
|
||||
var canEditMessage: Bool {
|
||||
return FeatureFlags.editMessageSend
|
||||
}
|
||||
|
||||
var canCopyOrShareOrSpeakText: Bool {
|
||||
guard !isViewOnce else {
|
||||
return false
|
||||
|
||||
@ -172,8 +172,8 @@ public class ConversationInputToolbar: UIView, LinkPreviewViewDraftDelegate, Quo
|
||||
return inputTextView
|
||||
}()
|
||||
|
||||
private lazy var attachmentButton: AttachmentButton = {
|
||||
let button = AttachmentButton()
|
||||
private lazy var addOrCancelButton: AddOrCancelButton = {
|
||||
let button = AddOrCancelButton()
|
||||
button.accessibilityLabel = OWSLocalizedString(
|
||||
"ATTACHMENT_LABEL",
|
||||
comment: "Accessibility label for attaching photos"
|
||||
@ -183,7 +183,7 @@ public class ConversationInputToolbar: UIView, LinkPreviewViewDraftDelegate, Quo
|
||||
comment: "Accessibility hint describing what you can do with the attachment button"
|
||||
)
|
||||
button.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: "attachmentButton")
|
||||
button.addTarget(self, action: #selector(attachmentButtonPressed), for: .touchUpInside)
|
||||
button.addTarget(self, action: #selector(addOrCancelButtonPressed), for: .touchUpInside)
|
||||
button.autoSetDimensions(to: CGSize(square: LayoutMetrics.minToolbarItemHeight))
|
||||
button.setContentHuggingHorizontalHigh()
|
||||
button.setCompressionResistanceHorizontalHigh()
|
||||
@ -222,6 +222,35 @@ public class ConversationInputToolbar: UIView, LinkPreviewViewDraftDelegate, Quo
|
||||
return button
|
||||
}()
|
||||
|
||||
private lazy var editMessageLabelWrapper: UIView = {
|
||||
let view = UIView.container()
|
||||
|
||||
let editIconView = UIImageView(image: UIImage(named: Theme.iconName(.contextMenuEdit)))
|
||||
editIconView.contentMode = .scaleAspectFit
|
||||
editIconView.autoSetDimension(.height, toSize: 16.0)
|
||||
editIconView.setContentHuggingHigh()
|
||||
|
||||
let editLabel = UILabel()
|
||||
editLabel.text = OWSLocalizedString(
|
||||
"INPUT_TOOLBAR_EDIT_MESSAGE_LABEL",
|
||||
comment: "Label at the top of the input text when editing a message"
|
||||
)
|
||||
editLabel.font = UIFont.dynamicTypeSubheadlineClamped.semibold()
|
||||
editLabel.textColor = Theme.primaryTextColor
|
||||
|
||||
let stack = UIStackView(arrangedSubviews: [editIconView, editLabel])
|
||||
stack.axis = .horizontal
|
||||
stack.alignment = .fill
|
||||
|
||||
view.addSubview(stack)
|
||||
stack.autoPinEdgesToSuperviewEdges(with: .init(hMargin: 10, vMargin: 8))
|
||||
|
||||
view.setContentHuggingHorizontalLow()
|
||||
view.setCompressionResistanceHorizontalLow()
|
||||
view.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: "editMessageWrapper")
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var quotedReplyWrapper: UIView = {
|
||||
let view = UIView.container()
|
||||
view.setContentHuggingHorizontalLow()
|
||||
@ -322,11 +351,18 @@ public class ConversationInputToolbar: UIView, LinkPreviewViewDraftDelegate, Quo
|
||||
OWSLogger.info("")
|
||||
}
|
||||
|
||||
editMessageLabelWrapper.isHidden = !shouldShowEditUI
|
||||
|
||||
quotedReplyWrapper.isHidden = quotedReply == nil
|
||||
self.quotedReply = quotedReply
|
||||
|
||||
// Vertical stack of message component views in the center: Link Preview, Reply Quote, Text Input View.
|
||||
let messageContentVStack = UIStackView(arrangedSubviews: [ quotedReplyWrapper, linkPreviewWrapper, inputTextView ])
|
||||
let messageContentVStack = UIStackView(arrangedSubviews: [
|
||||
editMessageLabelWrapper,
|
||||
quotedReplyWrapper,
|
||||
linkPreviewWrapper,
|
||||
inputTextView
|
||||
])
|
||||
messageContentVStack.axis = .vertical
|
||||
messageContentVStack.alignment = .fill
|
||||
messageContentVStack.setContentHuggingHorizontalLow()
|
||||
@ -362,9 +398,9 @@ public class ConversationInputToolbar: UIView, LinkPreviewViewDraftDelegate, Quo
|
||||
// Horizontal Stack: Attachment button, message components, Camera|VoiceNote|Send button.
|
||||
//
|
||||
// + Attachment button: pinned to the bottom left corner.
|
||||
mainPanelView.addSubview(attachmentButton)
|
||||
attachmentButton.autoPinEdge(toSuperviewMargin: .left)
|
||||
attachmentButton.autoPinEdge(toSuperviewEdge: .bottom)
|
||||
mainPanelView.addSubview(addOrCancelButton)
|
||||
addOrCancelButton.autoPinEdge(toSuperviewMargin: .left)
|
||||
addOrCancelButton.autoPinEdge(toSuperviewEdge: .bottom)
|
||||
|
||||
// Camera | Voice Message | Send: pinned to the bottom right corner.
|
||||
mainPanelView.addSubview(rightEdgeControlsView)
|
||||
@ -472,7 +508,7 @@ public class ConversationInputToolbar: UIView, LinkPreviewViewDraftDelegate, Quo
|
||||
voiceMemoContentView.setIsHidden(true, animated: isAnimated)
|
||||
|
||||
// Show Send button instead of Camera and Voice Message buttons only when text input isn't empty.
|
||||
let hasNonWhitespaceTextInput = !inputTextView.trimmedText.isEmpty
|
||||
let hasNonWhitespaceTextInput = !inputTextView.trimmedText.isEmpty || shouldShowEditUI
|
||||
rightEdgeControlsState = hasNonWhitespaceTextInput ? .sendButton : .default
|
||||
}
|
||||
|
||||
@ -485,17 +521,23 @@ public class ConversationInputToolbar: UIView, LinkPreviewViewDraftDelegate, Quo
|
||||
|
||||
// Attachment Button
|
||||
let hideAttachmentButton = isShowingVoiceMemoUI
|
||||
if setAttachmentButtonHidden(hideAttachmentButton, usingAnimator: animator) {
|
||||
if setAddOrCancelButtonHidden(hideAttachmentButton, usingAnimator: animator) {
|
||||
hasLayoutChanged = true
|
||||
}
|
||||
|
||||
// Attachment button has more complex animations and cannot be grouped with the rest.
|
||||
let attachmentButtonAppearance: AttachmentButton.Appearance = desiredKeyboardType == .attachment ? .close : .add
|
||||
attachmentButton.setAppearance(attachmentButtonAppearance, usingAnimator: animator)
|
||||
let addOrCancelButtonAppearance: AddOrCancelButton.Appearance = {
|
||||
if shouldShowEditUI {
|
||||
return .close
|
||||
} else {
|
||||
return desiredKeyboardType == .attachment ? .close : .add
|
||||
}
|
||||
}()
|
||||
addOrCancelButton.setAppearance(addOrCancelButtonAppearance, usingAnimator: animator)
|
||||
|
||||
// Show / hide Sticker or Keyboard buttons inside of the text input field.
|
||||
// Either buttons are only visible if there's no any text input, including whitespace-only.
|
||||
let hideStickerOrKeyboardButton = !inputTextView.untrimmedText.isEmpty || isShowingVoiceMemoUI || quotedReply != nil
|
||||
let hideStickerOrKeyboardButton = shouldShowEditUI || !inputTextView.untrimmedText.isEmpty || isShowingVoiceMemoUI || quotedReply != nil
|
||||
let hideStickerButton = hideStickerOrKeyboardButton || desiredKeyboardType == .sticker
|
||||
let hideKeyboardButton = hideStickerOrKeyboardButton || !hideStickerButton
|
||||
ConversationInputToolbar.setView(stickerButton, hidden: hideStickerButton, usingAnimator: animator)
|
||||
@ -557,14 +599,14 @@ public class ConversationInputToolbar: UIView, LinkPreviewViewDraftDelegate, Quo
|
||||
constant: 16
|
||||
)
|
||||
} else {
|
||||
constraint = messageContentView.leftAnchor.constraint(equalTo: attachmentButton.rightAnchor)
|
||||
constraint = messageContentView.leftAnchor.constraint(equalTo: addOrCancelButton.rightAnchor)
|
||||
}
|
||||
addConstraint(constraint)
|
||||
messageContentViewLeftEdgeConstraint = constraint
|
||||
}
|
||||
|
||||
private func setAttachmentButtonHidden(_ isHidden: Bool, usingAnimator animator: UIViewPropertyAnimator?) -> Bool {
|
||||
guard ConversationInputToolbar.setView(attachmentButton, hidden: isHidden, usingAnimator: animator) else { return false }
|
||||
private func setAddOrCancelButtonHidden(_ isHidden: Bool, usingAnimator animator: UIViewPropertyAnimator?) -> Bool {
|
||||
guard ConversationInputToolbar.setView(addOrCancelButton, hidden: isHidden, usingAnimator: animator) else { return false }
|
||||
updateMessageContentViewLeftEdgeConstraint(isViewHidden: isHidden)
|
||||
return true
|
||||
}
|
||||
@ -617,7 +659,7 @@ public class ConversationInputToolbar: UIView, LinkPreviewViewDraftDelegate, Quo
|
||||
let button = UIButton(type: .system)
|
||||
button.accessibilityLabel = MessageStrings.sendButton
|
||||
button.accessibilityIdentifier = UIView.accessibilityIdentifier(in: self, name: "sendButton")
|
||||
button.setImage(UIImage(imageLiteralResourceName: "send-blue-32"), for: .normal)
|
||||
button.setImage(UIImage(imageLiteralResourceName: "send-blue-28"), for: .normal)
|
||||
button.bounds.size = CGSize(width: 48, height: LayoutMetrics.minToolbarItemHeight)
|
||||
return button
|
||||
}()
|
||||
@ -731,15 +773,15 @@ public class ConversationInputToolbar: UIView, LinkPreviewViewDraftDelegate, Quo
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Attachment Button
|
||||
// MARK: Add/Cancel Button
|
||||
|
||||
private class AttachmentButton: UIButton {
|
||||
private class AddOrCancelButton: UIButton {
|
||||
|
||||
private let roundedCornersBackground: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .init(rgbHex: 0x3B3B3B)
|
||||
view.clipsToBounds = true
|
||||
view.layer.cornerRadius = 8
|
||||
view.layer.cornerRadius = 14
|
||||
view.isUserInteractionEnabled = false
|
||||
return view
|
||||
}()
|
||||
@ -888,10 +930,44 @@ public class ConversationInputToolbar: UIView, LinkPreviewViewDraftDelegate, Quo
|
||||
}
|
||||
|
||||
func clearTextMessage(animated: Bool) {
|
||||
editTarget = nil
|
||||
setMessageBody(nil, animated: animated)
|
||||
inputTextView.undoManager?.removeAllActions()
|
||||
}
|
||||
|
||||
// MARK: Edit Message
|
||||
|
||||
var shouldShowEditUI: Bool { editTarget != nil }
|
||||
|
||||
var editTarget: TSOutgoingMessage? {
|
||||
didSet {
|
||||
let animateChanges = window != nil
|
||||
|
||||
// Show the 'editing' tag
|
||||
if let editTarget = editTarget {
|
||||
|
||||
let body = editTarget.body ?? ""
|
||||
let ranges = editTarget.bodyRanges ?? .empty
|
||||
let messageBody = MessageBody(text: body, ranges: ranges)
|
||||
self.setMessageBody(messageBody, animated: true)
|
||||
|
||||
showEditMessageView(animated: animateChanges)
|
||||
} else {
|
||||
quotedReply = nil
|
||||
hideEditMessageView(animated: animateChanges)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func showEditMessageView(animated: Bool) {
|
||||
toggleMessageComponentVisibility(hide: false, component: editMessageLabelWrapper, animated: animated)
|
||||
}
|
||||
|
||||
private func hideEditMessageView(animated: Bool) {
|
||||
owsAssertDebug(editTarget == nil)
|
||||
toggleMessageComponentVisibility(hide: true, component: editMessageLabelWrapper, animated: animated)
|
||||
}
|
||||
|
||||
// MARK: Quoted Reply
|
||||
|
||||
var quotedReply: QuotedReplyModel? {
|
||||
@ -932,42 +1008,38 @@ public class ConversationInputToolbar: UIView, LinkPreviewViewDraftDelegate, Quo
|
||||
|
||||
updateInputLinkPreview()
|
||||
|
||||
if animated, quotedReplyWrapper.isHidden {
|
||||
isAnimatingHeightChange = true
|
||||
|
||||
UIView.animate(
|
||||
withDuration: ConversationInputToolbar.heightChangeAnimationDuration,
|
||||
animations: {
|
||||
self.quotedReplyWrapper.isHidden = false
|
||||
},
|
||||
completion: { _ in
|
||||
self.isAnimatingHeightChange = false
|
||||
}
|
||||
)
|
||||
} else {
|
||||
quotedReplyWrapper.isHidden = false
|
||||
}
|
||||
toggleMessageComponentVisibility(hide: false, component: quotedReplyWrapper, animated: animated)
|
||||
}
|
||||
|
||||
private func hideQuotedReplyView(animated: Bool) {
|
||||
owsAssertDebug(quotedReply == nil)
|
||||
toggleMessageComponentVisibility(hide: true, component: quotedReplyWrapper, animated: animated) { _ in
|
||||
self.quotedReplyWrapper.removeAllSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
if animated {
|
||||
private func toggleMessageComponentVisibility(
|
||||
hide: Bool,
|
||||
component: UIView,
|
||||
animated: Bool,
|
||||
completion: ((Bool) -> Void)? = nil
|
||||
) {
|
||||
if animated, component.isHidden != hide {
|
||||
isAnimatingHeightChange = true
|
||||
|
||||
UIView.animate(
|
||||
withDuration: ConversationInputToolbar.heightChangeAnimationDuration,
|
||||
animations: {
|
||||
self.quotedReplyWrapper.isHidden = true
|
||||
component.isHidden = hide
|
||||
},
|
||||
completion: { _ in
|
||||
completion: { completed in
|
||||
self.isAnimatingHeightChange = false
|
||||
self.quotedReplyWrapper.removeAllSubviews()
|
||||
completion?(completed)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
quotedReplyWrapper.isHidden = true
|
||||
quotedReplyWrapper.removeAllSubviews()
|
||||
component.isHidden = hide
|
||||
completion?(true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1845,7 +1917,7 @@ public class ConversationInputToolbar: UIView, LinkPreviewViewDraftDelegate, Quo
|
||||
UIView.setAnimationsEnabled(false)
|
||||
|
||||
_ = inputTextView.becomeFirstResponder()
|
||||
inputTextView.resignFirstResponder()
|
||||
_ = inputTextView.resignFirstResponder()
|
||||
|
||||
inputTextView.reloadMentionState()
|
||||
|
||||
@ -1876,7 +1948,7 @@ public class ConversationInputToolbar: UIView, LinkPreviewViewDraftDelegate, Quo
|
||||
}
|
||||
|
||||
func endEditingMessage() {
|
||||
inputTextView.resignFirstResponder()
|
||||
_ = inputTextView.resignFirstResponder()
|
||||
_ = stickerKeyboardIfLoaded?.resignFirstResponder()
|
||||
_ = attachmentKeyboardIfLoaded?.resignFirstResponder()
|
||||
}
|
||||
@ -1926,10 +1998,15 @@ extension ConversationInputToolbar {
|
||||
}
|
||||
|
||||
@objc
|
||||
private func attachmentButtonPressed() {
|
||||
private func addOrCancelButtonPressed() {
|
||||
Logger.verbose("")
|
||||
ImpactHapticFeedback.impactOccurred(style: .light)
|
||||
toggleKeyboardType(.attachment, animated: true)
|
||||
if shouldShowEditUI {
|
||||
editTarget = nil
|
||||
clearTextMessage(animated: true)
|
||||
} else {
|
||||
toggleKeyboardType(.attachment, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
|
||||
@ -106,7 +106,7 @@ extension ConversationViewController: ConversationInputToolbarDelegate {
|
||||
thread: self.thread,
|
||||
quotedReplyModel: inputToolbar.quotedReply,
|
||||
linkPreviewDraft: inputToolbar.linkPreviewDraft,
|
||||
editTarget: nil,
|
||||
editTarget: inputToolbar.editTarget,
|
||||
persistenceCompletionHandler: {
|
||||
AssertIsOnMainThread()
|
||||
self.loadCoordinator.enqueueReload()
|
||||
@ -358,7 +358,7 @@ extension ConversationViewController: ConversationInputToolbarDelegate {
|
||||
mediaAttachments: attachments,
|
||||
thread: self.thread,
|
||||
quotedReplyModel: inputToolbar.quotedReply,
|
||||
editTarget: nil,
|
||||
editTarget: inputToolbar.editTarget,
|
||||
persistenceCompletionHandler: {
|
||||
AssertIsOnMainThread()
|
||||
self.loadCoordinator.enqueueReload()
|
||||
|
||||
@ -115,6 +115,7 @@ extension ConversationViewController: ContextMenuInteractionDelegate {
|
||||
let actionOrder: [MessageAction.MessageActionType] = [
|
||||
.reply,
|
||||
.forward,
|
||||
.edit,
|
||||
.copy,
|
||||
.share,
|
||||
.select,
|
||||
|
||||
@ -8,6 +8,36 @@ import SignalServiceKit
|
||||
import SignalUI
|
||||
|
||||
extension ConversationViewController: MessageActionsDelegate {
|
||||
|
||||
func messageActionsEditItem(_ itemViewModel: CVItemViewModelImpl) {
|
||||
populateMessageEdit(itemViewModel)
|
||||
}
|
||||
|
||||
func populateMessageEdit(_ itemViewModel: CVItemViewModelImpl) {
|
||||
guard let message = itemViewModel.interaction as? TSOutgoingMessage else {
|
||||
return owsFailDebug("Invalid interaction.")
|
||||
}
|
||||
|
||||
// TODO: validate message can still be edited.
|
||||
|
||||
if message.quotedMessage != nil {
|
||||
let load = {
|
||||
Self.databaseStorage.read { transaction in
|
||||
QuotedReplyModel(message: message, transaction: transaction)
|
||||
}
|
||||
}
|
||||
guard let quotedReply = load() else {
|
||||
owsFailDebug("Could not build quoted reply.")
|
||||
return
|
||||
}
|
||||
|
||||
inputToolbar?.quotedReply = quotedReply
|
||||
}
|
||||
|
||||
inputToolbar?.editTarget = message
|
||||
inputToolbar?.beginEditingMessage()
|
||||
}
|
||||
|
||||
func messageActionsShowDetailsForItem(_ itemViewModel: CVItemViewModelImpl) {
|
||||
showDetailView(itemViewModel)
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ public class MessageAction: NSObject {
|
||||
case select
|
||||
case speak
|
||||
case stopSpeaking
|
||||
case edit
|
||||
}
|
||||
|
||||
let actionType: MessageActionType
|
||||
@ -63,6 +64,8 @@ public class MessageAction: NSObject {
|
||||
return .contextMenuSpeak
|
||||
case .stopSpeaking:
|
||||
return .contextMenuStopSpeaking
|
||||
case .edit:
|
||||
return .contextMenuEdit
|
||||
}
|
||||
}()
|
||||
return Theme.iconImage(icon)
|
||||
|
||||
@ -1189,6 +1189,9 @@
|
||||
/* Context menu button title */
|
||||
"CONTEXT_MENU_DETAILS" = "Info";
|
||||
|
||||
/* Context menu edit button title */
|
||||
"CONTEXT_MENU_EDIT_MESSAGE" = "Edit";
|
||||
|
||||
/* Context menu button title */
|
||||
"CONTEXT_MENU_FORWARD_MESSAGE" = "Forward";
|
||||
|
||||
@ -3313,6 +3316,9 @@
|
||||
/* Shown in inbox and conversation when a user joins Signal, embeds the new user's {{contact name}} */
|
||||
"INFO_MESSAGE_USER_JOINED_SIGNAL_BODY_FORMAT" = "%@ is on Signal!";
|
||||
|
||||
/* Label at the top of the input text when editing a message */
|
||||
"INPUT_TOOLBAR_EDIT_MESSAGE_LABEL" = "Edit message";
|
||||
|
||||
/* accessibility label for the button which shows the regular keyboard instead of sticker picker */
|
||||
"INPUT_TOOLBAR_KEYBOARD_BUTTON_ACCESSIBILITY_LABEL" = "Keyboard";
|
||||
|
||||
@ -3598,6 +3604,9 @@
|
||||
/* Action sheet button title */
|
||||
"MESSAGE_ACTION_DETAILS" = "More Info";
|
||||
|
||||
/* Action sheet edit message accessibility label */
|
||||
"MESSAGE_ACTION_EDIT_MESSAGE" = "Edit Message";
|
||||
|
||||
/* Label for button to compose a new email. */
|
||||
"MESSAGE_ACTION_EMAIL_NEW_MAIL_MESSAGE" = "New Email Message";
|
||||
|
||||
|
||||
@ -128,6 +128,8 @@ public class FeatureFlags: BaseFlags {
|
||||
/// If true, _only_ aci safety numbers will be displayed, and e164 safety numbers will not
|
||||
/// be displayed.
|
||||
public static let onlyAciSafetyNumbers = false
|
||||
|
||||
public static let editMessageSend = build.includes(.dev)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
Loading…
Reference in New Issue
Block a user