diff --git a/GliaWidgets.xcodeproj/project.pbxproj b/GliaWidgets.xcodeproj/project.pbxproj index c5ed9821f..2ee5affa1 100644 --- a/GliaWidgets.xcodeproj/project.pbxproj +++ b/GliaWidgets.xcodeproj/project.pbxproj @@ -554,6 +554,7 @@ C0175A282A67D470001FACDE /* GvaPersistentButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0175A272A67D470001FACDE /* GvaPersistentButtonStyle.swift */; }; C0175A2A2A67D499001FACDE /* GvaStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0175A292A67D499001FACDE /* GvaStyle.swift */; }; C0175A2C2A67E2E9001FACDE /* Theme+Gva.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0175A2B2A67E2E9001FACDE /* Theme+Gva.swift */; }; + C0175A2E2A78F7F0001FACDE /* MetadataWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0175A2D2A78F7F0001FACDE /* MetadataWrapper.swift */; }; C03A8047292BA76D00DDECA6 /* ChatViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03A8046292BA76D00DDECA6 /* ChatViewControllerTests.swift */; }; C03A8049292BC8DB00DDECA6 /* CallViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03A8048292BC8DB00DDECA6 /* CallViewControllerTests.swift */; }; C05AB01C295F416700AA381F /* VisitorCodeCloseButtonProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05AB01B295F416700AA381F /* VisitorCodeCloseButtonProperties.swift */; }; @@ -1245,6 +1246,7 @@ C0175A272A67D470001FACDE /* GvaPersistentButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GvaPersistentButtonStyle.swift; sourceTree = ""; }; C0175A292A67D499001FACDE /* GvaStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GvaStyle.swift; sourceTree = ""; }; C0175A2B2A67E2E9001FACDE /* Theme+Gva.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Gva.swift"; sourceTree = ""; }; + C0175A2D2A78F7F0001FACDE /* MetadataWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataWrapper.swift; sourceTree = ""; }; C03A8046292BA76D00DDECA6 /* ChatViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatViewControllerTests.swift; sourceTree = ""; }; C03A8048292BC8DB00DDECA6 /* CallViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallViewControllerTests.swift; sourceTree = ""; }; C05AB016295DA9FC00AA381F /* AlertViewController+VisitorCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlertViewController+VisitorCode.swift"; sourceTree = ""; }; @@ -2053,6 +2055,7 @@ children = ( 1A60AFDF25669A6100E53F53 /* ChatViewController.swift */, 9AB196E127C4045B00FD60AB /* ChatViewController.Mock.swift */, + C0175A2D2A78F7F0001FACDE /* MetadataWrapper.swift */, ); path = Chat; sourceTree = ""; @@ -3944,7 +3947,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "make write-diff\n"; + shellScript = "#make write-diff\n"; }; A5633E9F76E68066D5BFAF62 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -4389,6 +4392,7 @@ 9A1992ED27D6C19E00161AAE /* FileSystemStorage.Environment.Mock.swift in Sources */, 1AFB1E6825F7AE3C00CA460D /* ChatAttachment.swift in Sources */, 9A186A3527F5CF3C0055886D /* FileUploadStyle.Accessibility.swift in Sources */, + C0175A2E2A78F7F0001FACDE /* MetadataWrapper.swift in Sources */, 84A318A12869ECFC00CA1DE5 /* Unavailable.swift in Sources */, 1AE15E49257A6BD200A642C0 /* ConfirmationAlertConfiguration.swift in Sources */, C0175A172A5D30D7001FACDE /* GvaResponseTextView.swift in Sources */, diff --git a/GliaWidgets/Sources/Theme/Theme+Chat.swift b/GliaWidgets/Sources/Theme/Theme+Chat.swift index f6e325b82..5e3730be5 100644 --- a/GliaWidgets/Sources/Theme/Theme+Chat.swift +++ b/GliaWidgets/Sources/Theme/Theme+Chat.swift @@ -153,6 +153,7 @@ extension Theme { let visitorText = ChatTextContentStyle( textFont: font.bodyText, textColor: color.baseLight, + textStyle: .body, backgroundColor: color.primary, accessibility: .init(isFontScalingEnabled: true) ) @@ -177,6 +178,7 @@ extension Theme { let operatorText = ChatTextContentStyle( textFont: font.bodyText, textColor: color.baseDark, + textStyle: .body, backgroundColor: Color.lightGrey, accessibility: .init(isFontScalingEnabled: true) ) @@ -201,6 +203,7 @@ extension Theme { let choiceCardText = ChatTextContentStyle( textFont: font.bodyText, textColor: color.baseDark, + textStyle: .body, backgroundColor: color.baseLight, accessibility: .init(isFontScalingEnabled: true) ) @@ -215,6 +218,7 @@ extension Theme { let choiceCardOptionNormalState = ChoiceCardOptionStateStyle( textFont: font.bodyText, textColor: color.baseDark, + textStyle: .body, backgroundColor: Color.lightGrey, borderColor: nil, accessibility: .init( @@ -225,6 +229,7 @@ extension Theme { let choiceCardOptionSelectedState = ChoiceCardOptionStateStyle( textFont: font.bodyText, textColor: color.baseLight, + textStyle: .body, backgroundColor: color.primary, borderColor: nil, accessibility: .init( @@ -235,6 +240,7 @@ extension Theme { let choiceCardOptionDisabledState = ChoiceCardOptionStateStyle( textFont: font.bodyText, textColor: Color.grey, + textStyle: .body, backgroundColor: Color.lightGrey, borderColor: Color.baseShade, accessibility: .init( diff --git a/GliaWidgets/Sources/Theme/Theme+Gva.swift b/GliaWidgets/Sources/Theme/Theme+Gva.swift index b95e38312..6b8764a5d 100644 --- a/GliaWidgets/Sources/Theme/Theme+Gva.swift +++ b/GliaWidgets/Sources/Theme/Theme+Gva.swift @@ -8,7 +8,9 @@ extension Theme { title: .init( textFont: font.bodyText, textColor: .black, - backgroundColor: .clear + textStyle: .body, + backgroundColor: .clear, + accessibility: .init(isFontScalingEnabled: true) ), backgroundColor: .fill(color: color.lightGrey), cornerRadius: 10, @@ -20,7 +22,8 @@ extension Theme { backgroundColor: .fill(color: color.background), cornerRadius: 5, borderColor: .clear, - borderWidth: 0 + borderWidth: 0, + accessibility: .init(isFontScalingEnabled: true) ) ) diff --git a/GliaWidgets/Sources/View/Chat/ChatView.swift b/GliaWidgets/Sources/View/Chat/ChatView.swift index 2fd4dc8b9..904d087f4 100644 --- a/GliaWidgets/Sources/View/Chat/ChatView.swift +++ b/GliaWidgets/Sources/View/Chat/ChatView.swift @@ -855,7 +855,7 @@ extension ChatView { private func gvaResponseTextView( _ message: ChatMessage, - text: NSAttributedString, + text: NSMutableAttributedString, showImage: Bool, imageUrl: String? ) -> GvaResponseTextView { diff --git a/GliaWidgets/Sources/View/Chat/GVA/GvaPersistentButtonOptionView.swift b/GliaWidgets/Sources/View/Chat/GVA/GvaPersistentButtonOptionView.swift index 39705c5b5..17cd50d60 100644 --- a/GliaWidgets/Sources/View/Chat/GVA/GvaPersistentButtonOptionView.swift +++ b/GliaWidgets/Sources/View/Chat/GVA/GvaPersistentButtonOptionView.swift @@ -25,6 +25,9 @@ class GvaPersistentButtonOptionView: BaseView { override func setup() { super.setup() + isAccessibilityElement = true + accessibilityLabel = text + accessibilityTraits = .button layer.cornerRadius = style.cornerRadius layer.borderWidth = style.borderWidth layer.borderColor = style.borderColor.cgColor @@ -36,6 +39,11 @@ class GvaPersistentButtonOptionView: BaseView { textLabel.numberOfLines = 0 textLabel.isAccessibilityElement = false + setFontScalingEnabled( + style.accessibility.isFontScalingEnabled, + for: textLabel + ) + choiceButton.addTarget(self, action: #selector(onTap), for: .touchUpInside) } @@ -43,7 +51,7 @@ class GvaPersistentButtonOptionView: BaseView { super.defineLayout() var constraints = [NSLayoutConstraint](); defer { constraints.activate() } - heightAnchor.constraint(equalToConstant: 42).isActive = true + heightAnchor.constraint(greaterThanOrEqualToConstant: 42).isActive = true addSubview(textLabel) textLabel.translatesAutoresizingMaskIntoConstraints = false constraints += textLabel.layoutInSuperview(insets: viewInsets) @@ -65,22 +73,6 @@ class GvaPersistentButtonOptionView: BaseView { } } - private func applyStyle(_ style: ChoiceCardOptionStateStyle) { - setFontScalingEnabled( - style.accessibility.isFontScalingEnabled, - for: textLabel - ) - - UIView.transition(with: textLabel, duration: 0.2, options: .transitionCrossDissolve) { - self.layer.backgroundColor = style.backgroundColor.cgColor - self.textLabel.textColor = style.textColor - if let borderColor = style.borderColor { - self.layer.borderColor = borderColor.cgColor - self.layer.borderWidth = style.borderWidth - } - } - } - @objc private func onTap() { tap?() } diff --git a/GliaWidgets/Sources/View/Chat/GVA/GvaPersistentButtonStyle.swift b/GliaWidgets/Sources/View/Chat/GVA/GvaPersistentButtonStyle.swift index cb127e962..ecde87d78 100644 --- a/GliaWidgets/Sources/View/Chat/GVA/GvaPersistentButtonStyle.swift +++ b/GliaWidgets/Sources/View/Chat/GVA/GvaPersistentButtonStyle.swift @@ -112,6 +112,9 @@ extension GvaPersistentButtonStyle { /// Border width of the button public var borderWidth: CGFloat + /// Accessibility + public var accessibility: Accessibility + init( textFont: UIFont, textColor: UIColor, @@ -119,7 +122,8 @@ extension GvaPersistentButtonStyle { backgroundColor: ColorType, cornerRadius: CGFloat, borderColor: UIColor, - borderWidth: CGFloat + borderWidth: CGFloat, + accessibility: Accessibility = .unsupported ) { self.textFont = textFont self.textColor = textColor @@ -128,6 +132,7 @@ extension GvaPersistentButtonStyle { self.cornerRadius = cornerRadius self.borderColor = borderColor self.borderWidth = borderWidth + self.accessibility = accessibility } mutating func apply( @@ -180,3 +185,32 @@ extension GvaPersistentButtonStyle { } } } + +extension GvaPersistentButtonStyle { + /// Accessibility properties for ChoiceCardOptionStateStyle. + public struct Accessibility: Equatable { + /// Accessibility value. + public var value: String + + /// Flag that provides font dynamic type by setting `adjustsFontForContentSizeCategory` for component that supports it. + public var isFontScalingEnabled: Bool + + /// + /// - Parameters: + /// - value: Accessibility value. + /// - isFontScalingEnabled: Flag that provides font dynamic type by setting `adjustsFontForContentSizeCategory` for component that supports it. + public init( + value: String = "", + isFontScalingEnabled: Bool + ) { + self.value = value + self.isFontScalingEnabled = isFontScalingEnabled + } + + /// Accessibility is not supported intentionally. + public static let unsupported = Self( + value: "", + isFontScalingEnabled: false + ) + } +} diff --git a/GliaWidgets/Sources/View/Chat/Message/Content/ChatMessageContent.swift b/GliaWidgets/Sources/View/Chat/Message/Content/ChatMessageContent.swift index e65386800..49d28c769 100644 --- a/GliaWidgets/Sources/View/Chat/Message/Content/ChatMessageContent.swift +++ b/GliaWidgets/Sources/View/Chat/Message/Content/ChatMessageContent.swift @@ -6,7 +6,7 @@ enum ChatMessageContent { case downloads([FileDownload], accessibility: ChatFileContentView.AccessibilityProperties) case choiceCard(ChoiceCard) case gvaPersistentButton(GvaButton) - case attributedText(NSAttributedString, accessibility: TextAccessibilityProperties) + case attributedText(NSMutableAttributedString, accessibility: TextAccessibilityProperties) struct TextAccessibilityProperties { let label: String diff --git a/GliaWidgets/Sources/View/Chat/Message/Content/ChoiceCard/ChoiceCardOptionStateStyle.swift b/GliaWidgets/Sources/View/Chat/Message/Content/ChoiceCard/ChoiceCardOptionStateStyle.swift index cd5598e15..c2d9495d3 100644 --- a/GliaWidgets/Sources/View/Chat/Message/Content/ChoiceCard/ChoiceCardOptionStateStyle.swift +++ b/GliaWidgets/Sources/View/Chat/Message/Content/ChoiceCard/ChoiceCardOptionStateStyle.swift @@ -18,6 +18,7 @@ public final class ChoiceCardOptionStateStyle: ChatTextContentStyle { public init( textFont: UIFont, textColor: UIColor, + textStyle: UIFont.TextStyle, backgroundColor: UIColor, borderColor: UIColor?, borderWidth: CGFloat = 1, @@ -28,6 +29,7 @@ public final class ChoiceCardOptionStateStyle: ChatTextContentStyle { super.init( textFont: textFont, textColor: textColor, + textStyle: textStyle, backgroundColor: backgroundColor, accessibility: accessibility ) diff --git a/GliaWidgets/Sources/View/Chat/Message/Content/Text/ChatTextContentStyle.swift b/GliaWidgets/Sources/View/Chat/Message/Content/Text/ChatTextContentStyle.swift index 931936652..3d105f4c4 100644 --- a/GliaWidgets/Sources/View/Chat/Message/Content/Text/ChatTextContentStyle.swift +++ b/GliaWidgets/Sources/View/Chat/Message/Content/Text/ChatTextContentStyle.swift @@ -24,17 +24,17 @@ public class ChatTextContentStyle { /// - Parameters: /// - textFont: Font of the message text. /// - textColor: Color of the message text. - /// - textStyle: Text style of the message text. + /// - textStyle: Text style of the message text. Necessary for attributed strings. /// - backgroundColor: Background color of the content view. /// - accessibility: Accessibility related properties. /// public init( textFont: UIFont, textColor: UIColor, - textStyle: UIFont.TextStyle = .body, + textStyle: UIFont.TextStyle, backgroundColor: UIColor, cornerRadius: CGFloat = 8.49, - accessibility: Accessibility = .unsupported + accessibility: Accessibility ) { self.textFont = textFont self.textColor = textColor diff --git a/GliaWidgets/Sources/View/Chat/Message/Content/Text/ChatTextContentView.swift b/GliaWidgets/Sources/View/Chat/Message/Content/Text/ChatTextContentView.swift index 2d9360aaf..5f736669c 100644 --- a/GliaWidgets/Sources/View/Chat/Message/Content/Text/ChatTextContentView.swift +++ b/GliaWidgets/Sources/View/Chat/Message/Content/Text/ChatTextContentView.swift @@ -6,8 +6,8 @@ class ChatTextContentView: BaseView { set { setText(newValue) } } - var attributedText: NSAttributedString? { - get { return textView.attributedText } + var attributedText: NSMutableAttributedString? { + get { return textView.attributedText as? NSMutableAttributedString } set { return setAttributedText(newValue) } } @@ -68,7 +68,6 @@ class ChatTextContentView: BaseView { textView.font = style.textFont textView.backgroundColor = .clear textView.textColor = style.textColor - textView.isAccessibilityElement = false setFontScalingEnabled( style.accessibility.isFontScalingEnabled, @@ -110,7 +109,8 @@ class ChatTextContentView: BaseView { textView.accessibilityIdentifier = text } - private func setAttributedText(_ text: NSAttributedString?) { + private func setAttributedText(_ text: NSMutableAttributedString?) { + print(textView.attributedText) guard let text, !text.string.isEmpty else { textView.removeFromSuperview() return @@ -124,21 +124,21 @@ class ChatTextContentView: BaseView { } let attributes: [NSAttributedString.Key: Any] = [ - .font: style.textFont, + .font: UIFont.preferredFont(forTextStyle: style.textStyle), .foregroundColor: style.textColor ] - let attributedText = NSMutableAttributedString(attributedString: text) - attributedText.addAttributes( + text.addAttributes( attributes, range: NSRange( location: 0, - length: attributedText.length + length: text.length ) ) - textView.attributedText = attributedText + textView.attributedText = text textView.accessibilityIdentifier = text.string + print(textView.attributedText) } } @@ -168,6 +168,7 @@ extension ChatTextContentView { with: ChatTextContentStyle( textFont: .systemFont(ofSize: 10), textColor: .black, + textStyle: .body, backgroundColor: .black, accessibility: .unsupported ), diff --git a/GliaWidgets/Sources/ViewController/Chat/ChatViewController.Mock.swift b/GliaWidgets/Sources/ViewController/Chat/ChatViewController.Mock.swift index 7508e902d..a464f3673 100644 --- a/GliaWidgets/Sources/ViewController/Chat/ChatViewController.Mock.swift +++ b/GliaWidgets/Sources/ViewController/Chat/ChatViewController.Mock.swift @@ -1,6 +1,7 @@ #if DEBUG import Foundation import UIKit +import GliaCoreSDK // swiftlint:disable function_body_length extension ChatViewController { @@ -366,6 +367,53 @@ extension ChatViewController { return controller } + // MARK: - Glia Virtual Assistant Persistent Button State + + static func mockGvaPersistentButton() throws -> ChatViewController { + var chatViewModelEnv = ChatViewModel.Environment.mock + chatViewModelEnv.loadChatMessagesFromHistory = { true } + + let messageUuid = UUID.incrementing + let messageId = { messageUuid().uuidString } + let queueId = UUID.mock.uuidString + + let jsonData = mockGvaPersistentButtonJson() + let metadataWrapper = try JSONDecoder().decode(MetadataWrapper.self, from: jsonData ?? Data()) + let metadata = Message.Metadata(container: metadataWrapper.container) + + let messages: [ChatMessage] = [ + .mock(id: messageId(), + queueID: queueId, + operator: .mock( + name: "Rasmus", + pictureUrl: URL.mock.appendingPathComponent("opImage").appendingPathExtension("png").absoluteString + ), + sender: .operator, + content: "", + attachment: nil, + downloads: [], + metadata: metadata + ) + ] + + chatViewModelEnv.fetchChatHistory = { $0(.success(messages)) } + + var viewFactoryEnv = ViewFactory.Environment.mock + viewFactoryEnv.imageViewCache.getImageForKey = { _ in UIImage.mock } + + let chatViewModel = ChatViewModel.mock(environment: chatViewModelEnv) + let controller = ChatViewController.mock( + chatViewModel: chatViewModel, + viewFactory: .init( + with: .mock(), + messageRenderer: .mock, + environment: viewFactoryEnv + ) + ) + chatViewModel.action?(.setMessageText("Input Message Mock")) + return controller + } + // MARK: - Visitor File Download States static func mockVisitorFileDownloadStates(completion: ([ChatMessage]) -> Void) throws -> ChatViewController { var chatViewModelEnv = ChatViewModel.Environment.mock @@ -413,6 +461,32 @@ extension ChatViewController { completion(messages) return controller } + + static private func mockGvaPersistentButtonJson() -> Data? { + """ + { + "metadata": + { + "type" : "persistentButtons", + "content" : "This is a Glia Virutal Assistant Persistent button.", + "options" : [ + { + "value" : "I'm first button", + "text" : "First Button" + }, + { + "value" : "I'm second button", + "text" : "Second Button" + }, + { + "value" : "I'm third button", + "text" : "Third Button" + } + ] + } + } + """.data(using: .utf8) + } } // swiftlint:enable function_body_length #endif diff --git a/GliaWidgets/Sources/ViewController/Chat/MetadataWrapper.swift b/GliaWidgets/Sources/ViewController/Chat/MetadataWrapper.swift new file mode 100644 index 000000000..a19a0fcab --- /dev/null +++ b/GliaWidgets/Sources/ViewController/Chat/MetadataWrapper.swift @@ -0,0 +1,13 @@ +#if DEBUG +import Foundation +import UIKit +import GliaCoreSDK + +struct MetadataWrapper: Decodable { + let container: KeyedDecodingContainer + + init(from decoder: Decoder) throws { + self.container = try decoder.container(keyedBy: Message.Metadata.CodingKeys.self) + } +} +#endif diff --git a/GliaWidgets/Sources/ViewModel/Chat/Data/ChatMessage.Mock.swift b/GliaWidgets/Sources/ViewModel/Chat/Data/ChatMessage.Mock.swift index 4380efbd4..6a1aa42ac 100644 --- a/GliaWidgets/Sources/ViewModel/Chat/Data/ChatMessage.Mock.swift +++ b/GliaWidgets/Sources/ViewModel/Chat/Data/ChatMessage.Mock.swift @@ -7,7 +7,8 @@ extension ChatMessage { sender: ChatMessageSender = .visitor, content: String = "", attachment: ChatAttachment? = nil, - downloads: [FileDownload] = [] + downloads: [FileDownload] = [], + metadata: MessageMetadata? = nil ) -> ChatMessage { .init( id: id, @@ -16,7 +17,8 @@ extension ChatMessage { sender: sender, content: content, attachment: attachment, - downloads: downloads + downloads: downloads, + metadata: metadata ) } } diff --git a/GliaWidgets/Sources/ViewModel/Chat/Data/Gva.swift b/GliaWidgets/Sources/ViewModel/Chat/Data/Gva.swift index 736a21566..84a0987cf 100644 --- a/GliaWidgets/Sources/ViewModel/Chat/Data/Gva.swift +++ b/GliaWidgets/Sources/ViewModel/Chat/Data/Gva.swift @@ -2,7 +2,7 @@ import Foundation struct GvaResponseText: Decodable, Equatable { let type: GvaCardType - let content: NSAttributedString + let content: NSMutableAttributedString enum CodingKeys: String, CodingKey { case type, content @@ -13,27 +13,44 @@ struct GvaResponseText: Decodable, Equatable { type = try container.decode(GvaCardType.self, forKey: .type) let contentString = try container.decode(String.self, forKey: .content) let modifiedString = contentString.replacingOccurrences(of: "\n", with: "
") - content = modifiedString.htmlToAttributedString ?? NSAttributedString(string: "") + content = modifiedString.htmlToAttributedString ?? NSMutableAttributedString(string: "") } } -struct GvaButton: Decodable, Equatable { +struct GvaButton: Codable, Equatable { let type: GvaCardType - let content: NSAttributedString + let content: NSMutableAttributedString let options: [GvaOption] enum CodingKeys: String, CodingKey { case type, content, options } + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(type, forKey: .type) + try container.encode(content.string, forKey: .content) + try container.encode(options, forKey: .options) + } + init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) type = try container.decode(GvaCardType.self, forKey: .type) let contentString = try container.decode(String.self, forKey: .content) let modifiedString = contentString.replacingOccurrences(of: "\n", with: "
") - content = modifiedString.htmlToAttributedString ?? NSAttributedString(string: "") + content = modifiedString.htmlToAttributedString ?? NSMutableAttributedString(string: "") options = try container.decode([GvaOption].self, forKey: .options) } + + init( + type: GvaCardType, + content: NSMutableAttributedString, + options: [GvaOption] + ) { + self.type = type + self.content = content + self.options = options + } } struct GvaGallery: Decodable, Equatable { @@ -48,7 +65,7 @@ struct GvaGalleryCard: Decodable, Equatable { let options: [GvaOption]? } -struct GvaOption: Decodable, Equatable { +struct GvaOption: Codable, Equatable { let text: String let value: String? let url: String? @@ -68,7 +85,7 @@ enum GvaUrlTarget: String, Decodable { } } -enum GvaCardType: String, Decodable { +enum GvaCardType: String, Codable { case persistentButtons case quickReplies case plainText @@ -76,7 +93,7 @@ enum GvaCardType: String, Decodable { } private extension StringProtocol { - var htmlToAttributedString: NSAttributedString? { + var htmlToAttributedString: NSMutableAttributedString? { Data(utf8).htmlToAttributedString } var htmlToString: String { @@ -85,13 +102,13 @@ private extension StringProtocol { } private extension Data { - var htmlToAttributedString: NSAttributedString? { + var htmlToAttributedString: NSMutableAttributedString? { do { let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [ .documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue ] - return try NSAttributedString( + return try NSMutableAttributedString( data: self, options: options, documentAttributes: nil diff --git a/SnapshotTests/ChatViewControllerLayoutTests.swift b/SnapshotTests/ChatViewControllerLayoutTests.swift index 75aedd062..e30007365 100644 --- a/SnapshotTests/ChatViewControllerLayoutTests.swift +++ b/SnapshotTests/ChatViewControllerLayoutTests.swift @@ -33,6 +33,17 @@ class ChatViewControllerLayoutTests: SnapshotTestCase { ) } + func test_gvaPersistentButton() throws { + let viewController = try ChatViewController.mockGvaPersistentButton() + viewController.view.frame = UIScreen.main.bounds + assertSnapshot( + matching: viewController, + as: .image, + named: nameForDevice(), + record: true + ) + } + func test_visitorFileDownloadStates() throws { var chatMessages: [ChatMessage] = [] let viewController = try ChatViewController.mockVisitorFileDownloadStates { messages in diff --git a/SnapshotTests/ChatViewControllerVoiceOverTests.swift b/SnapshotTests/ChatViewControllerVoiceOverTests.swift index b3ff42f1b..88241ea6a 100644 --- a/SnapshotTests/ChatViewControllerVoiceOverTests.swift +++ b/SnapshotTests/ChatViewControllerVoiceOverTests.swift @@ -53,4 +53,15 @@ class ChatViewControllerVoiceOverTests: SnapshotTestCase { named: self.nameForDevice() ) } + + func test_gvaPersistentButton() throws { + let viewController = try ChatViewController.mockGvaPersistentButton() + viewController.view.frame = UIScreen.main.bounds + assertSnapshot( + matching: viewController, + as: .accessibilityImage(precision: Self.possiblePrecision), + named: nameForDevice(), + record: true + ) + } }