diff --git a/Mail/Views/AI Writer/AIModel.swift b/Mail/Views/AI Writer/AIModel.swift index ac2d0b302..629b3981f 100644 --- a/Mail/Views/AI Writer/AIModel.swift +++ b/Mail/Views/AI Writer/AIModel.swift @@ -63,15 +63,11 @@ final class AIModel: ObservableObject { func executeShortcut(_ shortcut: AIShortcutAction) async { if shortcut == .edit { - withAnimation { - conversation.append(AIMessage(type: .assistant, content: MailResourcesStrings.Localizable.aiMenuEditRequest)) - } + conversation.append(AIMessage(type: .assistant, content: MailResourcesStrings.Localizable.aiMenuEditRequest)) displayView(.prompt) } else { guard let contextId else { return } - withAnimation { - isLoading = true - } + isLoading = true do { let response = try await mailboxManager.apiFetcher.aiShortcut(contextId: contextId, shortcut: shortcut) handleAIResponse(response) @@ -100,25 +96,21 @@ final class AIModel: ObservableObject { contextId = newContextId } - withAnimation { - if let shortcutResponse = response as? AIShortcutResponse { - conversation.append(shortcutResponse.action) - } - conversation.append(AIMessage(type: .assistant, content: response.content)) - isLoading = false + if let shortcutResponse = response as? AIShortcutResponse { + conversation.append(shortcutResponse.action) } + conversation.append(AIMessage(type: .assistant, content: response.content)) + isLoading = false } private func handleError(_ error: Error) { - withAnimation { - isLoading = false - - if let mailApiError = error as? MailApiError, - mailApiError == .apiAIMaxSyntaxTokensReached || mailApiError == .apiAITooManyRequests { - self.error = mailApiError.localizedDescription - } else { - self.error = MailResourcesStrings.Localizable.aiErrorUnknown - } + isLoading = false + + if let mailApiError = error as? MailApiError, + mailApiError == .apiAIMaxSyntaxTokensReached || mailApiError == .apiAITooManyRequests { + self.error = mailApiError.localizedDescription + } else { + self.error = MailResourcesStrings.Localizable.aiErrorUnknown } } } diff --git a/Mail/Views/AI Writer/AIProgressView.swift b/Mail/Views/AI Writer/AIProgressView.swift index 2e386e935..977e2552c 100644 --- a/Mail/Views/AI Writer/AIProgressView.swift +++ b/Mail/Views/AI Writer/AIProgressView.swift @@ -30,7 +30,6 @@ struct AIProgressView: View { .progressViewStyle(.circular) .tint(MailResourcesAsset.aiColor.swiftUIColor) } - .frame(height: UIConstants.buttonMediumHeight) } } diff --git a/Mail/Views/AI Writer/AIPropositionMenu.swift b/Mail/Views/AI Writer/AIPropositionMenu.swift index 361c59c52..07e9f4b24 100644 --- a/Mail/Views/AI Writer/AIPropositionMenu.swift +++ b/Mail/Views/AI Writer/AIPropositionMenu.swift @@ -58,7 +58,6 @@ struct AIPropositionMenu: View { .frame(width: 24, height: 24) Text(MailResourcesStrings.Localizable.aiButtonRefine) } - .frame(height: UIConstants.buttonMediumHeight) } .simultaneousGesture(TapGesture().onEnded { matomo.track(eventWithCategory: .aiWriter, name: "refine") diff --git a/Mail/Views/AI Writer/AIPropositionView.swift b/Mail/Views/AI Writer/AIPropositionView.swift index f922af252..2bf645be1 100644 --- a/Mail/Views/AI Writer/AIPropositionView.swift +++ b/Mail/Views/AI Writer/AIPropositionView.swift @@ -49,9 +49,7 @@ struct AIPropositionView: View { SelectableTextView( textPlainHeight: $textPlainHeight, text: aiModel.conversation.last?.content ?? "", - foregroundColor: aiModel.isLoading ? MailResourcesAsset.textTertiaryColor - .swiftUIColor : MailResourcesAsset - .textPrimaryColor.swiftUIColor + style: aiModel.isLoading ? .loading : .standard ) .frame(height: textPlainHeight) .tint(MailResourcesAsset.aiColor.swiftUIColor) @@ -60,9 +58,7 @@ struct AIPropositionView: View { .padding(.horizontal, value: .regular) } .onAppear { - withAnimation { - aiModel.isLoading = true - } + aiModel.isLoading = true } .task { await aiModel.createConversation() diff --git a/Mail/Views/Thread/SelectableTextView.swift b/Mail/Views/Thread/SelectableTextView.swift index 118e195b8..79792457e 100644 --- a/Mail/Views/Thread/SelectableTextView.swift +++ b/Mail/Views/Thread/SelectableTextView.swift @@ -18,14 +18,28 @@ import Foundation import MailCore +import MailResources import SwiftUI import UIKit struct SelectableTextView: UIViewRepresentable { + enum Style { + case loading, standard + + var foregroundColor: UIColor { + switch self { + case .loading: + return MailResourcesAsset.textTertiaryColor.color + case .standard: + return MailResourcesAsset.textPrimaryColor.color + } + } + } + @Binding var textPlainHeight: CGFloat let text: String? - var foregroundColor = MailTextStyle.body.color + var style = Style.standard func makeUIView(context: Context) -> UITextView { let textView = UITextView() @@ -34,7 +48,7 @@ struct SelectableTextView: UIViewRepresentable { textView.isEditable = false textView.textContainer.lineFragmentPadding = 0 textView.font = .systemFont(ofSize: 16) - textView.textColor = UIColor(foregroundColor) + textView.textColor = style.foregroundColor textView.linkTextAttributes = [.underlineStyle: 1, .foregroundColor: UIColor.tintColor] textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) @@ -42,12 +56,39 @@ struct SelectableTextView: UIViewRepresentable { } func updateUIView(_ uiView: UITextView, context: Context) { - Task { - uiView.text = text - uiView.textColor = UIColor(foregroundColor) + // Replace text when the style is standard and the current color has not yet been changed + if style == .standard && uiView.textColor != style.foregroundColor { + replaceText(text: text ?? "", in: uiView) + } else { + insertText(text: text ?? "", in: uiView) + } + } + private func insertText(text: String, in uiView: UITextView) { + uiView.text = text + uiView.textColor = style.foregroundColor + + computeViewHeight(uiView) + } + + private func replaceText(text: String, in uiView: UITextView) { + UIView.animate(withDuration: 0.2) { + uiView.alpha = 0 + } completion: { _ in + insertText(text: text, in: uiView) + UIView.animate(withDuration: 0.2) { + uiView.alpha = 1 + } + } + } + + private func computeViewHeight(_ uiView: UIView) { + Task { await MainActor.run { - let sizeThatFits = uiView.sizeThatFits(CGSize(width: uiView.frame.width, height: CGFloat.greatestFiniteMagnitude)) + let sizeThatFits = uiView.sizeThatFits(CGSize( + width: uiView.frame.width, + height: CGFloat.greatestFiniteMagnitude + )) textPlainHeight = sizeThatFits.height } }