Fixes #1282 - Switch composer text vield to a @FocusState

This commit is contained in:
Stefan Ceriu 2023-08-18 13:40:15 +03:00 committed by Stefan Ceriu
parent 03f1ad1dce
commit a14c995686
4 changed files with 24 additions and 41 deletions

View File

@ -18,6 +18,7 @@ import SwiftUI
struct ComposerToolbar: View { struct ComposerToolbar: View {
@ObservedObject var context: ComposerToolbarViewModel.Context @ObservedObject var context: ComposerToolbarViewModel.Context
@FocusState private var composerFocused: Bool
var body: some View { var body: some View {
HStack(alignment: .bottom, spacing: 10) { HStack(alignment: .bottom, spacing: 10) {
@ -26,11 +27,17 @@ struct ComposerToolbar: View {
messageComposer messageComposer
.environmentObject(context) .environmentObject(context)
} }
.onChange(of: context.composerFocused) { newValue in
composerFocused = newValue
}
.onChange(of: composerFocused) { newValue in
context.composerFocused = newValue
}
} }
private var messageComposer: some View { private var messageComposer: some View {
MessageComposer(text: $context.composerText, MessageComposer(text: $context.composerText,
focused: $context.composerFocused, focused: $composerFocused,
sendingDisabled: context.viewState.sendButtonDisabled, sendingDisabled: context.viewState.sendButtonDisabled,
mode: context.viewState.composerMode) { mode: context.viewState.composerMode) {
sendMessage() sendMessage()

View File

@ -18,7 +18,7 @@ import SwiftUI
struct MessageComposer: View { struct MessageComposer: View {
@Binding var text: String @Binding var text: String
@Binding var focused: Bool var focused: FocusState<Bool>.Binding
let sendingDisabled: Bool let sendingDisabled: Bool
let mode: RoomScreenComposerMode let mode: RoomScreenComposerMode
@ -37,7 +37,7 @@ struct MessageComposer: View {
HStack(alignment: .bottom) { HStack(alignment: .bottom) {
MessageComposerTextField(placeholder: L10n.richTextEditorComposerPlaceholder, MessageComposerTextField(placeholder: L10n.richTextEditorComposerPlaceholder,
text: $text, text: $text,
focused: $focused, focused: focused,
isMultiline: $isMultiline, isMultiline: $isMultiline,
maxHeight: 300, maxHeight: 300,
enterKeyHandler: sendAction, enterKeyHandler: sendAction,
@ -70,7 +70,7 @@ struct MessageComposer: View {
.fill(Color.compound.bgSubtleSecondary) .fill(Color.compound.bgSubtleSecondary)
roundedRectangle roundedRectangle
.stroke(Color.compound._borderTextFieldFocused, lineWidth: 1) .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 // Explicitly disable all animations to fix weirdness with the header immediately
@ -177,7 +177,7 @@ struct MessageComposer_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
VStack { VStack {
MessageComposer(text: .constant(""), MessageComposer(text: .constant(""),
focused: .constant(false), focused: FocusState<Bool>().projectedValue,
sendingDisabled: true, sendingDisabled: true,
mode: .default, mode: .default,
sendAction: { }, sendAction: { },
@ -186,7 +186,7 @@ struct MessageComposer_Previews: PreviewProvider {
editCancellationAction: { }) editCancellationAction: { })
MessageComposer(text: .constant("This is a short message."), MessageComposer(text: .constant("This is a short message."),
focused: .constant(false), focused: FocusState<Bool>().projectedValue,
sendingDisabled: false, sendingDisabled: false,
mode: .default, mode: .default,
sendAction: { }, sendAction: { },
@ -195,7 +195,7 @@ struct MessageComposer_Previews: PreviewProvider {
editCancellationAction: { }) editCancellationAction: { })
MessageComposer(text: .constant("This is a very long message that will wrap to 2 lines on an iPhone 14."), MessageComposer(text: .constant("This is a very long message that will wrap to 2 lines on an iPhone 14."),
focused: .constant(false), focused: FocusState<Bool>().projectedValue,
sendingDisabled: false, sendingDisabled: false,
mode: .default, mode: .default,
sendAction: { }, sendAction: { },
@ -204,7 +204,7 @@ struct MessageComposer_Previews: PreviewProvider {
editCancellationAction: { }) 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."), 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<Bool>().projectedValue,
sendingDisabled: false, sendingDisabled: false,
mode: .default, mode: .default,
sendAction: { }, sendAction: { },
@ -213,7 +213,7 @@ struct MessageComposer_Previews: PreviewProvider {
editCancellationAction: { }) editCancellationAction: { })
MessageComposer(text: .constant("Some message"), MessageComposer(text: .constant("Some message"),
focused: .constant(false), focused: FocusState<Bool>().projectedValue,
sendingDisabled: false, sendingDisabled: false,
mode: .edit(originalItemId: .random), mode: .edit(originalItemId: .random),
sendAction: { }, sendAction: { },
@ -222,7 +222,7 @@ struct MessageComposer_Previews: PreviewProvider {
editCancellationAction: { }) editCancellationAction: { })
MessageComposer(text: .constant(""), MessageComposer(text: .constant(""),
focused: .constant(false), focused: FocusState<Bool>().projectedValue,
sendingDisabled: false, sendingDisabled: false,
mode: .reply(itemID: .random, mode: .reply(itemID: .random,
replyDetails: .loaded(sender: .init(id: "Kirk"), replyDetails: .loaded(sender: .init(id: "Kirk"),
@ -254,7 +254,7 @@ struct MessageComposer_Previews: PreviewProvider {
ForEach(replyTypes, id: \.self) { replyDetails in ForEach(replyTypes, id: \.self) { replyDetails in
MessageComposer(text: .constant(""), MessageComposer(text: .constant(""),
focused: .constant(false), focused: FocusState<Bool>().projectedValue,
sendingDisabled: false, sendingDisabled: false,
mode: .reply(itemID: .random, mode: .reply(itemID: .random,
replyDetails: replyDetails), replyDetails: replyDetails),

View File

@ -22,7 +22,7 @@ typealias PasteHandler = (NSItemProvider) -> Void
struct MessageComposerTextField: View { struct MessageComposerTextField: View {
let placeholder: String let placeholder: String
@Binding var text: String @Binding var text: String
@Binding var focused: Bool var focused: FocusState<Bool>.Binding
@Binding var isMultiline: Bool @Binding var isMultiline: Bool
let maxHeight: CGFloat let maxHeight: CGFloat
@ -31,13 +31,13 @@ struct MessageComposerTextField: View {
var body: some View { var body: some View {
UITextViewWrapper(text: $text, UITextViewWrapper(text: $text,
focused: $focused,
isMultiline: $isMultiline, isMultiline: $isMultiline,
maxHeight: maxHeight, maxHeight: maxHeight,
enterKeyHandler: enterKeyHandler, enterKeyHandler: enterKeyHandler,
pasteHandler: pasteHandler) pasteHandler: pasteHandler)
.accessibilityLabel(placeholder) .accessibilityLabel(placeholder)
.background(placeholderView, alignment: .topLeading) .background(placeholderView, alignment: .topLeading)
.focused(focused)
} }
@ViewBuilder @ViewBuilder
@ -54,7 +54,6 @@ private struct UITextViewWrapper: UIViewRepresentable {
typealias UIViewType = UITextView typealias UIViewType = UITextView
@Binding var text: String @Binding var text: String
@Binding var focused: Bool
@Binding var isMultiline: Bool @Binding var isMultiline: Bool
let maxHeight: CGFloat 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 { func makeCoordinator() -> Coordinator {
Coordinator(text: $text, Coordinator(text: $text,
focused: $focused,
maxHeight: maxHeight, maxHeight: maxHeight,
enterKeyHandler: enterKeyHandler, enterKeyHandler: enterKeyHandler,
pasteHandler: pasteHandler) pasteHandler: pasteHandler)
@ -128,7 +120,6 @@ private struct UITextViewWrapper: UIViewRepresentable {
final class Coordinator: NSObject, UITextViewDelegate, ElementTextViewDelegate { final class Coordinator: NSObject, UITextViewDelegate, ElementTextViewDelegate {
private var text: Binding<String> private var text: Binding<String>
private var focused: Binding<Bool>
private let maxHeight: CGFloat private let maxHeight: CGFloat
@ -136,12 +127,10 @@ private struct UITextViewWrapper: UIViewRepresentable {
private let pasteHandler: PasteHandler private let pasteHandler: PasteHandler
init(text: Binding<String>, init(text: Binding<String>,
focused: Binding<Bool>,
maxHeight: CGFloat, maxHeight: CGFloat,
enterKeyHandler: @escaping EnterKeyHandler, enterKeyHandler: @escaping EnterKeyHandler,
pasteHandler: @escaping PasteHandler) { pasteHandler: @escaping PasteHandler) {
self.text = text self.text = text
self.focused = focused
self.maxHeight = maxHeight self.maxHeight = maxHeight
self.enterKeyHandler = enterKeyHandler self.enterKeyHandler = enterKeyHandler
self.pasteHandler = pasteHandler self.pasteHandler = pasteHandler
@ -151,18 +140,6 @@ private struct UITextViewWrapper: UIViewRepresentable {
text.wrappedValue = textView.text 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) { func textViewDidReceiveEnterKeyPress(_ textView: UITextView) {
enterKeyHandler() enterKeyHandler()
} }
@ -256,19 +233,17 @@ struct MessageComposerTextField_Previews: PreviewProvider {
struct PreviewWrapper: View { struct PreviewWrapper: View {
@State var text: String @State var text: String
@State var focused: Bool
@State var isMultiline: Bool @State var isMultiline: Bool
init(text: String) { init(text: String) {
_text = .init(initialValue: text) _text = .init(initialValue: text)
_focused = .init(initialValue: false)
_isMultiline = .init(initialValue: false) _isMultiline = .init(initialValue: false)
} }
var body: some View { var body: some View {
MessageComposerTextField(placeholder: "Placeholder", MessageComposerTextField(placeholder: "Placeholder",
text: $text, text: $text,
focused: $focused, focused: FocusState().projectedValue,
isMultiline: $isMultiline, isMultiline: $isMultiline,
maxHeight: 300, maxHeight: 300,
enterKeyHandler: { }, enterKeyHandler: { },

View File

@ -227,7 +227,8 @@ class LoggingTests: XCTestCase {
lastMessage: AttributedString(lastMessage), lastMessage: AttributedString(lastMessage),
lastMessageFormattedTimestamp: "Now", lastMessageFormattedTimestamp: "Now",
unreadNotificationCount: 0, unreadNotificationCount: 0,
canonicalAlias: nil) canonicalAlias: nil,
inviter: nil)
// When logging that value // When logging that value
XCTAssert(MXLogger.logFiles.isEmpty) XCTAssert(MXLogger.logFiles.isEmpty)