Fixes #2765 - Use the keyboard up arrow to edit the last message, use escape to cancel editing or replying.

This commit is contained in:
Stefan Ceriu
2024-05-07 12:56:51 +03:00
committed by Stefan Ceriu
parent 3017100e5e
commit e05e664f6d
8 changed files with 91 additions and 28 deletions
@@ -197,7 +197,10 @@ private class GlobalSearchTextField: UITextField {
} }
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) { override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard let key = presses.first?.key else { return } guard let key = presses.first?.key else {
super.pressesBegan(presses, with: event)
return
}
if keyPressHandler(key.keyCode) { if keyPressHandler(key.keyCode) {
return return
@@ -32,6 +32,7 @@ enum ComposerToolbarVoiceMessageAction {
enum ComposerToolbarViewModelAction { enum ComposerToolbarViewModelAction {
case sendMessage(plain: String, html: String?, mode: RoomScreenComposerMode, intentionalMentions: IntentionalMentions) case sendMessage(plain: String, html: String?, mode: RoomScreenComposerMode, intentionalMentions: IntentionalMentions)
case editLastMessage
case attach(ComposerAttachmentType) case attach(ComposerAttachmentType)
case handlePasteOrDrop(provider: NSItemProvider) case handlePasteOrDrop(provider: NSItemProvider)
@@ -47,6 +48,7 @@ enum ComposerToolbarViewModelAction {
enum ComposerToolbarViewAction { enum ComposerToolbarViewAction {
case composerAppeared case composerAppeared
case sendMessage case sendMessage
case editLastMessage
case cancelReply case cancelReply
case cancelEdit case cancelEdit
case attach(ComposerAttachmentType) case attach(ComposerAttachmentType)
@@ -135,6 +135,8 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
sendPlainComposerText() sendPlainComposerText()
} }
} }
case .editLastMessage:
actionsSubject.send(.editLastMessage)
case .cancelReply: case .cancelReply:
set(mode: .default) set(mode: .default)
case .cancelEdit: case .cancelEdit:
@@ -161,12 +161,19 @@ struct ComposerToolbar: View {
showResizeGrabber: context.viewState.bindings.composerActionsEnabled, showResizeGrabber: context.viewState.bindings.composerActionsEnabled,
isExpanded: $context.composerExpanded) { isExpanded: $context.composerExpanded) {
context.send(viewAction: .sendMessage) context.send(viewAction: .sendMessage)
} editAction: {
context.send(viewAction: .editLastMessage)
} pasteAction: { provider in } pasteAction: { provider in
context.send(viewAction: .handlePasteOrDrop(provider: provider)) context.send(viewAction: .handlePasteOrDrop(provider: provider))
} replyCancellationAction: { } cancellationAction: {
context.send(viewAction: .cancelReply) switch context.viewState.composerMode {
} editCancellationAction: { case .edit:
context.send(viewAction: .cancelEdit) context.send(viewAction: .cancelEdit)
case .reply:
context.send(viewAction: .cancelReply)
default:
break
}
} onAppearAction: { } onAppearAction: {
context.send(viewAction: .composerAppeared) context.send(viewAction: .composerAppeared)
} }
@@ -18,7 +18,7 @@ import Compound
import SwiftUI import SwiftUI
import WysiwygComposer import WysiwygComposer
typealias EnterKeyHandler = () -> Void typealias GenericKeyHandler = (_ key: UIKeyboardHIDUsage) -> Void
typealias PasteHandler = (NSItemProvider) -> Void typealias PasteHandler = (NSItemProvider) -> Void
struct MessageComposer: View { struct MessageComposer: View {
@@ -27,10 +27,10 @@ struct MessageComposer: View {
let mode: RoomScreenComposerMode let mode: RoomScreenComposerMode
let showResizeGrabber: Bool let showResizeGrabber: Bool
@Binding var isExpanded: Bool @Binding var isExpanded: Bool
let sendAction: EnterKeyHandler let sendAction: () -> Void
let editAction: () -> Void
let pasteAction: PasteHandler let pasteAction: PasteHandler
let replyCancellationAction: () -> Void let cancellationAction: () -> Void
let editCancellationAction: () -> Void
let onAppearAction: () -> Void let onAppearAction: () -> Void
@State private var composerTranslation: CGFloat = 0 @State private var composerTranslation: CGFloat = 0
@@ -86,7 +86,7 @@ struct MessageComposer: View {
MessageComposerTextField(placeholder: L10n.richTextEditorComposerPlaceholder, MessageComposerTextField(placeholder: L10n.richTextEditorComposerPlaceholder,
text: $plainComposerText, text: $plainComposerText,
maxHeight: 300, maxHeight: 300,
enterKeyHandler: sendAction, keyHandler: { handleKeyPress($0) },
pasteHandler: pasteAction) pasteHandler: pasteAction)
.tint(.compound.iconAccentTertiary) .tint(.compound.iconAccentTertiary)
.padding(.vertical, 10) .padding(.vertical, 10)
@@ -103,9 +103,9 @@ struct MessageComposer: View {
private var header: some View { private var header: some View {
switch mode { switch mode {
case .reply(_, let replyDetails, _): case .reply(_, let replyDetails, _):
MessageComposerReplyHeader(replyDetails: replyDetails, action: replyCancellationAction) MessageComposerReplyHeader(replyDetails: replyDetails, action: cancellationAction)
case .edit: case .edit:
MessageComposerEditHeader(action: editCancellationAction) MessageComposerEditHeader(action: cancellationAction)
case .recordVoiceMessage, .previewVoiceMessage, .default: case .recordVoiceMessage, .previewVoiceMessage, .default:
EmptyView() EmptyView()
} }
@@ -135,6 +135,19 @@ struct MessageComposer: View {
} }
} }
} }
private func handleKeyPress(_ key: UIKeyboardHIDUsage) {
switch key {
case .keyboardReturnOrEnter:
sendAction()
case .keyboardUpArrow:
editAction()
case .keyboardEscape:
cancellationAction()
default:
break
}
}
} }
private struct MessageComposerReplyHeader: View { private struct MessageComposerReplyHeader: View {
@@ -244,9 +257,9 @@ struct MessageComposer_Previews: PreviewProvider, TestablePreview {
showResizeGrabber: false, showResizeGrabber: false,
isExpanded: .constant(false), isExpanded: .constant(false),
sendAction: { }, sendAction: { },
editAction: { },
pasteAction: { _ in }, pasteAction: { _ in },
replyCancellationAction: { }, cancellationAction: { },
editCancellationAction: { },
onAppearAction: { viewModel.setup() }) onAppearAction: { viewModel.setup() })
} }
@@ -20,13 +20,13 @@ struct MessageComposerTextField: View {
@Binding var text: NSAttributedString @Binding var text: NSAttributedString
let maxHeight: CGFloat let maxHeight: CGFloat
let enterKeyHandler: EnterKeyHandler let keyHandler: GenericKeyHandler
let pasteHandler: PasteHandler let pasteHandler: PasteHandler
var body: some View { var body: some View {
UITextViewWrapper(text: $text, UITextViewWrapper(text: $text,
maxHeight: maxHeight, maxHeight: maxHeight,
enterKeyHandler: enterKeyHandler, keyHandler: keyHandler,
pasteHandler: pasteHandler) pasteHandler: pasteHandler)
.accessibilityLabel(placeholder) .accessibilityLabel(placeholder)
.background(placeholderView, alignment: .topLeading) .background(placeholderView, alignment: .topLeading)
@@ -49,7 +49,7 @@ private struct UITextViewWrapper: UIViewRepresentable {
let maxHeight: CGFloat let maxHeight: CGFloat
let enterKeyHandler: EnterKeyHandler let keyHandler: GenericKeyHandler
let pasteHandler: PasteHandler let pasteHandler: PasteHandler
private let font = UIFont.preferredFont(forTextStyle: .body) private let font = UIFont.preferredFont(forTextStyle: .body)
@@ -115,7 +115,7 @@ private struct UITextViewWrapper: UIViewRepresentable {
func makeCoordinator() -> Coordinator { func makeCoordinator() -> Coordinator {
Coordinator(text: $text, Coordinator(text: $text,
maxHeight: maxHeight, maxHeight: maxHeight,
enterKeyHandler: enterKeyHandler, keyHandler: keyHandler,
pasteHandler: pasteHandler) pasteHandler: pasteHandler)
} }
@@ -124,16 +124,16 @@ private struct UITextViewWrapper: UIViewRepresentable {
private let maxHeight: CGFloat private let maxHeight: CGFloat
private let enterKeyHandler: EnterKeyHandler private let keyHandler: GenericKeyHandler
private let pasteHandler: PasteHandler private let pasteHandler: PasteHandler
init(text: Binding<NSAttributedString>, init(text: Binding<NSAttributedString>,
maxHeight: CGFloat, maxHeight: CGFloat,
enterKeyHandler: @escaping EnterKeyHandler, keyHandler: @escaping GenericKeyHandler,
pasteHandler: @escaping PasteHandler) { pasteHandler: @escaping PasteHandler) {
self.text = text self.text = text
self.maxHeight = maxHeight self.maxHeight = maxHeight
self.enterKeyHandler = enterKeyHandler self.keyHandler = keyHandler
self.pasteHandler = pasteHandler self.pasteHandler = pasteHandler
} }
@@ -141,8 +141,8 @@ private struct UITextViewWrapper: UIViewRepresentable {
text.wrappedValue = textView.attributedText text.wrappedValue = textView.attributedText
} }
func textViewDidReceiveEnterKeyPress(_ textView: UITextView) { func textViewDidReceiveKeyPress(_ textView: UITextView, key: UIKeyboardHIDUsage) {
enterKeyHandler() keyHandler(key)
} }
func textViewDidReceiveShiftEnterKeyPress(_ textView: UITextView) { func textViewDidReceiveShiftEnterKeyPress(_ textView: UITextView) {
@@ -157,7 +157,7 @@ private struct UITextViewWrapper: UIViewRepresentable {
private protocol ElementTextViewDelegate: AnyObject { private protocol ElementTextViewDelegate: AnyObject {
func textViewDidReceiveShiftEnterKeyPress(_ textView: UITextView) func textViewDidReceiveShiftEnterKeyPress(_ textView: UITextView)
func textViewDidReceiveEnterKeyPress(_ textView: UITextView) func textViewDidReceiveKeyPress(_ textView: UITextView, key: UIKeyboardHIDUsage)
func textView(_ textView: UITextView, didReceivePasteWith provider: NSItemProvider) func textView(_ textView: UITextView, didReceivePasteWith provider: NSItemProvider)
} }
@@ -178,7 +178,26 @@ private class ElementTextView: UITextView, PillAttachmentViewProviderDelegate {
} }
@objc func enterKeyPressed(sender: UIKeyCommand) { @objc func enterKeyPressed(sender: UIKeyCommand) {
elementDelegate?.textViewDidReceiveEnterKeyPress(self) elementDelegate?.textViewDidReceiveKeyPress(self, key: .keyboardReturnOrEnter)
}
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard let key = presses.first?.key else {
super.pressesBegan(presses, with: event)
return
}
if key.keyCode == .keyboardUpArrow, selectedRange.location == 0 {
elementDelegate?.textViewDidReceiveKeyPress(self, key: key.keyCode)
return
}
if key.keyCode == .keyboardEscape {
elementDelegate?.textViewDidReceiveKeyPress(self, key: key.keyCode)
return
}
super.pressesBegan(presses, with: event)
} }
// Pasting support // Pasting support
@@ -254,7 +273,7 @@ struct MessageComposerTextField_Previews: PreviewProvider, TestablePreview {
MessageComposerTextField(placeholder: "Placeholder", MessageComposerTextField(placeholder: "Placeholder",
text: $text, text: $text,
maxHeight: 300, maxHeight: 300,
enterKeyHandler: { }, keyHandler: { _ in },
pasteHandler: { _ in }) pasteHandler: { _ in })
} }
} }
@@ -206,6 +206,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
mode: mode, mode: mode,
intentionalMentions: intentionalMentions) intentionalMentions: intentionalMentions)
} }
case .editLastMessage:
editLastMessage()
case .attach(let attachment): case .attach(let attachment):
attach(attachment) attach(attachment)
case .handlePasteOrDrop(let provider): case .handlePasteOrDrop(let provider):
@@ -264,6 +266,20 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
} }
} }
private func editLastMessage() {
guard let item = timelineController.timelineItems.reversed().first(where: {
guard let item = $0 as? EventBasedMessageTimelineItemProtocol else {
return false
}
return item.sender.id == roomProxy.ownUserID && item.isEditable
}) else {
return
}
roomScreenInteractionHandler.processTimelineItemMenuAction(.edit, itemID: item.id)
}
private func attach(_ attachment: ComposerAttachmentType) { private func attach(_ attachment: ComposerAttachmentType) {
switch attachment { switch attachment {
case .camera: case .camera:
+1
View File
@@ -0,0 +1 @@
Use the keyboard up arrow to edit the last message, use escape to cancel editing or replying.