diff --git a/Mail/Helpers/OldRichTextEditor.swift b/Mail/Helpers/OldRichTextEditor.swift deleted file mode 100644 index 62b61d0316..0000000000 --- a/Mail/Helpers/OldRichTextEditor.swift +++ /dev/null @@ -1,510 +0,0 @@ -/* - Infomaniak Mail - iOS App - Copyright (C) 2022 Infomaniak Network SA - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -import InfomaniakCore -import InfomaniakCoreUI -import InfomaniakDI -import InfomaniakRichEditor -import MailCore -import MailCoreUI -import MailResources -import SQRichTextEditor -import SwiftUI -import WebKit - -struct OldRichTextEditor: UIViewRepresentable { - @State private var mustUpdateBody = false - - @Binding var model: RichTextEditorModel - @Binding var body: String - @Binding var isShowingCamera: Bool - @Binding var isShowingFileSelection: Bool - @Binding var isShowingPhotoLibrary: Bool - @Binding var becomeFirstResponder: Bool - @Binding var isShowingAIPrompt: Bool - @Binding var isShowingAlert: NewMessageAlert? - - let blockRemoteContent: Bool - - init(model: Binding, - body: Binding, - isShowingCamera: Binding, - isShowingFileSelection: Binding, - isShowingPhotoLibrary: Binding, - becomeFirstResponder: Binding, - isShowingAIPrompt: Binding, - isShowingAlert: Binding, - blockRemoteContent: Bool) { - _model = model - _body = body - _isShowingCamera = isShowingCamera - _isShowingFileSelection = isShowingFileSelection - _isShowingPhotoLibrary = isShowingPhotoLibrary - _becomeFirstResponder = becomeFirstResponder - _isShowingAIPrompt = isShowingAIPrompt - _isShowingAlert = isShowingAlert - self.blockRemoteContent = blockRemoteContent - } - - class Coordinator: SQTextEditorDelegate { - var parent: OldRichTextEditor - - init(_ parent: OldRichTextEditor) { - self.parent = parent - NotificationCenter.default.addObserver( - self, - selector: #selector(requireBodyUpdate), - name: Notification.Name.updateComposeMessageBody, - object: nil - ) - } - - @MainActor - func insertBody(editor: SQTextEditorView) async throws { - guard let editor = (editor as? MailEditorView) else { throw MailError.unknownError } - try await insertBody(editor: editor) - } - - @MainActor - func insertBody(editor: MailEditorView) async throws { - try await editor.contentBlocker.setRemoteContentBlocked(parent.blockRemoteContent) - editor.clear() - try await editor.insertRawHTML(parent.body) - editor.moveCursorToStart() - editor.webView.scrollView.isScrollEnabled = false - parent.model.height = CGFloat(editor.contentHeight) - } - - func editorDidLoad(_ editor: SQTextEditorView) { - Task { - do { - try await insertBody(editor: editor) - } catch { - print("Failed to load editor: \(error)") - } - } - } - - func editor(_ editor: SQTextEditorView, contentHeightDidChange height: Int) { - parent.model.height = CGFloat(height) - } - - func editor(_ editor: SQTextEditorView, cursorPositionDidChange position: SQEditorCursorPosition) { - let newCursorPosition = CGFloat(position.bottom) + 20 - if parent.model.cursorPosition != newCursorPosition { - parent.model.cursorPosition = newCursorPosition - } - } - - func editor(_ editor: SQTextEditorView, selectedTextAttributeDidChange attribute: SQTextAttribute) { - if let mailEditor = editor as? MailEditorView { - mailEditor.updateToolbarItems(style: mailEditor.toolbarStyle) - } - } - - func editorContentChanged(_ editor: SQTextEditorView, content: String) { - var parentBody = parent.body.trimmingCharacters(in: .whitespacesAndNewlines) - parentBody = parentBody.replacingOccurrences(of: "\r", with: "") - if parentBody != content { - parent.body = content - } - } - - @objc func requireBodyUpdate() { - parent.mustUpdateBody = true - } - } - - func makeCoordinator() -> Coordinator { - return Coordinator(self) - } - - func makeUIView(context: Context) -> MailEditorView { - let richTextEditor = MailEditorView( - isShowingCamera: $isShowingCamera, - isShowingFileSelection: $isShowingFileSelection, - isShowingPhotoLibrary: $isShowingPhotoLibrary, - isShowingAIPrompt: $isShowingAIPrompt, - isShowingAlert: $isShowingAlert - ) - richTextEditor.delegate = context.coordinator - return richTextEditor - } - - func updateUIView(_ uiView: MailEditorView, context: Context) { - if becomeFirstResponder { - DispatchQueue.main.async { - uiView.setBecomeFirstResponder() - becomeFirstResponder = false - } - } - if mustUpdateBody { - Task { - mustUpdateBody = false - try await context.coordinator.insertBody(editor: uiView) - } - } - } - - static func dismantleUIView(_ uiView: MailEditorView, coordinator: Coordinator) { - uiView.webView.configuration.userContentController.removeAllScriptMessageHandlers() - } -} - -extension SQTextEditorView { - func insertHtml(_ html: String) async throws { - return try await withCheckedThrowingContinuation { continuation in - insertHTML(html) { error in - if let error { - continuation.resume(throwing: error) - } else { - continuation.resume() - } - } - } - } -} - -struct RichTextEditorModel { - var cursorPosition: CGFloat = 0 - var height: CGFloat = 0 -} - -final class MailEditorView: SQTextEditorView { - @LazyInjectService var matomo: MatomoUtils - - lazy var toolbar = getToolbar() - var isShowingCamera: Binding - var isShowingFileSelection: Binding - var isShowingPhotoLibrary: Binding - var isShowingAIPrompt: Binding - var isShowingAlert: Binding - - var toolbarStyle = ToolbarStyle.main - - init(isShowingCamera: Binding, isShowingFileSelection: Binding, isShowingPhotoLibrary: Binding, - isShowingAIPrompt: Binding, isShowingAlert: Binding) { - self.isShowingCamera = isShowingCamera - self.isShowingFileSelection = isShowingFileSelection - self.isShowingPhotoLibrary = isShowingPhotoLibrary - self.isShowingAIPrompt = isShowingAIPrompt - self.isShowingAlert = isShowingAlert - super.init() - } - - public func setBecomeFirstResponder() { - webView.becomeFirstResponder() - } - - lazy var contentBlocker = ContentBlocker(webView: editorWebView) - - private lazy var editorWebView: WKWebView = { - let config = WKWebViewConfiguration() - config.preferences = WKPreferences() - config.preferences.minimumFontSize = 10 - config.preferences.javaScriptCanOpenWindowsAutomatically = false - config.processPool = WKProcessPool() - config.userContentController = WKUserContentController() - config.setURLSchemeHandler(URLSchemeHandler(), forURLScheme: URLSchemeHandler.scheme) - - for jsMessageName in JSMessageName.allCases { - config.userContentController.add(self, name: jsMessageName.rawValue) - } - - let css = customCss ?? MessageWebViewUtils.loadAndFormatCSS(for: .editor) - let cssStyle = "(() => { document.head.innerHTML += `\(css)`; })()" - let cssScript = WKUserScript(source: cssStyle, injectionTime: .atDocumentEnd, forMainFrameOnly: false) - config.userContentController.addUserScript(cssScript) - - let _webView = WKWebView(frame: .zero, configuration: config) - _webView.translatesAutoresizingMaskIntoConstraints = false - _webView.navigationDelegate = self - _webView.allowsLinkPreview = false - _webView.addInputAccessoryView(toolbar: self.toolbar) - _webView.isOpaque = false - _webView.backgroundColor = .clear - self.updateToolbarItems(style: .main) - _webView.scrollView.keyboardDismissMode = .interactive - - #if DEBUG && !TEST - if #available(iOS 17.0, *) { - _webView.isInspectable = true - } - #endif - - return _webView - }() - - override var webView: WKWebView { - get { - return editorWebView - } - set { - editorWebView = newValue - } - } - - private func callEditorMethod(name: String, completion: ((_ error: Error?) -> Void)?) { - webView.evaluateJavaScript("editor.\(name)()") { _, error in - completion?(error) - } - } - - // MARK: - Custom function - - func insertRawHTML(_ html: String) async throws { - let cleanedHTML = try await SwiftSoupUtils(fromHTMLFragment: html).cleanBody() - try await webView.evaluateJavaScript("document.getElementById('editor').innerHTML = `\(cleanedHTML)`") - } - - // MARK: - Editor methods - - /// Removes any current selection and moves the cursor to the very beginning of the document. - func moveCursorToStart(completion: ((_ error: Error?) -> Void)? = nil) { - callEditorMethod(name: "moveCursorToStart", completion: completion) - } - - /// Removes any current selection and moves the cursor to the very end of the document. - func moveCursorToEnd(completion: ((_ error: Error?) -> Void)? = nil) { - callEditorMethod(name: "moveCursorToEnd", completion: completion) - } - - func addBold(completion: ((_ error: Error?) -> Void)? = nil) { - callEditorMethod(name: "bold", completion: completion) - } - - func makeUnorderedList(completion: ((_ error: Error?) -> Void)? = nil) { - callEditorMethod(name: "makeUnorderedList", completion: completion) - } - - func removeList(completion: ((_ error: Error?) -> Void)? = nil) { - callEditorMethod(name: "removeList", completion: completion) - } - - // MARK: - Custom Toolbar - - public func updateToolbarItems(style: ToolbarStyle) { - toolbarStyle = style - - let flexibleSpaceItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil) - - let actionItems = style.actions.map { action -> UIBarButtonItem in - let item = UIBarButtonItem( - image: action.icon, - style: .plain, - target: self, - action: #selector(onToolbarClick(sender:)) - ) - item.tag = action.rawValue - item.isSelected = action.isSelected(textAttribute: selectedTextAttribute) - item.tintColor = action.tint - if action == .editText && style == .textEdition { - item.tintColor = UserDefaults.shared.accentColor.primary.color - } - return item - } - let barButtonItems = Array(actionItems.map { [$0] }.joined(separator: [flexibleSpaceItem])) - - toolbar.setItems(barButtonItems, animated: false) - toolbar.setNeedsLayout() - } - - public func getToolbar() -> UIToolbar { - let newToolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: 320, height: 48)) - UIConstants.applyComposeViewStyle(to: newToolbar) - return newToolbar - } - - @objc func onToolbarClick(sender: UIBarButtonItem) { - guard let toolbarAction = ToolbarAction(rawValue: sender.tag) else { return } - - if let matomoName = toolbarAction.matomoName { - matomo.track(eventWithCategory: .editorActions, name: matomoName) - } - - switch toolbarAction { - case .bold: - bold() - case .italic: - italic() - case .underline: - underline() - case .strikeThrough: - strikethrough() - case .unorderedList: - makeUnorderedList() - case .editText: - updateToolbarItems(style: toolbarStyle == .main ? .textEdition : .main) - case .ai: - webView.resignFirstResponder() - isShowingAIPrompt.wrappedValue = true - case .addFile: - isShowingFileSelection.wrappedValue = true - case .addPhoto: - isShowingPhotoLibrary.wrappedValue = true - case .takePhoto: - isShowingCamera.wrappedValue = true - case .link: - if selectedTextAttribute.format.hasLink { - removeLink() - } else { - webView.resignFirstResponder() - isShowingAlert.wrappedValue = NewMessageAlert(type: .link(handler: { url in - self.makeLink(url: url) - })) - } - case .programMessage: - // TODO: Handle programmed message - showWorkInProgressSnackBar() - } - } -} - -enum ToolbarStyle { - case main - case textEdition - - var actions: [ToolbarAction] { - switch self { - case .main: - @InjectService var featureFlagsManageable: FeatureFlagsManageable - var mainActions: [ToolbarAction] = [.editText, .addFile, .addPhoto, .takePhoto, .link] - featureFlagsManageable.feature(.aiMailComposer, on: { - mainActions.insert(.ai, at: 1) - }, off: nil) - return mainActions - case .textEdition: - return [.editText, .bold, .italic, .underline, .strikeThrough, .unorderedList] - } - } -} - -enum ToolbarAction: Int { - case bold = 1 - case italic - case underline - case strikeThrough - case unorderedList - case editText - case ai - case addFile - case addPhoto - case takePhoto - case link - case programMessage - - var icon: UIImage { - switch self { - case .bold: - return MailResourcesAsset.bold.image - case .italic: - return MailResourcesAsset.italic.image - case .underline: - return MailResourcesAsset.underline.image - case .strikeThrough: - return MailResourcesAsset.strikeThrough.image - case .unorderedList: - return MailResourcesAsset.unorderedList.image - case .editText: - return MailResourcesAsset.textModes.image - case .ai: - return MailResourcesAsset.aiWriter.image - case .addFile: - return MailResourcesAsset.folder.image - case .addPhoto: - return MailResourcesAsset.pictureLandscape.image - case .takePhoto: - return MailResourcesAsset.photo.image - case .link: - return MailResourcesAsset.hyperlink.image - case .programMessage: - return MailResourcesAsset.waitingMessage.image - } - } - - var tint: UIColor { - if self == .ai { - return MailResourcesAsset.aiColor.color - } else { - return MailResourcesAsset.textSecondaryColor.color - } - } - - var matomoName: String? { - switch self { - case .bold: - return "bold" - case .italic: - return "italic" - case .underline: - return "underline" - case .strikeThrough: - return "strikeThrough" - case .unorderedList: - return "unorderedList" - case .ai: - return "aiWriter" - case .addFile: - return "importFile" - case .addPhoto: - return "importImage" - case .takePhoto: - return "importFromCamera" - case .link: - return "addLink" - case .programMessage: - return "postpone" - default: - return nil - } - } - - func isSelected(textAttributes: RETextAttributes) -> Bool { - switch self { - case .bold: - return textAttributes.format.hasBold - case .italic: - return textAttributes.format.hasItalic - case .underline: - return textAttributes.format.hasUnderline - case .strikeThrough: - return textAttributes.format.hasStrikeThrough - case .link: - return false - case .unorderedList, .editText, .ai, .addFile, .addPhoto, .takePhoto, .programMessage: - return false - } - } - - func isSelected(textAttribute: SQTextAttribute) -> Bool { - switch self { - case .bold: - return textAttribute.format.hasBold - case .italic: - return textAttribute.format.hasItalic - case .underline: - return textAttribute.format.hasUnderline - case .strikeThrough: - return textAttribute.format.hasStrikethrough - case .link: - return textAttribute.format.hasLink - case .unorderedList, .editText, .ai, .addFile, .addPhoto, .takePhoto, .programMessage: - return false - } - } -} diff --git a/Mail/Views/New Message/ComposeMessageBodyView.swift b/Mail/Views/New Message/ComposeMessageBodyView.swift index 4d00248507..9970e3e33a 100644 --- a/Mail/Views/New Message/ComposeMessageBodyView.swift +++ b/Mail/Views/New Message/ComposeMessageBodyView.swift @@ -24,14 +24,13 @@ import SwiftModalPresentation import SwiftUI struct ComposeMessageBodyView: View { - @State private var height = CGFloat.zero @State private var isShowingCamera = false @ModalState(context: ContextKeys.compose) private var isShowingFileSelection = false @ModalState(context: ContextKeys.compose) private var isShowingPhotoLibrary = false @ObservedRealmObject var draft: Draft - @Binding var editorModel: RichTextEditorModel + @Binding var editorModel: EditorModel @Binding var editorFocus: Bool @Binding var currentSignature: Signature? @Binding var isShowingAIPrompt: Bool @@ -51,14 +50,14 @@ struct ComposeMessageBodyView: View { EditorView( body: $draft.body, - height: $height, + model: $editorModel, isShowingFileSelection: $isShowingFileSelection, isShowingCamera: $isShowingCamera, isShowingPhotoLibrary: $isShowingPhotoLibrary, isShowingAIPrompt: $isShowingAIPrompt, isShowingAlert: $isShowingAlert ) - .frame(height: height) + .frame(height: editorModel.height) } .fullScreenCover(isPresented: $isShowingCamera) { CameraPicker { data in @@ -96,7 +95,7 @@ struct ComposeMessageBodyView: View { #Preview { let draft = Draft() return ComposeMessageBodyView(draft: draft, - editorModel: .constant(RichTextEditorModel()), + editorModel: .constant(EditorModel()), editorFocus: .constant(false), currentSignature: .constant(nil), isShowingAIPrompt: .constant(false), diff --git a/Mail/Views/New Message/ComposeMessageView.swift b/Mail/Views/New Message/ComposeMessageView.swift index 75bf515124..f95542aabd 100644 --- a/Mail/Views/New Message/ComposeMessageView.swift +++ b/Mail/Views/New Message/ComposeMessageView.swift @@ -84,7 +84,7 @@ struct ComposeMessageView: View { @State private var currentSignature: Signature? @State private var initialAttachments = [Attachable]() - @State private var editorModel = RichTextEditorModel() + @State private var editorModel = EditorModel() @Weak private var scrollView: UIScrollView? @StateObject private var attachmentsManager: AttachmentsManager @@ -192,16 +192,17 @@ struct ComposeMessageView: View { self.scrollView = scrollView scrollView.keyboardDismissMode = .interactive } - .onChange(of: editorModel.height) { _ in - guard let scrollView else { return } - - let fullSize = scrollView.contentSize.height - let realPosition = (fullSize - editorModel.height) + editorModel.cursorPosition - - guard realPosition >= 0 else { return } - let rect = CGRect(x: 0, y: realPosition, width: 1, height: 1) - scrollView.scrollRectToVisible(rect, animated: true) - } +// .onChange(of: editorModel.height) { _ in +// guard let scrollView else { return } +// +// let fullSize = scrollView.contentSize.height +// let realPosition = (fullSize - editorModel.height) + editorModel.cursorPosition +// +// guard realPosition >= 0 else { return } +// let rect = CGRect(x: 0, y: realPosition, width: 1, height: 1) +// scrollView.scrollRectToVisible(rect, animated: true) +// } + .onChange(of: editorModel.cursorPosition, perform: keepCursorVisible) .onChange(of: autocompletionType) { newValue in guard newValue != nil else { return } @@ -352,6 +353,12 @@ struct ComposeMessageView: View { } dismissMessageView() } + + private func keepCursorVisible(_ cursorPosition: CGPoint?) { + guard let scrollView, let cursorPosition else { + return + } + } } #Preview { diff --git a/Mail/Views/New Message/MailRichEditor/EditorCoordinator.swift b/Mail/Views/New Message/MailRichEditor/EditorCoordinator.swift index 5468ddcdf4..d750ef94f5 100644 --- a/Mail/Views/New Message/MailRichEditor/EditorCoordinator.swift +++ b/Mail/Views/New Message/MailRichEditor/EditorCoordinator.swift @@ -25,7 +25,7 @@ final class EditorCoordinator { let toolbar: UIToolbar! private(set) var parent: EditorView - private(set) var toolbarStyle = ToolbarStyle.main + private(set) var toolbarStyle = EditorToolbarStyle.main init(parent: EditorView) { self.parent = parent @@ -50,9 +50,12 @@ extension EditorCoordinator: RichEditorViewDelegate { parent.body = richEditorView.html } + func richEditorViewDidChangeSelection(_ richEditorView: RichEditorView) { + parent.model.cursorPosition = richEditorView.selection?.anchorPoint + } + func richEditorView(_ richEditorView: RichEditorView, contentHeightDidChange contentHeight: CGFloat) { - parent.height = contentHeight - print("NEW HEIGHT", contentHeight) + parent.model.height = contentHeight } func richEditorView(_ richEditorView: RichEditorView, selectedTextAttributesDidChange textAttributes: RETextAttributes) { @@ -67,7 +70,7 @@ extension EditorCoordinator { UIConstants.applyComposeViewStyle(to: toolbar) } - public func updateToolbarItems(for richEditorView: RichEditorView, style: ToolbarStyle) { + public func updateToolbarItems(for richEditorView: RichEditorView, style: EditorToolbarStyle) { toolbarStyle = style let flexibleSpaceItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil) @@ -94,7 +97,7 @@ extension EditorCoordinator { } @objc private func onToolbarClick(_ sender: UIBarButtonItem, for richEditorView: RichEditorView) { - guard let toolbarAction = ToolbarAction(rawValue: sender.tag) else { return } + guard let toolbarAction = EditorToolbarAction(rawValue: sender.tag) else { return } switch toolbarAction { case .ai, .addFile, .addPhoto, .takePhoto, .programMessage: @@ -104,7 +107,7 @@ extension EditorCoordinator { } } - private func performAppAction(_ action: ToolbarAction) { + private func performAppAction(_ action: EditorToolbarAction) { switch action { case .ai: parent.isShowingAIPrompt = true @@ -121,10 +124,10 @@ extension EditorCoordinator { } } - private func performFormatAction(_ action: ToolbarAction, for richEditorView: RichEditorView) { + private func performFormatAction(_ action: EditorToolbarAction, for richEditorView: RichEditorView) { switch action { case .editText: - let newToolbarStyle: ToolbarStyle = toolbarStyle == .main ? .textEdition : .main + let newToolbarStyle: EditorToolbarStyle = toolbarStyle == .main ? .textEdition : .main updateToolbarItems(for: richEditorView, style: newToolbarStyle) case .bold: richEditorView.bold() diff --git a/Mail/Views/New Message/MailRichEditor/EditorToolbar.swift b/Mail/Views/New Message/MailRichEditor/EditorToolbar.swift new file mode 100644 index 0000000000..b0181cbe96 --- /dev/null +++ b/Mail/Views/New Message/MailRichEditor/EditorToolbar.swift @@ -0,0 +1,140 @@ +/* + Infomaniak Mail - iOS App + Copyright (C) 2024 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import InfomaniakDI +import InfomaniakRichEditor +import MailCore +import MailResources +import UIKit + +enum EditorToolbarStyle { + case main + case textEdition + + var actions: [EditorToolbarAction] { + switch self { + case .main: + @InjectService var featureFlagsManageable: FeatureFlagsManageable + var mainActions: [EditorToolbarAction] = [.editText, .addFile, .addPhoto, .takePhoto, .link] + featureFlagsManageable.feature(.aiMailComposer, on: { + mainActions.insert(.ai, at: 1) + }, off: nil) + return mainActions + case .textEdition: + return [.editText, .bold, .italic, .underline, .strikeThrough, .unorderedList] + } + } +} + +enum EditorToolbarAction: Int { + case bold = 1 + case italic + case underline + case strikeThrough + case unorderedList + case editText + case ai + case addFile + case addPhoto + case takePhoto + case link + case programMessage + + var icon: UIImage { + switch self { + case .bold: + return MailResourcesAsset.bold.image + case .italic: + return MailResourcesAsset.italic.image + case .underline: + return MailResourcesAsset.underline.image + case .strikeThrough: + return MailResourcesAsset.strikeThrough.image + case .unorderedList: + return MailResourcesAsset.unorderedList.image + case .editText: + return MailResourcesAsset.textModes.image + case .ai: + return MailResourcesAsset.aiWriter.image + case .addFile: + return MailResourcesAsset.folder.image + case .addPhoto: + return MailResourcesAsset.pictureLandscape.image + case .takePhoto: + return MailResourcesAsset.photo.image + case .link: + return MailResourcesAsset.hyperlink.image + case .programMessage: + return MailResourcesAsset.waitingMessage.image + } + } + + var tint: UIColor { + if self == .ai { + return MailResourcesAsset.aiColor.color + } else { + return MailResourcesAsset.textSecondaryColor.color + } + } + + var matomoName: String? { + switch self { + case .bold: + return "bold" + case .italic: + return "italic" + case .underline: + return "underline" + case .strikeThrough: + return "strikeThrough" + case .unorderedList: + return "unorderedList" + case .ai: + return "aiWriter" + case .addFile: + return "importFile" + case .addPhoto: + return "importImage" + case .takePhoto: + return "importFromCamera" + case .link: + return "addLink" + case .programMessage: + return "postpone" + default: + return nil + } + } + + func isSelected(textAttributes: RETextAttributes) -> Bool { + switch self { + case .bold: + return textAttributes.format.hasBold + case .italic: + return textAttributes.format.hasItalic + case .underline: + return textAttributes.format.hasUnderline + case .strikeThrough: + return textAttributes.format.hasStrikeThrough + case .link: + return false + case .unorderedList, .editText, .ai, .addFile, .addPhoto, .takePhoto, .programMessage: + return false + } + } +} diff --git a/Mail/Views/New Message/MailRichEditor/EditorView.swift b/Mail/Views/New Message/MailRichEditor/EditorView.swift index 55594c2ffe..1348192721 100644 --- a/Mail/Views/New Message/MailRichEditor/EditorView.swift +++ b/Mail/Views/New Message/MailRichEditor/EditorView.swift @@ -20,9 +20,16 @@ import InfomaniakRichEditor import MailCore import SwiftUI +struct EditorModel { + var height: CGFloat = .zero + var cursorPosition: CGPoint? = .zero +} + struct EditorView: UIViewRepresentable { @Binding var body: String - @Binding var height: CGFloat + + @Binding var model: EditorModel + @Binding var isShowingFileSelection: Bool @Binding var isShowingCamera: Bool @Binding var isShowingPhotoLibrary: Bool @@ -50,7 +57,7 @@ struct EditorView: UIViewRepresentable { #Preview { EditorView(body: .constant(""), - height: .constant(.zero), + model: .constant(EditorModel()), isShowingFileSelection: .constant(false), isShowingCamera: .constant(false), isShowingPhotoLibrary: .constant(false),