diff --git a/ElementX/Sources/Screens/ComposerToolbar/View/ComposerToolbar.swift b/ElementX/Sources/Screens/ComposerToolbar/View/ComposerToolbar.swift index 1560e4cfd..7a018ab3d 100644 --- a/ElementX/Sources/Screens/ComposerToolbar/View/ComposerToolbar.swift +++ b/ElementX/Sources/Screens/ComposerToolbar/View/ComposerToolbar.swift @@ -18,6 +18,7 @@ import SwiftUI struct ComposerToolbar: View { @ObservedObject var context: ComposerToolbarViewModel.Context + @FocusState private var composerFocused: Bool var body: some View { HStack(alignment: .bottom, spacing: 10) { @@ -26,11 +27,17 @@ struct ComposerToolbar: View { messageComposer .environmentObject(context) } + .onChange(of: context.composerFocused) { newValue in + composerFocused = newValue + } + .onChange(of: composerFocused) { newValue in + context.composerFocused = newValue + } } - + private var messageComposer: some View { MessageComposer(text: $context.composerText, - focused: $context.composerFocused, + focused: $composerFocused, sendingDisabled: context.viewState.sendButtonDisabled, mode: context.viewState.composerMode) { sendMessage() diff --git a/ElementX/Sources/Screens/ComposerToolbar/View/MessageComposer.swift b/ElementX/Sources/Screens/ComposerToolbar/View/MessageComposer.swift index 292cdb40d..5f1c7ec09 100644 --- a/ElementX/Sources/Screens/ComposerToolbar/View/MessageComposer.swift +++ b/ElementX/Sources/Screens/ComposerToolbar/View/MessageComposer.swift @@ -18,7 +18,7 @@ import SwiftUI struct MessageComposer: View { @Binding var text: String - @Binding var focused: Bool + var focused: FocusState.Binding let sendingDisabled: Bool let mode: RoomScreenComposerMode @@ -37,7 +37,7 @@ struct MessageComposer: View { HStack(alignment: .bottom) { MessageComposerTextField(placeholder: L10n.richTextEditorComposerPlaceholder, text: $text, - focused: $focused, + focused: focused, isMultiline: $isMultiline, maxHeight: 300, enterKeyHandler: sendAction, @@ -70,7 +70,7 @@ struct MessageComposer: View { .fill(Color.compound.bgSubtleSecondary) roundedRectangle .stroke(Color.compound._borderTextFieldFocused, lineWidth: 1) - .opacity(focused ? 1 : 0) + .opacity(focused.wrappedValue ? 1 : 0) } } // Explicitly disable all animations to fix weirdness with the header immediately @@ -177,7 +177,7 @@ struct MessageComposer_Previews: PreviewProvider { static var previews: some View { VStack { MessageComposer(text: .constant(""), - focused: .constant(false), + focused: FocusState().projectedValue, sendingDisabled: true, mode: .default, sendAction: { }, @@ -186,7 +186,7 @@ struct MessageComposer_Previews: PreviewProvider { editCancellationAction: { }) MessageComposer(text: .constant("This is a short message."), - focused: .constant(false), + focused: FocusState().projectedValue, sendingDisabled: false, mode: .default, sendAction: { }, @@ -195,7 +195,7 @@ struct MessageComposer_Previews: PreviewProvider { editCancellationAction: { }) MessageComposer(text: .constant("This is a very long message that will wrap to 2 lines on an iPhone 14."), - focused: .constant(false), + focused: FocusState().projectedValue, sendingDisabled: false, mode: .default, sendAction: { }, @@ -204,7 +204,7 @@ struct MessageComposer_Previews: PreviewProvider { editCancellationAction: { }) MessageComposer(text: .constant("This is an even longer message that will wrap to 3 lines on an iPhone 14, just to see the difference it makes."), - focused: .constant(false), + focused: FocusState().projectedValue, sendingDisabled: false, mode: .default, sendAction: { }, @@ -213,7 +213,7 @@ struct MessageComposer_Previews: PreviewProvider { editCancellationAction: { }) MessageComposer(text: .constant("Some message"), - focused: .constant(false), + focused: FocusState().projectedValue, sendingDisabled: false, mode: .edit(originalItemId: .random), sendAction: { }, @@ -222,7 +222,7 @@ struct MessageComposer_Previews: PreviewProvider { editCancellationAction: { }) MessageComposer(text: .constant(""), - focused: .constant(false), + focused: FocusState().projectedValue, sendingDisabled: false, mode: .reply(itemID: .random, replyDetails: .loaded(sender: .init(id: "Kirk"), @@ -254,7 +254,7 @@ struct MessageComposer_Previews: PreviewProvider { ForEach(replyTypes, id: \.self) { replyDetails in MessageComposer(text: .constant(""), - focused: .constant(false), + focused: FocusState().projectedValue, sendingDisabled: false, mode: .reply(itemID: .random, replyDetails: replyDetails), diff --git a/ElementX/Sources/Screens/ComposerToolbar/View/MessageComposerTextField.swift b/ElementX/Sources/Screens/ComposerToolbar/View/MessageComposerTextField.swift index 7d57e5fe9..74bcfeefb 100644 --- a/ElementX/Sources/Screens/ComposerToolbar/View/MessageComposerTextField.swift +++ b/ElementX/Sources/Screens/ComposerToolbar/View/MessageComposerTextField.swift @@ -22,7 +22,7 @@ typealias PasteHandler = (NSItemProvider) -> Void struct MessageComposerTextField: View { let placeholder: String @Binding var text: String - @Binding var focused: Bool + var focused: FocusState.Binding @Binding var isMultiline: Bool let maxHeight: CGFloat @@ -31,13 +31,13 @@ struct MessageComposerTextField: View { var body: some View { UITextViewWrapper(text: $text, - focused: $focused, isMultiline: $isMultiline, maxHeight: maxHeight, enterKeyHandler: enterKeyHandler, pasteHandler: pasteHandler) .accessibilityLabel(placeholder) .background(placeholderView, alignment: .topLeading) + .focused(focused) } @ViewBuilder @@ -54,7 +54,6 @@ private struct UITextViewWrapper: UIViewRepresentable { typealias UIViewType = UITextView @Binding var text: String - @Binding var focused: Bool @Binding var isMultiline: Bool let maxHeight: CGFloat @@ -110,17 +109,10 @@ private struct UITextViewWrapper: UIViewRepresentable { } } } - - if !focused, textView.isFirstResponder { - textView.resignFirstResponder() - } else if focused, textView.window != nil, !textView.isFirstResponder { - textView.becomeFirstResponder() - } } func makeCoordinator() -> Coordinator { Coordinator(text: $text, - focused: $focused, maxHeight: maxHeight, enterKeyHandler: enterKeyHandler, pasteHandler: pasteHandler) @@ -128,7 +120,6 @@ private struct UITextViewWrapper: UIViewRepresentable { final class Coordinator: NSObject, UITextViewDelegate, ElementTextViewDelegate { private var text: Binding - private var focused: Binding private let maxHeight: CGFloat @@ -136,12 +127,10 @@ private struct UITextViewWrapper: UIViewRepresentable { private let pasteHandler: PasteHandler init(text: Binding, - focused: Binding, maxHeight: CGFloat, enterKeyHandler: @escaping EnterKeyHandler, pasteHandler: @escaping PasteHandler) { self.text = text - self.focused = focused self.maxHeight = maxHeight self.enterKeyHandler = enterKeyHandler self.pasteHandler = pasteHandler @@ -151,18 +140,6 @@ private struct UITextViewWrapper: UIViewRepresentable { text.wrappedValue = textView.text } - func textViewDidBeginEditing(_ textView: UITextView) { - DispatchQueue.main.async { - self.focused.wrappedValue = true - } - } - - func textViewDidEndEditing(_ textView: UITextView) { - DispatchQueue.main.async { - self.focused.wrappedValue = false - } - } - func textViewDidReceiveEnterKeyPress(_ textView: UITextView) { enterKeyHandler() } @@ -256,19 +233,17 @@ struct MessageComposerTextField_Previews: PreviewProvider { struct PreviewWrapper: View { @State var text: String - @State var focused: Bool @State var isMultiline: Bool init(text: String) { _text = .init(initialValue: text) - _focused = .init(initialValue: false) _isMultiline = .init(initialValue: false) } var body: some View { MessageComposerTextField(placeholder: "Placeholder", text: $text, - focused: $focused, + focused: FocusState().projectedValue, isMultiline: $isMultiline, maxHeight: 300, enterKeyHandler: { }, diff --git a/UnitTests/Sources/LoggingTests.swift b/UnitTests/Sources/LoggingTests.swift index a1e79884a..e714d44e4 100644 --- a/UnitTests/Sources/LoggingTests.swift +++ b/UnitTests/Sources/LoggingTests.swift @@ -227,7 +227,8 @@ class LoggingTests: XCTestCase { lastMessage: AttributedString(lastMessage), lastMessageFormattedTimestamp: "Now", unreadNotificationCount: 0, - canonicalAlias: nil) + canonicalAlias: nil, + inviter: nil) // When logging that value XCTAssert(MXLogger.logFiles.isEmpty)