mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
Fixes #2765 - Use the keyboard up arrow to edit the last message, use escape to cancel editing or replying.
This commit is contained in:
committed by
Stefan Ceriu
parent
3017100e5e
commit
e05e664f6d
@@ -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() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+33
-14
@@ -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,10 +141,10 @@ 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) {
|
||||||
textView.insertText("\n")
|
textView.insertText("\n")
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,9 +176,28 @@ private class ElementTextView: UITextView, PillAttachmentViewProviderDelegate {
|
|||||||
@objc func shiftEnterKeyPressed(sender: UIKeyCommand) {
|
@objc func shiftEnterKeyPressed(sender: UIKeyCommand) {
|
||||||
elementDelegate?.textViewDidReceiveShiftEnterKeyPress(self)
|
elementDelegate?.textViewDidReceiveShiftEnterKeyPress(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@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:
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Use the keyboard up arrow to edit the last message, use escape to cancel editing or replying.
|
||||||
Reference in New Issue
Block a user