mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
Fixes #1282 - Switch composer text vield to a @FocusState
This commit is contained in:
parent
03f1ad1dce
commit
a14c995686
@ -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()
|
||||||
|
@ -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),
|
||||||
|
@ -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: { },
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user