From 80fcf0827fa5b1b233f8b8352ac64226658fa97c Mon Sep 17 00:00:00 2001 From: Ahmed-Naguib93 Date: Sun, 6 Oct 2024 21:49:26 +0300 Subject: [PATCH 01/13] Typing in long pasted text moves the cursor to the end refs:MBL-17838 affects:Student, Teacher release note: Moved the cursor to the end when pasting long text test plan: --- .../View/ComposeMessageView.swift | 98 ++++++++++--------- .../View/UITextViewWrapper.swift | 27 +++-- 2 files changed, 74 insertions(+), 51 deletions(-) diff --git a/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift b/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift index 3330dada3f..700817aa2e 100644 --- a/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift +++ b/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift @@ -25,7 +25,7 @@ public struct ComposeMessageView: View, ScreenViewTrackable { public let screenViewTrackingParameters: ScreenViewTrackingParameters @State private var recipientViewHeight: CGFloat = .zero @State private var searchTextFieldHeight: CGFloat = .zero - + private let attachmentsViewId = "attachmentsView" private enum FocusedInput { case subject case message @@ -47,51 +47,58 @@ public struct ComposeMessageView: View, ScreenViewTrackable { public var body: some View { InstUI.BaseScreen(state: model.state, config: model.screenConfig) { geometry in - VStack(spacing: 0) { - headerView - .background( - GeometryReader { proxy in - Color.clear - .onAppear { - headerHeight = proxy.size.height - model.showSearchRecipientsView = false - focusedInput = nil + ScrollViewReader { proxy in + VStack(spacing: 0) { + headerView + .background( + GeometryReader { proxy in + Color.clear + .onAppear { + headerHeight = proxy.size.height + model.showSearchRecipientsView = false + focusedInput = nil + } + } + ) + separator + courseView + separator + ZStack(alignment: .topLeading) { + VStack(spacing: 0) { + propertiesView + separator + + bodyView(geometry: geometry) { + withAnimation { + proxy.scrollTo(attachmentsViewId) } + } + attachmentsView + .id(attachmentsViewId) + if !model.includedMessages.isEmpty { + includedMessages + } + // This Rectangle adds extra height to ensure smoother display of the list of recipients + // without affecting the UI or any logic. + Rectangle() + .fill(Color.clear) + .frame(height: 150) + .allowsHitTesting(false) } - ) - separator - courseView - separator - ZStack(alignment: .topLeading) { - VStack(spacing: 0) { - propertiesView - separator - - bodyView(geometry: geometry) - attachmentsView - if !model.includedMessages.isEmpty { - includedMessages - } - // This Rectangle adds extra height to ensure smoother display of the list of recipients - // without affecting the UI or any logic. - Rectangle() - .fill(Color.clear) - .frame(height: 150) - .allowsHitTesting(false) - } - if model.showSearchRecipientsView { - RecipientFilterView(recipients: model.searchedRecipients) { selectedRecipient in - model.showSearchRecipientsView = false - model.textRecipientSearch = "" - model.didSelectRecipient.accept(selectedRecipient) + if model.showSearchRecipientsView { + RecipientFilterView(recipients: model.searchedRecipients) { selectedRecipient in + model.showSearchRecipientsView = false + model.textRecipientSearch = "" + model.didSelectRecipient.accept(selectedRecipient) + } + .accessibilityHidden(true) + .offset(y: model.recipients.isEmpty ? searchTextFieldHeight : recipientViewHeight + searchTextFieldHeight) + .padding(.horizontal, 35) + .fixedSize(horizontal: false, vertical: true) + .animation(.smooth, value: model.showSearchRecipientsView) } - .accessibilityHidden(true) - .offset(y: model.recipients.isEmpty ? searchTextFieldHeight : recipientViewHeight + searchTextFieldHeight) - .padding(.horizontal, 35) - .fixedSize(horizontal: false, vertical: true) - .animation(.smooth, value: model.showSearchRecipientsView) - } + } } } .font(.regular12) @@ -361,7 +368,10 @@ public struct ComposeMessageView: View, ScreenViewTrackable { } @ViewBuilder - private func bodyView(geometry: GeometryProxy) -> some View { + private func bodyView( + geometry: GeometryProxy, + onPaste: @escaping () -> Void + ) -> some View { VStack(spacing: 0) { HStack { Text("Message", bundle: .core) @@ -388,7 +398,7 @@ public struct ComposeMessageView: View, ScreenViewTrackable { .padding(.leading, defaultHorizontalPaddingValue) .padding(.top, defaultVerticalPaddingValue) - UITextViewWrapper(text: $model.bodyText) { + UITextViewWrapper(text: $model.bodyText, onPaste: onPaste) { let tv = UITextView() tv.isScrollEnabled = false tv.textContainer.widthTracksTextView = true diff --git a/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift b/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift index c983a49686..698778da6f 100644 --- a/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift +++ b/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift @@ -21,6 +21,7 @@ import SwiftUI struct UITextViewWrapper: UIViewRepresentable { @Binding var text: String + var onPaste: () -> Void = {} let textViewBuilder: () -> UITextView func makeUIView(context: UIViewRepresentableContext) -> UITextView { @@ -41,14 +42,26 @@ struct UITextViewWrapper: UIViewRepresentable { class Coordinator: NSObject, UITextViewDelegate { - var parent: UITextViewWrapper + var parent: UITextViewWrapper - init(_ textField: UITextViewWrapper) { - self.parent = textField - } + init(_ textField: UITextViewWrapper) { + self.parent = textField + } - func textViewDidChange(_ textView: UITextView) { - self.parent.text = textView.text - } + func textViewDidChange(_ textView: UITextView) { + self.parent.text = textView.text + } + + func textView( + _ textView: UITextView, + shouldChangeTextIn range: NSRange, + replacementText text: String + ) -> Bool { + // Detect when pasted content comes from the pasteboard + if let pasteboard = UIPasteboard.general.string, text == pasteboard { + parent.onPaste() // Trigger the onPaste callback + } + return true + } } } From f5432f2f7cbb9838131043b385ebd9fbab089d79 Mon Sep 17 00:00:00 2001 From: Ahmed-Naguib93 Date: Fri, 11 Oct 2024 13:55:16 +0300 Subject: [PATCH 02/13] Move move courser to end refs:MBL-17838 affects:Student, Teacher release note: Moved the cursor to the end when pasting long text test plan: --- .../Inbox/ComposeMessage/View/UITextViewWrapper.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift b/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift index 698778da6f..2051af48a9 100644 --- a/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift +++ b/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift @@ -35,9 +35,9 @@ struct UITextViewWrapper: UIViewRepresentable { } func makeCoordinator() -> Coordinator { - let coordinator = Coordinator(self) + let coordinator = Coordinator(self) - return coordinator + return coordinator } class Coordinator: NSObject, UITextViewDelegate { @@ -60,8 +60,15 @@ struct UITextViewWrapper: UIViewRepresentable { // Detect when pasted content comes from the pasteboard if let pasteboard = UIPasteboard.general.string, text == pasteboard { parent.onPaste() // Trigger the onPaste callback + textView.moveCourserToEnd() } return true } } } + +extension UITextView { + public func moveCourserToEnd() { + selectedTextRange = textRange(from: endOfDocument, to: endOfDocument) + } +} From 97306dbecfed0d6a2e9a3dc9fad3435bb8ef110d Mon Sep 17 00:00:00 2001 From: Ahmed-Naguib93 Date: Tue, 15 Oct 2024 19:19:06 +0300 Subject: [PATCH 03/13] Fix issue in past text for inbox refs: MBL-17838 affects: Student , Teacher release note: Moved the cursor to the end when pasting long text test plan: --- .../ComposeMessage/View/UITextViewWrapper.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift b/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift index 2051af48a9..d2e2370b55 100644 --- a/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift +++ b/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift @@ -57,6 +57,17 @@ struct UITextViewWrapper: UIViewRepresentable { shouldChangeTextIn range: NSRange, replacementText text: String ) -> Bool { + if text == "\n" { // When hit `Return` button "New Line" + let currentCursorPosition = textView.selectedRange.location + 1 + let textLength = textView.text.count + let textPosition = textView.position(from: textView.beginningOfDocument, offset: currentCursorPosition) + + if let textPosition, textLength > currentCursorPosition { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + textView.moveCourserTo(position: textPosition) + } + } + } // Detect when pasted content comes from the pasteboard if let pasteboard = UIPasteboard.general.string, text == pasteboard { parent.onPaste() // Trigger the onPaste callback @@ -71,4 +82,8 @@ extension UITextView { public func moveCourserToEnd() { selectedTextRange = textRange(from: endOfDocument, to: endOfDocument) } + + public func moveCourserTo(position: UITextPosition) { + selectedTextRange = textRange(from: position, to: position) + } } From 73c600965ab6f8ec610a7579ed890c53f7343193 Mon Sep 17 00:00:00 2001 From: Ahmed-Naguib93 Date: Tue, 15 Oct 2024 19:33:50 +0300 Subject: [PATCH 04/13] fix swiftLint issue refs:MBL-17838 affects:Student , Teacher release note:Moved the cursor to the end when pasting long text test plan: --- Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift b/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift index d2e2370b55..015975d662 100644 --- a/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift +++ b/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift @@ -61,8 +61,8 @@ struct UITextViewWrapper: UIViewRepresentable { let currentCursorPosition = textView.selectedRange.location + 1 let textLength = textView.text.count let textPosition = textView.position(from: textView.beginningOfDocument, offset: currentCursorPosition) - if let textPosition, textLength > currentCursorPosition { + // Wait a bit so can move the courser to the exact position DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { textView.moveCourserTo(position: textPosition) } From 42af1cf2af3d22301fb007c13cf3920e77b7d3c3 Mon Sep 17 00:00:00 2001 From: Ahmed-Naguib93 Date: Tue, 15 Oct 2024 20:51:25 +0300 Subject: [PATCH 05/13] Fix past long text refs:MBL-17838 affects:Student , Teacher release note:Moved the cursor to the end when pasting long text test plan: --- .../View/ComposeMessageView.swift | 46 +++------- .../View/UITextViewWrapper.swift | 89 ------------------- 2 files changed, 12 insertions(+), 123 deletions(-) delete mode 100644 Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift diff --git a/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift b/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift index 700817aa2e..af133eeb2a 100644 --- a/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift +++ b/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift @@ -25,7 +25,6 @@ public struct ComposeMessageView: View, ScreenViewTrackable { public let screenViewTrackingParameters: ScreenViewTrackingParameters @State private var recipientViewHeight: CGFloat = .zero @State private var searchTextFieldHeight: CGFloat = .zero - private let attachmentsViewId = "attachmentsView" private enum FocusedInput { case subject case message @@ -47,7 +46,6 @@ public struct ComposeMessageView: View, ScreenViewTrackable { public var body: some View { InstUI.BaseScreen(state: model.state, config: model.screenConfig) { geometry in - ScrollViewReader { proxy in VStack(spacing: 0) { headerView .background( @@ -68,13 +66,8 @@ public struct ComposeMessageView: View, ScreenViewTrackable { propertiesView separator - bodyView(geometry: geometry) { - withAnimation { - proxy.scrollTo(attachmentsViewId) - } - } + bodyView(geometry: geometry) attachmentsView - .id(attachmentsViewId) if !model.includedMessages.isEmpty { includedMessages } @@ -97,9 +90,7 @@ public struct ComposeMessageView: View, ScreenViewTrackable { .fixedSize(horizontal: false, vertical: true) .animation(.smooth, value: model.showSearchRecipientsView) } - } - } } .font(.regular12) .foregroundColor(.textDarkest) @@ -368,10 +359,7 @@ public struct ComposeMessageView: View, ScreenViewTrackable { } @ViewBuilder - private func bodyView( - geometry: GeometryProxy, - onPaste: @escaping () -> Void - ) -> some View { + private func bodyView(geometry: GeometryProxy) -> some View { VStack(spacing: 0) { HStack { Text("Message", bundle: .core) @@ -397,26 +385,16 @@ public struct ComposeMessageView: View, ScreenViewTrackable { } .padding(.leading, defaultHorizontalPaddingValue) .padding(.top, defaultVerticalPaddingValue) - - UITextViewWrapper(text: $model.bodyText, onPaste: onPaste) { - let tv = UITextView() - tv.isScrollEnabled = false - tv.textContainer.widthTracksTextView = true - tv.textContainer.lineBreakMode = .byWordWrapping - tv.font = UIFont.scaledNamedFont(.regular16) - tv.translatesAutoresizingMaskIntoConstraints = false - tv.widthAnchor.constraint(equalToConstant: geometry.frame(in: .global).width - (2 * defaultHorizontalPaddingValue)).isActive = true - tv.backgroundColor = .backgroundLightest - return tv - } - .font(.regular16, lineHeight: .condensed) - .textInputAutocapitalization(.sentences) - .focused($focusedInput, equals: .message) - .foregroundColor(.textDarkest) - .padding(.horizontal, defaultHorizontalPaddingValue) - .frame(minHeight: 60) - .accessibility(label: Text("Message", bundle: .core)) - .accessibilityIdentifier("ComposeMessage.body") + TextEditor(text: $model.bodyText) + .foregroundColor(.textDarkest) + .font(.regular16, lineHeight: .condensed) + .focused($focusedInput, equals: .message) + .paddingStyle(.horizontal, .standard) + .scrollDisabled(true) + .textInputAutocapitalization(.sentences) + .frame(minHeight: 60) + .accessibility(label: Text("Message", bundle: .core)) + .accessibilityIdentifier("ComposeMessage.body") } .disabled(model.isMessageDisabled) .opacity(model.isMessageDisabled ? 0.6 : 1) diff --git a/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift b/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift deleted file mode 100644 index 015975d662..0000000000 --- a/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift +++ /dev/null @@ -1,89 +0,0 @@ -// -// This file is part of Canvas. -// Copyright (C) 2024-present Instructure, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -// - -import Foundation -import SwiftUI - -struct UITextViewWrapper: UIViewRepresentable { - @Binding var text: String - var onPaste: () -> Void = {} - let textViewBuilder: () -> UITextView - - func makeUIView(context: UIViewRepresentableContext) -> UITextView { - let tv = textViewBuilder() - tv.delegate = context.coordinator - return tv - } - - func updateUIView(_ textView: UITextView, context: UIViewRepresentableContext) { - textView.text = text - } - - func makeCoordinator() -> Coordinator { - let coordinator = Coordinator(self) - - return coordinator - } - - class Coordinator: NSObject, UITextViewDelegate { - - var parent: UITextViewWrapper - - init(_ textField: UITextViewWrapper) { - self.parent = textField - } - - func textViewDidChange(_ textView: UITextView) { - self.parent.text = textView.text - } - - func textView( - _ textView: UITextView, - shouldChangeTextIn range: NSRange, - replacementText text: String - ) -> Bool { - if text == "\n" { // When hit `Return` button "New Line" - let currentCursorPosition = textView.selectedRange.location + 1 - let textLength = textView.text.count - let textPosition = textView.position(from: textView.beginningOfDocument, offset: currentCursorPosition) - if let textPosition, textLength > currentCursorPosition { - // Wait a bit so can move the courser to the exact position - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - textView.moveCourserTo(position: textPosition) - } - } - } - // Detect when pasted content comes from the pasteboard - if let pasteboard = UIPasteboard.general.string, text == pasteboard { - parent.onPaste() // Trigger the onPaste callback - textView.moveCourserToEnd() - } - return true - } - } -} - -extension UITextView { - public func moveCourserToEnd() { - selectedTextRange = textRange(from: endOfDocument, to: endOfDocument) - } - - public func moveCourserTo(position: UITextPosition) { - selectedTextRange = textRange(from: position, to: position) - } -} From 92ea9184542a1a0e032d9467d1ce1b893ff58a74 Mon Sep 17 00:00:00 2001 From: Ahmed-Naguib93 Date: Tue, 15 Oct 2024 20:53:27 +0300 Subject: [PATCH 06/13] Fix indentation refs:MBL-17838 affects:Student , Teacher release note:Moved the cursor to the end when pasting long text test plan: --- .../View/ComposeMessageView.swift | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift b/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift index af133eeb2a..46efc830a3 100644 --- a/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift +++ b/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift @@ -46,51 +46,51 @@ public struct ComposeMessageView: View, ScreenViewTrackable { public var body: some View { InstUI.BaseScreen(state: model.state, config: model.screenConfig) { geometry in - VStack(spacing: 0) { - headerView - .background( - GeometryReader { proxy in - Color.clear - .onAppear { - headerHeight = proxy.size.height - model.showSearchRecipientsView = false - focusedInput = nil - } - } - ) - separator - courseView - separator - ZStack(alignment: .topLeading) { - VStack(spacing: 0) { - propertiesView - separator - - bodyView(geometry: geometry) - attachmentsView - if !model.includedMessages.isEmpty { - includedMessages - } - // This Rectangle adds extra height to ensure smoother display of the list of recipients - // without affecting the UI or any logic. - Rectangle() - .fill(Color.clear) - .frame(height: 150) - .allowsHitTesting(false) + VStack(spacing: 0) { + headerView + .background( + GeometryReader { proxy in + Color.clear + .onAppear { + headerHeight = proxy.size.height + model.showSearchRecipientsView = false + focusedInput = nil + } } - if model.showSearchRecipientsView { - RecipientFilterView(recipients: model.searchedRecipients) { selectedRecipient in - model.showSearchRecipientsView = false - model.textRecipientSearch = "" - model.didSelectRecipient.accept(selectedRecipient) - } - .accessibilityHidden(true) - .offset(y: model.recipients.isEmpty ? searchTextFieldHeight : recipientViewHeight + searchTextFieldHeight) - .padding(.horizontal, 35) - .fixedSize(horizontal: false, vertical: true) - .animation(.smooth, value: model.showSearchRecipientsView) + ) + separator + courseView + separator + ZStack(alignment: .topLeading) { + VStack(spacing: 0) { + propertiesView + separator + + bodyView(geometry: geometry) + attachmentsView + if !model.includedMessages.isEmpty { + includedMessages + } + // This Rectangle adds extra height to ensure smoother display of the list of recipients + // without affecting the UI or any logic. + Rectangle() + .fill(Color.clear) + .frame(height: 150) + .allowsHitTesting(false) + } + if model.showSearchRecipientsView { + RecipientFilterView(recipients: model.searchedRecipients) { selectedRecipient in + model.showSearchRecipientsView = false + model.textRecipientSearch = "" + model.didSelectRecipient.accept(selectedRecipient) } + .accessibilityHidden(true) + .offset(y: model.recipients.isEmpty ? searchTextFieldHeight : recipientViewHeight + searchTextFieldHeight) + .padding(.horizontal, 35) + .fixedSize(horizontal: false, vertical: true) + .animation(.smooth, value: model.showSearchRecipientsView) } + } } .font(.regular12) .foregroundColor(.textDarkest) From 37151a9061563a89e366f663dabbf15d9ea6c716 Mon Sep 17 00:00:00 2001 From: Ahmed-Naguib93 Date: Wed, 16 Oct 2024 00:28:53 +0300 Subject: [PATCH 07/13] Fix issue in iOS18 refs:MBL-17838 affects:Student , Teacher release note: Fixed an issue where pasting long text and typing in the middle moved the cursor to the end. test plan: --- Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift b/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift index 46efc830a3..0e9d310ad2 100644 --- a/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift +++ b/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift @@ -390,7 +390,6 @@ public struct ComposeMessageView: View, ScreenViewTrackable { .font(.regular16, lineHeight: .condensed) .focused($focusedInput, equals: .message) .paddingStyle(.horizontal, .standard) - .scrollDisabled(true) .textInputAutocapitalization(.sentences) .frame(minHeight: 60) .accessibility(label: Text("Message", bundle: .core)) From 294b6d6cb99e6db64fac384de9c6ce986a8d38cd Mon Sep 17 00:00:00 2001 From: Ahmed-Naguib93 Date: Wed, 16 Oct 2024 02:15:15 +0300 Subject: [PATCH 08/13] Fix past issues refs:MBL-17838 affects:Student , Teacher release note:Fixed an issue where pasting long text and typing in the middle moved the cursor to the end. test plan: --- .../View/ComposeMessageView.swift | 29 +++++++--- .../View/UITextViewWrapper.swift | 56 +++++++++++++++++++ 2 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift diff --git a/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift b/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift index 0e9d310ad2..4f8c438841 100644 --- a/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift +++ b/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift @@ -385,15 +385,26 @@ public struct ComposeMessageView: View, ScreenViewTrackable { } .padding(.leading, defaultHorizontalPaddingValue) .padding(.top, defaultVerticalPaddingValue) - TextEditor(text: $model.bodyText) - .foregroundColor(.textDarkest) - .font(.regular16, lineHeight: .condensed) - .focused($focusedInput, equals: .message) - .paddingStyle(.horizontal, .standard) - .textInputAutocapitalization(.sentences) - .frame(minHeight: 60) - .accessibility(label: Text("Message", bundle: .core)) - .accessibilityIdentifier("ComposeMessage.body") + + UITextViewWrapper(text: $model.bodyText) { + let tv = UITextView() + tv.isScrollEnabled = false + tv.textContainer.widthTracksTextView = true + tv.textContainer.lineBreakMode = .byWordWrapping + tv.font = UIFont.scaledNamedFont(.regular16) + tv.translatesAutoresizingMaskIntoConstraints = false + tv.widthAnchor.constraint(equalToConstant: geometry.frame(in: .global).width - (2 * defaultHorizontalPaddingValue)).isActive = true + tv.backgroundColor = .backgroundLightest + return tv + } + .font(.regular16, lineHeight: .condensed) + .textInputAutocapitalization(.sentences) + .focused($focusedInput, equals: .message) + .foregroundColor(.textDarkest) + .padding(.horizontal, defaultHorizontalPaddingValue) + .frame(minHeight: 60) + .accessibility(label: Text("Message", bundle: .core)) + .accessibilityIdentifier("ComposeMessage.body") } .disabled(model.isMessageDisabled) .opacity(model.isMessageDisabled ? 0.6 : 1) diff --git a/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift b/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift new file mode 100644 index 0000000000..7c19fc1c68 --- /dev/null +++ b/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift @@ -0,0 +1,56 @@ +// +// This file is part of Canvas. +// Copyright (C) 2024-present Instructure, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// + +import Foundation +import SwiftUI + +struct UITextViewWrapper: UIViewRepresentable { + @Binding var text: String + let textViewBuilder: () -> UITextView + + func makeUIView(context: UIViewRepresentableContext) -> UITextView { + let tv = textViewBuilder() + tv.delegate = context.coordinator + return tv + } + + func updateUIView(_ textView: UITextView, context: UIViewRepresentableContext) { + if text != textView.text { + textView.text = text + } + } + + func makeCoordinator() -> Coordinator { + let coordinator = Coordinator(self) + + return coordinator + } + + class Coordinator: NSObject, UITextViewDelegate { + + var parent: UITextViewWrapper + + init(_ textField: UITextViewWrapper) { + self.parent = textField + } + + func textViewDidChange(_ textView: UITextView) { + self.parent.text = textView.text + } + } +} From e444057a23f749fabb554a0bbd331ed3d18c0555 Mon Sep 17 00:00:00 2001 From: Ahmed-Naguib93 Date: Wed, 16 Oct 2024 10:06:23 +0300 Subject: [PATCH 09/13] Fix swiftlint issues refs:MBL-17838 affects:Student , Teacher release note: Fixed an issue where pasting long text and typing in the middle moved the cursor to the end. test plan: --- Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift b/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift index 4f8c438841..2a261e237e 100644 --- a/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift +++ b/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift @@ -385,7 +385,6 @@ public struct ComposeMessageView: View, ScreenViewTrackable { } .padding(.leading, defaultHorizontalPaddingValue) .padding(.top, defaultVerticalPaddingValue) - UITextViewWrapper(text: $model.bodyText) { let tv = UITextView() tv.isScrollEnabled = false From 15cd769b89f6013b3c3480a3b03cc4ed0ea79948 Mon Sep 17 00:00:00 2001 From: Ahmed-Naguib93 Date: Wed, 16 Oct 2024 10:45:35 +0300 Subject: [PATCH 10/13] Update for CI refs:MBL-17838 affects:Student , Teacher release note: Fixed an issue where pasting long text and typing in the middle moved the cursor to the end. test plan: --- Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift b/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift index 7c19fc1c68..94391d6359 100644 --- a/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift +++ b/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift @@ -37,7 +37,6 @@ struct UITextViewWrapper: UIViewRepresentable { func makeCoordinator() -> Coordinator { let coordinator = Coordinator(self) - return coordinator } From 4e0017824a956bbeb8c9fde342e09bc7d9add05f Mon Sep 17 00:00:00 2001 From: Ahmed-Naguib93 Date: Wed, 16 Oct 2024 12:38:25 +0300 Subject: [PATCH 11/13] Use textEditor instead refs:MBL-17838 affects:Student , Teacher release note:Fixed an issue where pasting long text and typing in the middle moved the cursor to the end. test plan: --- .../View/ComposeMessageView.swift | 16 ++---- .../View/UITextViewWrapper.swift | 55 ------------------- 2 files changed, 4 insertions(+), 67 deletions(-) delete mode 100644 Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift diff --git a/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift b/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift index 2a261e237e..907f48e909 100644 --- a/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift +++ b/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift @@ -385,22 +385,14 @@ public struct ComposeMessageView: View, ScreenViewTrackable { } .padding(.leading, defaultHorizontalPaddingValue) .padding(.top, defaultVerticalPaddingValue) - UITextViewWrapper(text: $model.bodyText) { - let tv = UITextView() - tv.isScrollEnabled = false - tv.textContainer.widthTracksTextView = true - tv.textContainer.lineBreakMode = .byWordWrapping - tv.font = UIFont.scaledNamedFont(.regular16) - tv.translatesAutoresizingMaskIntoConstraints = false - tv.widthAnchor.constraint(equalToConstant: geometry.frame(in: .global).width - (2 * defaultHorizontalPaddingValue)).isActive = true - tv.backgroundColor = .backgroundLightest - return tv - } + InstUI.TextEditorCell( + placeholder: "", + text: $model.bodyText + ) .font(.regular16, lineHeight: .condensed) .textInputAutocapitalization(.sentences) .focused($focusedInput, equals: .message) .foregroundColor(.textDarkest) - .padding(.horizontal, defaultHorizontalPaddingValue) .frame(minHeight: 60) .accessibility(label: Text("Message", bundle: .core)) .accessibilityIdentifier("ComposeMessage.body") diff --git a/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift b/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift deleted file mode 100644 index 94391d6359..0000000000 --- a/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// This file is part of Canvas. -// Copyright (C) 2024-present Instructure, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -// - -import Foundation -import SwiftUI - -struct UITextViewWrapper: UIViewRepresentable { - @Binding var text: String - let textViewBuilder: () -> UITextView - - func makeUIView(context: UIViewRepresentableContext) -> UITextView { - let tv = textViewBuilder() - tv.delegate = context.coordinator - return tv - } - - func updateUIView(_ textView: UITextView, context: UIViewRepresentableContext) { - if text != textView.text { - textView.text = text - } - } - - func makeCoordinator() -> Coordinator { - let coordinator = Coordinator(self) - return coordinator - } - - class Coordinator: NSObject, UITextViewDelegate { - - var parent: UITextViewWrapper - - init(_ textField: UITextViewWrapper) { - self.parent = textField - } - - func textViewDidChange(_ textView: UITextView) { - self.parent.text = textView.text - } - } -} From 544c1284d8db9b3321e320ebc8786527e79449b6 Mon Sep 17 00:00:00 2001 From: Ahmed-Naguib93 Date: Wed, 16 Oct 2024 13:12:49 +0300 Subject: [PATCH 12/13] Update for CI refs: MBL-17838 affects: Student , Teacher release note: Fixed an issue where pasting long text and typing in the middle moved the cursor to the end. test plan: --- Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift b/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift index 94391d6359..3c853a32c0 100644 --- a/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift +++ b/Core/Core/Inbox/ComposeMessage/View/UITextViewWrapper.swift @@ -49,7 +49,7 @@ struct UITextViewWrapper: UIViewRepresentable { } func textViewDidChange(_ textView: UITextView) { - self.parent.text = textView.text + parent.text = textView.text } } } From 18d0f508b6605a49a88b0853d94666a1bd1349bd Mon Sep 17 00:00:00 2001 From: Ahmed-Naguib93 Date: Thu, 17 Oct 2024 13:11:33 +0300 Subject: [PATCH 13/13] Remove white lines refs:MBL-17838 affects: Student , Teacher release note:Fixed an issue where pasting long text and typing in the middle moved the cursor to the end. test plan: --- Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift b/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift index 2a261e237e..3330dada3f 100644 --- a/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift +++ b/Core/Core/Inbox/ComposeMessage/View/ComposeMessageView.swift @@ -25,6 +25,7 @@ public struct ComposeMessageView: View, ScreenViewTrackable { public let screenViewTrackingParameters: ScreenViewTrackingParameters @State private var recipientViewHeight: CGFloat = .zero @State private var searchTextFieldHeight: CGFloat = .zero + private enum FocusedInput { case subject case message @@ -90,6 +91,7 @@ public struct ComposeMessageView: View, ScreenViewTrackable { .fixedSize(horizontal: false, vertical: true) .animation(.smooth, value: model.showSearchRecipientsView) } + } } .font(.regular12) @@ -385,6 +387,7 @@ public struct ComposeMessageView: View, ScreenViewTrackable { } .padding(.leading, defaultHorizontalPaddingValue) .padding(.top, defaultVerticalPaddingValue) + UITextViewWrapper(text: $model.bodyText) { let tv = UITextView() tv.isScrollEnabled = false