diff --git a/GliaWidgets.xcodeproj/project.pbxproj b/GliaWidgets.xcodeproj/project.pbxproj index 7dd6c05aa..f0aaebb7c 100644 --- a/GliaWidgets.xcodeproj/project.pbxproj +++ b/GliaWidgets.xcodeproj/project.pbxproj @@ -590,6 +590,8 @@ C03A8049292BC8DB00DDECA6 /* CallViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03A8048292BC8DB00DDECA6 /* CallViewControllerTests.swift */; }; C05AB01C295F416700AA381F /* VisitorCodeCloseButtonProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05AB01B295F416700AA381F /* VisitorCodeCloseButtonProperties.swift */; }; C05E3EDE29C99E070013BC81 /* ProximityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05E3EDD29C99E070013BC81 /* ProximityManager.swift */; }; + C06152D52AB1BC1300063BF8 /* Font+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06152D42AB1BC1300063BF8 /* Font+Extensions.swift */; }; + C06152DA2AB1BC4300063BF8 /* OrientationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06152D92AB1BC4300063BF8 /* OrientationManager.swift */; }; C06A757F296EC76B006B69A2 /* VisitorCodeStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06A757E296EC76B006B69A2 /* VisitorCodeStyle.swift */; }; C06A7582296EC856006B69A2 /* NumberSlotStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06A7581296EC856006B69A2 /* NumberSlotStyle.swift */; }; C06A7584296EC9DC006B69A2 /* NumberSlotStyle.Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06A7583296EC9DC006B69A2 /* NumberSlotStyle.Accessibility.swift */; }; @@ -641,6 +643,9 @@ C0D2F08B29A4E95700803B47 /* ConnectView.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D2F08929A4E92D00803B47 /* ConnectView.Mock.swift */; }; C0D2F08C29A4EBA900803B47 /* VIdeoCallView.Environment.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D2F06F29A4DB5C00803B47 /* VIdeoCallView.Environment.Mock.swift */; }; C0D2F08F29A61A8D00803B47 /* VideoCallViewController.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D2F08D29A61A7800803B47 /* VideoCallViewController.Mock.swift */; }; + C0E948042AB1D5D200890026 /* ActionButtonSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E948032AB1D5D200890026 /* ActionButtonSwiftUI.swift */; }; + C0E948062AB1D64700890026 /* HeaderButtonSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E948052AB1D64700890026 /* HeaderButtonSwiftUI.swift */; }; + C0E948092AB1D6AB00890026 /* HeaderSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E948082AB1D6AB00890026 /* HeaderSwiftUI.swift */; }; C2B201AEDBE3A53369DF524F /* Pods_GliaWidgetsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CCF7E6C5499635E67EF6A604 /* Pods_GliaWidgetsTests.framework */; }; C4119E06268F41D1004DFEFB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C4119E05268F41D1004DFEFB /* Main.storyboard */; }; C42463742673ABE10082C135 /* ScreenShareHandler.Interface.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42463732673ABE10082C135 /* ScreenShareHandler.Interface.swift */; }; @@ -1322,6 +1327,8 @@ C05AB016295DA9FC00AA381F /* AlertViewController+VisitorCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlertViewController+VisitorCode.swift"; sourceTree = ""; }; C05AB01B295F416700AA381F /* VisitorCodeCloseButtonProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisitorCodeCloseButtonProperties.swift; sourceTree = ""; }; C05E3EDD29C99E070013BC81 /* ProximityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProximityManager.swift; sourceTree = ""; }; + C06152D42AB1BC1300063BF8 /* Font+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Font+Extensions.swift"; sourceTree = ""; }; + C06152D92AB1BC4300063BF8 /* OrientationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrientationManager.swift; sourceTree = ""; }; C06A757E296EC76B006B69A2 /* VisitorCodeStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisitorCodeStyle.swift; sourceTree = ""; }; C06A7581296EC856006B69A2 /* NumberSlotStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberSlotStyle.swift; sourceTree = ""; }; C06A7583296EC9DC006B69A2 /* NumberSlotStyle.Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberSlotStyle.Accessibility.swift; sourceTree = ""; }; @@ -1372,6 +1379,9 @@ C0D2F08629A4E8AE00803B47 /* CallButtonBar.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallButtonBar.Mock.swift; sourceTree = ""; }; C0D2F08929A4E92D00803B47 /* ConnectView.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectView.Mock.swift; sourceTree = ""; }; C0D2F08D29A61A7800803B47 /* VideoCallViewController.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCallViewController.Mock.swift; sourceTree = ""; }; + C0E948032AB1D5D200890026 /* ActionButtonSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonSwiftUI.swift; sourceTree = ""; }; + C0E948052AB1D64700890026 /* HeaderButtonSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderButtonSwiftUI.swift; sourceTree = ""; }; + C0E948082AB1D6AB00890026 /* HeaderSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderSwiftUI.swift; sourceTree = ""; }; C4119E05268F41D1004DFEFB /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; C42463732673ABE10082C135 /* ScreenShareHandler.Interface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenShareHandler.Interface.swift; sourceTree = ""; }; C43C12F82694B14900C37E1B /* GliaPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GliaPresenter.swift; sourceTree = ""; }; @@ -1644,6 +1654,7 @@ 1A205D5A25655CB1003AA3CD /* GliaWidgets */ = { isa = PBXGroup; children = ( + C06152D22AB1BBEF00063BF8 /* SwiftUI */, 313EBD53294310EE008E9597 /* SecureConversations */, 7594091329891C48008B173A /* Public */, 1A60AFC12566857200E53F53 /* Sources */, @@ -3503,6 +3514,32 @@ path = ProximityManager; sourceTree = ""; }; + C06152D22AB1BBEF00063BF8 /* SwiftUI */ = { + isa = PBXGroup; + children = ( + C0E948012AB1D5B100890026 /* Components */, + C06152D82AB1BC2F00063BF8 /* Managers */, + C06152D32AB1BBFD00063BF8 /* Extensions */, + ); + path = SwiftUI; + sourceTree = ""; + }; + C06152D32AB1BBFD00063BF8 /* Extensions */ = { + isa = PBXGroup; + children = ( + C06152D42AB1BC1300063BF8 /* Font+Extensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + C06152D82AB1BC2F00063BF8 /* Managers */ = { + isa = PBXGroup; + children = ( + C06152D92AB1BC4300063BF8 /* OrientationManager.swift */, + ); + path = Managers; + sourceTree = ""; + }; C06A757D296EC743006B69A2 /* VisitorCode */ = { isa = PBXGroup; children = ( @@ -3646,6 +3683,32 @@ path = Mocks; sourceTree = ""; }; + C0E948012AB1D5B100890026 /* Components */ = { + isa = PBXGroup; + children = ( + C0E948072AB1D69C00890026 /* Header */, + C0E948022AB1D5BC00890026 /* Buttons */, + ); + path = Components; + sourceTree = ""; + }; + C0E948022AB1D5BC00890026 /* Buttons */ = { + isa = PBXGroup; + children = ( + C0E948032AB1D5D200890026 /* ActionButtonSwiftUI.swift */, + C0E948052AB1D64700890026 /* HeaderButtonSwiftUI.swift */, + ); + path = Buttons; + sourceTree = ""; + }; + C0E948072AB1D69C00890026 /* Header */ = { + isa = PBXGroup; + children = ( + C0E948082AB1D6AB00890026 /* HeaderSwiftUI.swift */, + ); + path = Header; + sourceTree = ""; + }; C42463722673ABCD0082C135 /* Screensharing */ = { isa = PBXGroup; children = ( @@ -4159,7 +4222,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "make write-diff\n"; + shellScript = "#make write-diff\n"; }; A5633E9F76E68066D5BFAF62 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -4224,6 +4287,7 @@ 1A6075E7258220E300569B0E /* UserImageStyle.swift in Sources */, 1ABD6C5D25B59D1C00D56EFA /* BubbleWindow.swift in Sources */, 75940964298D3889008B173A /* MessageRenderer.Web.swift in Sources */, + C0E948042AB1D5D200890026 /* ActionButtonSwiftUI.swift in Sources */, 1AE15E38257A578B00A642C0 /* MessageAlertConfiguration.swift in Sources */, C0175A132A56E29E001FACDE /* ChatMessageCardType.swift in Sources */, AFBBF5782851C391004993B3 /* Glia.Deprecated.swift in Sources */, @@ -4314,6 +4378,7 @@ 9AE0A7622822AF3000725946 /* FontScaling.Environment.Live.swift in Sources */, 1A0452DD25DBD0A4000DA0C1 /* HeaderButton.swift in Sources */, AF3D520B2983235C00AD8E69 /* FileUploader.Mock.swift in Sources */, + C0E948092AB1D6AB00890026 /* HeaderSwiftUI.swift in Sources */, AF39330B29B2A6A00008B60D /* ChatViewModel.SecureConverstaions.swift in Sources */, 75AF8CEF27DAA819009EEE2C /* SurveyViewController.swift in Sources */, 1A60AF96256675C400E53F53 /* UIColor+Extensions.swift in Sources */, @@ -4413,6 +4478,7 @@ 9A19926B27D3BA8700161AAE /* ViewFactory.Environment.Mock.swift in Sources */, 84681A9B2A669D8800DD7406 /* QuickReplyView.swift in Sources */, 1A6EB05725A717CB0007081A /* ChatMessage.swift in Sources */, + C0E948062AB1D64700890026 /* HeaderButtonSwiftUI.swift in Sources */, 1AA738B225790D5A00E1120F /* AlertView.swift in Sources */, 845E2F8E283FB5B500C04D56 /* Theme.Survey.SingleQuestion.Accessibility.swift in Sources */, C47901B725ED2FB0007EE195 /* AlertViewController+ScreenShareOffer.swift in Sources */, @@ -4518,6 +4584,7 @@ 9AB196DE27C3FFF400FD60AB /* Call.Environment.Mock.swift in Sources */, 1A0C143125B8547200B00695 /* EngagementStyle.swift in Sources */, 7594098B298D38C2008B173A /* CallVisualizer+Presentation.swift in Sources */, + C06152DA2AB1BC4300063BF8 /* OrientationManager.swift in Sources */, 9A186A3727F5D38D0055886D /* ChatMessageEntryStyle.Accessibility.swift in Sources */, 9AB061C1280EFE09008960FA /* ChatFileDownloadStyle.Accessibility.swift in Sources */, 1A7CA8272574D6F40047CBBE /* ConnectStyle.swift in Sources */, @@ -4609,6 +4676,7 @@ 6E9C01AD26D3B8B500EBE1D4 /* OperatorTypingIndicatorView.swift in Sources */, AF10ED8B29B7A4C000E85309 /* ChatViewModel+ChoiceCards.swift in Sources */, 7594097E298D38C2008B173A /* CallVisualizer.BubbleIcon.swift in Sources */, + C06152D52AB1BC1300063BF8 /* Font+Extensions.swift in Sources */, AFA2FDF628907FC800428E6D /* BubbleStyle.Mock.swift in Sources */, 755D186B29A6A5830009F5E8 /* WelcomeStyle+MessageTitleStyle.swift in Sources */, 75940981298D38C2008B173A /* VisitorCodeView+NumberView.swift in Sources */, diff --git a/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationView.swift b/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationView.swift index 7aca96ded..c09728ba4 100644 --- a/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationView.swift +++ b/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationView.swift @@ -1,216 +1,95 @@ -import Foundation -import UIKit +import SwiftUI extension SecureConversations { - final class ConfirmationView: BaseView { - static let sideMargin = 24.0 - static let portraitConfirmationImageSize = 144.0 - static let landscapeConfirmationImageSize = 90.0 - - struct Props: Equatable { - let style: ConfirmationStyle - let header: Header.Props - let checkMessageButtonTap: Cmd - } - - private let header: Header - - var topRootStackViewConstraint: NSLayoutConstraint? - var confirmationImageViewWidthConstraints: NSLayoutConstraint? - var confirmationImageViewHeightConstraints: NSLayoutConstraint? - - lazy var rootStackView = UIStackView.make( - .vertical, - spacing: 0, - distribution: .fill, - alignment: .center - )( - confirmationImageView, - titleLabel, - subtitleLabel, - spacer, - checkMessagesButton - ) - - let confirmationImageView = UIImageView().makeView { imageView in - imageView.image = Asset.mcConfirmation.image.withRenderingMode(.alwaysTemplate) - } - let titleLabel = UILabel().makeView { label in - label.numberOfLines = 0 - label.textAlignment = .center - } - - let subtitleLabel = UILabel().makeView { label in - label.numberOfLines = 0 - label.textAlignment = .center - } - - // Flexible space to accommodate the check messages button - // at the bottom of the view. - let spacer = UIView() - - lazy var checkMessagesButton = UIButton(type: .custom).makeView { button in - button.addTarget( - self, - action: #selector(handleCheckMessagesButtonTap), - for: .touchUpInside - ) - - button.layer.cornerRadius = 4 - } - - var props: Props { - didSet { - renderProps() + struct ConfirmationViewSwiftUI: View { + static let horizontalPadding: CGFloat = 24.0 + static let bottomPaddingPortrait: CGFloat = 24 + static let bottomPaddingLandScape: CGFloat = 8 + static let checkmarkPortraitSize: CGFloat = 100.0 + static let checkmarkLandscapeSize: CGFloat = 70.0 + static let mainSpacing: CGFloat = 0 + + let model: Model + + var body: some View { + ZStack { + backgroundColor() + VStack(spacing: Self.mainSpacing) { + HeaderSwiftUI(model: model.makeHeaderModel()) + Spacer(minLength: Self.horizontalPadding) + VStack(spacing: Self.mainSpacing) { + checkmarkImage() + titleView() + subtitleView() + if model.orientation.isPortrait { + Spacer(minLength: 2) + } + confirmationButtonView() + } + .padding(.bottom, model.orientation.isPortrait ? Self.bottomPaddingPortrait : Self.bottomPaddingLandScape) + .padding(.horizontal, Self.horizontalPadding) + } } } + } +} - init(props: Props) { - self.header = Header( - props: props.header - ) - self.props = props - super.init() - } - - @available(*, unavailable) - required init() { - fatalError("init() has not been implemented") - } - - override func defineLayout() { - super.defineLayout() - defineHeaderLayout() - defineRootStackViewLayout() - defineConfirmationImageViewLayout() - defineTitleLabelLayout() - defineSubtitleLabelLayout() - defineSpacerLayout() - defineCheckMessagesButtonLayout() - renderProps() - } - - override func setup() { - super.setup() - addSubview(rootStackView) - } - - override func layoutSubviews() { - super.layoutSubviews() - - changeConfirmationImageViewDimensions() - } - - private func changeConfirmationImageViewDimensions() { - // The portrait factor is the factor between the space from the header - // to the beginning of the stack view versus the height of the screen - // in the Figma design. The landscape factor was calculated through trial - // and error to avoid a bug where the image was so big that it would hide - // the text below it. - let factor = currentOrientation.isPortrait ? 0.2783 : 0.075 - topRootStackViewConstraint?.constant = self.rootStackView.frame.height * factor - - let imageSize = currentOrientation.isPortrait ? - Self.portraitConfirmationImageSize : - Self.landscapeConfirmationImageSize - - confirmationImageViewWidthConstraints?.constant = imageSize - confirmationImageViewHeightConstraints?.constant = imageSize - } - - private func defineHeaderLayout() { - addSubview(header) - var constraints = [NSLayoutConstraint](); defer { constraints.activate() } - constraints += header.layoutInSuperview(edges: .horizontal) - constraints += header.layoutInSuperview(edges: .top) - } - - func defineRootStackViewLayout() { - topRootStackViewConstraint = rootStackView.topAnchor.constraint(equalTo: header.bottomAnchor) - NSLayoutConstraint.activate([ - rootStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Self.sideMargin), - rootStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -Self.sideMargin), - topRootStackViewConstraint, - rootStackView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -Self.sideMargin) - ].compactMap { $0 }) - } - - private func defineConfirmationImageViewLayout() { - confirmationImageViewWidthConstraints = confirmationImageView.widthAnchor.constraint( - equalToConstant: Self.portraitConfirmationImageSize - ) - confirmationImageViewHeightConstraints = confirmationImageView.heightAnchor.constraint( - equalToConstant: Self.portraitConfirmationImageSize - ) - - NSLayoutConstraint.activate([ - confirmationImageView.topAnchor.constraint(equalTo: rootStackView.topAnchor), - confirmationImageViewWidthConstraints, - confirmationImageViewHeightConstraints - ].compactMap { $0 }) - - rootStackView.setCustomSpacing(32, after: confirmationImageView) - } - - private func defineTitleLabelLayout() { - rootStackView.setCustomSpacing(16, after: titleLabel) - } - - private func defineSubtitleLabelLayout() { - rootStackView.setCustomSpacing(0, after: subtitleLabel) - } - - private func defineSpacerLayout() { - NSLayoutConstraint.activate([ - spacer.heightAnchor.constraint(greaterThanOrEqualToConstant: 1) - ]) - } - private func defineCheckMessagesButtonLayout() { - NSLayoutConstraint.activate([ - checkMessagesButton.widthAnchor.constraint( - equalTo: rootStackView.widthAnchor - ), - checkMessagesButton.heightAnchor.constraint(equalToConstant: 48) - ]) - } - @objc func handleCheckMessagesButtonTap() { - props.checkMessageButtonTap() - } - - private func renderProps() { - header.props = props.header - header.showCloseButton() - - confirmationImageView.tintColor = props.style.confirmationImageTint - titleLabel.text = props.style.titleStyle.text - titleLabel.textColor = props.style.titleStyle.color - titleLabel.font = props.style.titleStyle.font - setFontScalingEnabled( - props.style.titleStyle.accessibility.isFontScalingEnabled, - for: titleLabel - ) +private extension SecureConversations.ConfirmationViewSwiftUI { + @ViewBuilder + func backgroundColor() -> some View { + SwiftUI.Color(model.style.backgroundColor) + .edgesIgnoringSafeArea(.all) + } + @ViewBuilder + func checkmarkImage() -> some View { + SwiftUI.Image(uiImage: Asset.mcConfirmation.image.withRenderingMode(.alwaysTemplate)) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: model.orientation.isPortrait ? Self.checkmarkPortraitSize : Self.checkmarkLandscapeSize) + .foregroundColor(SwiftUI.Color(model.style.confirmationImageTint)) + .padding(.bottom, model.orientation.isPortrait ? 32 : 8) + .accessibility(hidden: true) + } - subtitleLabel.text = props.style.subtitleStyle.text - subtitleLabel.textColor = props.style.subtitleStyle.color - subtitleLabel.font = props.style.subtitleStyle.font - setFontScalingEnabled( - props.style.subtitleStyle.accessibility.isFontScalingEnabled, - for: subtitleLabel - ) + @ViewBuilder + func titleView() -> some View { + Text(model.style.titleStyle.text) + .font(.convert(model.style.titleStyle.font)) + .multilineTextAlignment(.center) + .foregroundColor(SwiftUI.Color(model.style.titleStyle.color)) + .padding(.bottom, model.orientation.isPortrait ? 16 : 8) + } - checkMessagesButton.setTitle(props.style.checkMessagesButtonStyle.title, for: .normal) - checkMessagesButton.setTitleColor(props.style.checkMessagesButtonStyle.textColor, for: .normal) - checkMessagesButton.backgroundColor = props.style.checkMessagesButtonStyle.backgroundColor - checkMessagesButton.accessibilityTraits = .button - checkMessagesButton.accessibilityIdentifier = "secureConversations_confirmationCheckMessages_button" - checkMessagesButton.accessibilityLabel = props.style.checkMessagesButtonStyle.accessibility.label - checkMessagesButton.accessibilityHint = props.style.checkMessagesButtonStyle.accessibility.hint - setFontScalingEnabled( - props.style.checkMessagesButtonStyle.accessibility.isFontScalingEnabled, - for: checkMessagesButton - ) + @ViewBuilder + func subtitleView() -> some View { + Text(model.style.subtitleStyle.text) + .font(.convert(model.style.subtitleStyle.font)) + .multilineTextAlignment(.center) + .lineLimit(nil) + .foregroundColor(SwiftUI.Color(model.style.subtitleStyle.color)) + } - backgroundColor = props.style.backgroundColor - } + @ViewBuilder + func confirmationButtonView() -> some View { + SwiftUI.Button { + model.event(.chatTranscriptScreenRequested) + } label: { + Text(model.style.checkMessagesButtonStyle.title) + .font(.convert(model.style.checkMessagesButtonStyle.font)) + .multilineTextAlignment(.center) + .foregroundColor(SwiftUI.Color(model.style.checkMessagesButtonStyle.textColor)) + .padding(.vertical, 8) + .padding(.horizontal, 16) + .frame( + maxWidth: .infinity, + minHeight: 48, + idealHeight: 48 + ) + .background(SwiftUI.Color(model.style.checkMessagesButtonStyle.backgroundColor)) + .cornerRadius(4) + } + .accessibility(identifier: "secureConversations_confirmationCheckMessages_button") + .accessibility(label: Text(model.style.checkMessagesButtonStyle.accessibility.label)) + .accessibility(hint: Text(model.style.checkMessagesButtonStyle.accessibility.hint)) } } diff --git a/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationViewController.swift b/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationViewController.swift index c1904fca1..0af1c8ce3 100644 --- a/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationViewController.swift +++ b/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationViewController.swift @@ -1,25 +1,14 @@ import UIKit +import SwiftUI extension SecureConversations { final class ConfirmationViewController: UIViewController { - var props: Props { - didSet { - guard props != oldValue else { return } - renderProps() - } - } - - private let viewFactory: ViewFactory - private let viewModel: ConfirmationViewModel + private let model: ConfirmationViewSwiftUI.Model init( - viewModel: ConfirmationViewModel, - viewFactory: ViewFactory, - props: Props + model: ConfirmationViewSwiftUI.Model ) { - self.viewModel = viewModel - self.viewFactory = viewFactory - self.props = props + self.model = model super.init(nibName: nil, bundle: nil) } @@ -30,26 +19,22 @@ extension SecureConversations { override func loadView() { super.loadView() - renderProps() - } + let hostingController: UIHostingController + let confirmationView = ConfirmationViewSwiftUI(model: model) - func renderProps() { - let confirmationView: ConfirmationView - if let currentView = view as? ConfirmationView { - confirmationView = currentView - } else { - confirmationView = viewFactory.makeSecureConversationsConfirmationView( - props: props.confirmationViewProps - ) - view = confirmationView - } - confirmationView.props = props.confirmationViewProps - } - } -} + hostingController = UIHostingController(rootView: confirmationView) + addChild(hostingController) + view.addSubview(hostingController.view) + hostingController.didMove(toParent: self) -extension SecureConversations.ConfirmationViewController { - struct Props: Equatable { - let confirmationViewProps: SecureConversations.ConfirmationView.Props + hostingController.rootView = ConfirmationViewSwiftUI(model: model) + hostingController.view.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + hostingController.view.topAnchor.constraint(equalTo: view.topAnchor), + hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), + hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor) + ]) + } } } diff --git a/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModel.swift b/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModel.swift index f21b60899..07249e736 100644 --- a/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModel.swift +++ b/GliaWidgets/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModel.swift @@ -1,87 +1,81 @@ import Foundation -extension SecureConversations { - final class ConfirmationViewModel: ViewModel { - var action: ((Action) -> Void)? +extension SecureConversations.ConfirmationViewSwiftUI { + class Model: ObservableObject { + let orientation: OrientationManager + let style: SecureConversations.ConfirmationStyle var delegate: ((DelegateEvent) -> Void)? - var environment: Environment - init(environment: Environment) { - self.environment = environment - } - - func event(_ event: Event) { - switch event { - case .closeTapped: - delegate?(.closeTapped) - } + init( + environment: OrientationManager.Environment, + style: SecureConversations.ConfirmationStyle, + delegate: ((DelegateEvent) -> Void)? + ) { + self.orientation = .init(environment: environment) + self.style = style + self.delegate = delegate } + } +} - func reportChange() { - delegate?(.renderProps(props())) +// MARK: - Public Methods +extension SecureConversations.ConfirmationViewSwiftUI.Model { + func event(_ event: Event) { + switch event { + case .closeTapped: + delegate?(.closeTapped) + case .chatTranscriptScreenRequested: + delegate?(.chatTranscriptScreenRequested) } } } -extension SecureConversations.ConfirmationViewModel { - func props() -> SecureConversations.ConfirmationViewController.Props { - let confirmationStyle = environment.confirmationStyle - let confirmationViewProps = SecureConversations.ConfirmationView.Props( - style: confirmationStyle, - header: Self.buildHeaderProps( - style: confirmationStyle, - closeButtonCmd: Cmd(closure: { [weak self] in self?.delegate?(.closeTapped) }) - ), - checkMessageButtonTap: Cmd { [weak self] in self?.delegate?(.chatTranscriptScreenRequested) } +// MARK: - Private Methods +extension SecureConversations.ConfirmationViewSwiftUI.Model { + func makeHeaderModel() -> HeaderSwiftUI.Model { + let endButtonProps: ActionButtonSwiftUI.Model = .init( + style: style.header.endButton, + accessibilityIdentifier: "header_end_button", + isEnabled: false, + isHidden: true ) - let viewControllerProps = SecureConversations.ConfirmationViewController.Props( - confirmationViewProps: confirmationViewProps + let closeButtonProps: HeaderButtonSwiftUI.Model = .init( + tap: Cmd(closure: { [weak self] in + self?.delegate?(.closeTapped) + }), + style: style.header.closeButton, + isEnabled: true, + isHidden: false ) - return viewControllerProps - } - - static func buildHeaderProps( - style: SecureConversations.ConfirmationStyle, - closeButtonCmd: Cmd - ) -> Header.Props { - let backButton = style.header.backButton.map { HeaderButton.Props(style: $0) } + let endScreenShareButtonProps: HeaderButtonSwiftUI.Model = .init( + style: style.header.endScreenShareButton, + isEnabled: false, + isHidden: true + ) - return Header.Props( + return .init( title: style.headerTitle, effect: .none, - endButton: .init(style: style.header.endButton, accessibilityIdentifier: "header_end_button"), - backButton: backButton, - closeButton: .init(tap: closeButtonCmd, style: style.header.closeButton), - endScreenshareButton: .init(style: style.header.endScreenShareButton), + endButton: endButtonProps, + backButton: nil, + closeButton: closeButtonProps, + endScreenshareButton: endScreenShareButtonProps, style: style.header ) } } -extension SecureConversations.ConfirmationViewModel { +// MARK: - Objects +extension SecureConversations.ConfirmationViewSwiftUI.Model { enum Event { case closeTapped - } - - enum Action { - case start + case chatTranscriptScreenRequested } enum DelegateEvent { case closeTapped - case renderProps(SecureConversations.ConfirmationViewController.Props) case chatTranscriptScreenRequested } - - enum StartAction { - case none - } -} - -extension SecureConversations.ConfirmationViewModel { - struct Environment { - var confirmationStyle: SecureConversations.ConfirmationStyle - } } diff --git a/GliaWidgets/SecureConversations/Confirmation/Theme+SecureConversationsConfirmation.swift b/GliaWidgets/SecureConversations/Confirmation/Theme+SecureConversationsConfirmation.swift index 942ae8ef7..b40c4f48b 100644 --- a/GliaWidgets/SecureConversations/Confirmation/Theme+SecureConversationsConfirmation.swift +++ b/GliaWidgets/SecureConversations/Confirmation/Theme+SecureConversationsConfirmation.swift @@ -12,7 +12,7 @@ extension Theme { let titleStyle = SecureConversations.ConfirmationStyle.TitleStyle( text: Confirmation.title, - font: font.header3, + font: font.header1, color: color.baseDark, accessibility: .init(isFontScalingEnabled: true) ) diff --git a/GliaWidgets/SecureConversations/SecureConversations.Coordinator.swift b/GliaWidgets/SecureConversations/SecureConversations.Coordinator.swift index 9eac3423e..222dd72d4 100644 --- a/GliaWidgets/SecureConversations/SecureConversations.Coordinator.swift +++ b/GliaWidgets/SecureConversations/SecureConversations.Coordinator.swift @@ -160,29 +160,19 @@ extension SecureConversations { } func presentSecureConversationsConfirmationViewController() { - let viewModel = SecureConversations.ConfirmationViewModel( - environment: .init( - confirmationStyle: viewFactory.theme.secureConversationsConfirmation - ) - ) - - let controller = SecureConversations.ConfirmationViewController( - viewModel: viewModel, - viewFactory: viewFactory, - props: viewModel.props() - ) + let model = SecureConversations.ConfirmationViewSwiftUI.Model( + environment: .init(uiApplication: environment.uiApplication), + style: viewFactory.theme.secureConversationsConfirmation, + delegate: { [weak self] event in + switch event { + case .closeTapped: + self?.delegate?(.closeTapped(.doNotPresentSurvey)) + case .chatTranscriptScreenRequested: + self?.navigateToTranscript() + } + }) - viewModel.delegate = { [weak self, weak controller] event in - switch event { - case .closeTapped: - self?.delegate?(.closeTapped(.doNotPresentSurvey)) - // Bind changes in view model to view controller. - case let .renderProps(props): - controller?.props = props - case .chatTranscriptScreenRequested: - self?.navigateToTranscript() - } - } + let controller = SecureConversations.ConfirmationViewController(model: model) self.navigationPresenter.push( controller, diff --git a/GliaWidgets/Sources/ViewFactory/ViewFactory.swift b/GliaWidgets/Sources/ViewFactory/ViewFactory.swift index 0dfb41c53..5ed46237b 100644 --- a/GliaWidgets/Sources/ViewFactory/ViewFactory.swift +++ b/GliaWidgets/Sources/ViewFactory/ViewFactory.swift @@ -136,8 +136,4 @@ class ViewFactory { ) -> SecureConversations.WelcomeView { return .init(props: props, environment: environment) } - - func makeSecureConversationsConfirmationView(props: SecureConversations.ConfirmationView.Props) -> SecureConversations.ConfirmationView { - return .init(props: props) - } } diff --git a/GliaWidgets/SwiftUI/Components/Buttons/ActionButtonSwiftUI.swift b/GliaWidgets/SwiftUI/Components/Buttons/ActionButtonSwiftUI.swift new file mode 100644 index 000000000..96c92b465 --- /dev/null +++ b/GliaWidgets/SwiftUI/Components/Buttons/ActionButtonSwiftUI.swift @@ -0,0 +1,65 @@ +import SwiftUI + +struct ActionButtonSwiftUI: View { + static let edgeInsets: EdgeInsets = .init(top: 6, leading: 16, bottom: 6, trailing: 16) + let model: Model + + var body: some View { + Text(model.style.title) + .font(.convert(model.style.titleFont)) + .foregroundColor(SwiftUI.Color(model.style.titleColor)) + .padding(Self.edgeInsets) + .background(SwiftUI.Color(model.style.backgroundColor.color)) + .cornerRadius(model.style.cornerRaidus ?? 0) + .overlay( + RoundedRectangle( + cornerRadius: model.style.cornerRaidus ?? 0) + .stroke( + SwiftUI.Color(model.style.borderColor ?? .clear), + lineWidth: model.style.borderWidth ?? 0 + ) + ) + .shadow( + color: SwiftUI.Color(model.style.shadowColor ?? .clear), + radius: model.style.shadowRadius ?? 0, + x: model.style.shadowOffset?.width ?? 0, + y: model.style.shadowOffset?.height ?? 0 + ) + .accessibility(identifier: model.accessibilityIdentifier) + .accessibility(addTraits: .isButton) + .accessibility(removeTraits: .isImage) + .onTapGesture(perform: model.tap.callAsFunction) + } +} + +extension ActionButtonSwiftUI { + class Model: ObservableObject { + let style: ActionButtonStyle + let height: CGFloat + let tap: Cmd + let accessibilityIdentifier: String + @Published var isEnabled: Bool + @Published var isHidden: Bool + + init( + style: ActionButtonStyle = .init( + title: "", + titleFont: .systemFont(ofSize: 16), + titleColor: .white, + backgroundColor: .fill(color: .blue) + ), + height: CGFloat = 40, + tap: Cmd = .nop, + accessibilityIdentifier: String = "", + isEnabled: Bool, + isHidden: Bool + ) { + self.style = style + self.height = height + self.tap = tap + self.accessibilityIdentifier = accessibilityIdentifier.isEmpty ? style.title : accessibilityIdentifier + self.isEnabled = isEnabled + self.isHidden = isHidden + } + } +} diff --git a/GliaWidgets/SwiftUI/Components/Buttons/HeaderButtonSwiftUI.swift b/GliaWidgets/SwiftUI/Components/Buttons/HeaderButtonSwiftUI.swift new file mode 100644 index 000000000..6a488132f --- /dev/null +++ b/GliaWidgets/SwiftUI/Components/Buttons/HeaderButtonSwiftUI.swift @@ -0,0 +1,53 @@ +import SwiftUI + +struct HeaderButtonSwiftUI: View { + static let enabledOpacity: CGFloat = 1.0 + static let disabledOpacity: CGFloat = 0.6 + static let horizontalPadding: CGFloat = 8 + static let verticalPadding: CGFloat = 6 + + let model: Model + + var body: some View { + SwiftUI.Image(uiImage: model.style.image) + .resizable() + .aspectRatio(contentMode: .fit) + .frame( + width: model.size.width, + height: model.size.height + ) + .padding(.horizontal, Self.horizontalPadding) + .padding(.vertical, Self.verticalPadding) + .contentShape(Rectangle()) + .foregroundColor(SwiftUI.Color(model.style.color)) + .opacity(model.isEnabled ? Self.enabledOpacity : Self.disabledOpacity) + .accessibility(label: Text(model.style.accessibility.label)) + .accessibility(addTraits: .isButton) + .accessibility(removeTraits: .isImage) + .onTapGesture(perform: model.tap.callAsFunction) + } +} + +extension HeaderButtonSwiftUI { + class Model: ObservableObject { + var tap: Cmd + var style: HeaderButtonStyle + var size: CGSize + var isEnabled: Bool + var isHidden: Bool + + init( + tap: Cmd = .nop, + style: HeaderButtonStyle, + size: CGSize = CGSize(width: 14, height: 14), + isEnabled: Bool, + isHidden: Bool + ) { + self.tap = tap + self.style = style + self.size = size + self.isEnabled = isEnabled + self.isHidden = isHidden + } + } +} diff --git a/GliaWidgets/SwiftUI/Components/Header/HeaderSwiftUI.swift b/GliaWidgets/SwiftUI/Components/Header/HeaderSwiftUI.swift new file mode 100644 index 000000000..ca0adf74b --- /dev/null +++ b/GliaWidgets/SwiftUI/Components/Header/HeaderSwiftUI.swift @@ -0,0 +1,84 @@ +import SwiftUI + +struct HeaderSwiftUI: View { + static let height: CGFloat = 58 + static let horizontalPadding: CGFloat = 16 + static let bottomPadding: CGFloat = 12 + static let spacing: CGFloat = 16 + + let model: Model + + var body: some View { + ZStack(alignment: .bottom) { + HStack(spacing: Self.spacing) { + if let backButton = model.backButton { + HeaderButtonSwiftUI(model: backButton) + } + + Spacer() + if !model.endButton.isHidden { + ActionButtonSwiftUI(model: model.endButton) + } + + if !model.endScreenshareButton.isHidden { + HeaderButtonSwiftUI(model: model.endScreenshareButton) + } + + if !model.closeButton.isHidden { + HeaderButtonSwiftUI(model: model.closeButton) + } + } + Text(model.title) + .font(.convert(model.style.titleFont)) + .foregroundColor(SwiftUI.Color(model.style.titleColor)) + .accessibility(identifier: "header_view_title_label") + .accessibility(label: Text(model.title)) + .accessibility(addTraits: .isHeader) + } + .padding(.horizontal, Self.horizontalPadding) + .padding(.bottom, Self.bottomPadding) + .frame( + height: Self.height + (UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0), + alignment: .bottom + ) + .background(SwiftUI.Color(model.style.backgroundColor.color)) + .edgesIgnoringSafeArea([.leading, .top, .trailing]) + } +} + +extension HeaderSwiftUI { + class Model: ObservableObject { + let title: String + let effect: Effect + let endButton: ActionButtonSwiftUI.Model + let backButton: HeaderButtonSwiftUI.Model? + let closeButton: HeaderButtonSwiftUI.Model + let endScreenshareButton: HeaderButtonSwiftUI.Model + let style: HeaderStyle + + init( + title: String, + effect: Effect, + endButton: ActionButtonSwiftUI.Model, + backButton: HeaderButtonSwiftUI.Model?, + closeButton: HeaderButtonSwiftUI.Model, + endScreenshareButton: HeaderButtonSwiftUI.Model, + style: HeaderStyle + ) { + self.title = title + self.effect = effect + self.endButton = endButton + self.backButton = backButton + self.closeButton = closeButton + self.endScreenshareButton = endScreenshareButton + self.style = style + } + } +} + +extension HeaderSwiftUI { + enum Effect { + case none + case blur + } +} diff --git a/GliaWidgets/SwiftUI/Extensions/Font+Extensions.swift b/GliaWidgets/SwiftUI/Extensions/Font+Extensions.swift new file mode 100644 index 000000000..7b20072ab --- /dev/null +++ b/GliaWidgets/SwiftUI/Extensions/Font+Extensions.swift @@ -0,0 +1,7 @@ +import SwiftUI + +extension Font { + static func convert(_ uiFont: UIFont) -> Font { + return .custom(uiFont.familyName + uiFont.fontName, size: uiFont.pointSize) + } +} diff --git a/GliaWidgets/SwiftUI/Managers/OrientationManager.swift b/GliaWidgets/SwiftUI/Managers/OrientationManager.swift new file mode 100644 index 000000000..6cd35a41f --- /dev/null +++ b/GliaWidgets/SwiftUI/Managers/OrientationManager.swift @@ -0,0 +1,40 @@ +import SwiftUI +import Combine + +class OrientationManager: ObservableObject { + @Published private(set) var orientation: UIInterfaceOrientation + + private let environment: Environment + private var orientationSubscription: AnyCancellable? + + init(environment: Environment) { + self.environment = environment + orientation = environment.uiApplication.windows().first?.windowScene?.interfaceOrientation ?? .unknown + orientationSubscription = NotificationCenter + .default + .publisher(for: UIDevice.orientationDidChangeNotification) + .map { _ in self.currentOrientation } + .removeDuplicates() + .assign(to: \.orientation, on: self) + } + + var isPortrait: Bool { + orientation == .portrait || orientation == .portraitUpsideDown + } + + var isLandscape: Bool { + !isPortrait + } + + private var currentOrientation: UIInterfaceOrientation { + environment.uiApplication.windows().first?.windowScene?.interfaceOrientation ?? .unknown + } +} + +extension OrientationManager { + struct Environment { + var uiApplication: UIKitBased.UIApplication +// var uiDevice: UIKitBased.UIDevice +// var notificationCenter: FoundationBased.NotificationCenter + } +} diff --git a/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/HeaderStyle.Mock.swift b/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/HeaderStyle.Mock.swift index b16e5e1c4..b40ba6765 100644 --- a/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/HeaderStyle.Mock.swift +++ b/GliaWidgetsTests/CallVisualizer/VideoCall/Mocks/HeaderStyle.Mock.swift @@ -27,9 +27,10 @@ extension HeaderStyle { extension HeaderButtonStyle { static func mock( image: UIImage = .mock, - color: UIColor = .white + color: UIColor = .white, + accessibility: Accessibility = .init(label: "", hint: "") ) -> HeaderButtonStyle { - return .init(image: image, color: color) + return .init(image: image, color: color, accessibility: accessibility) } } diff --git a/GliaWidgetsTests/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModel.Mock.swift b/GliaWidgetsTests/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModel.Mock.swift index 383ed28f0..85b8203a4 100644 --- a/GliaWidgetsTests/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModel.Mock.swift +++ b/GliaWidgetsTests/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModel.Mock.swift @@ -1,14 +1,73 @@ -import Foundation +import UIKit @testable import GliaWidgets -extension SecureConversations.ConfirmationViewModel { - static let mock = SecureConversations.ConfirmationViewModel( - environment: .mock - ) +extension SecureConversations.ConfirmationStyle { + static func mock( + title: String = "", + titleStyle: TitleStyle = .mock(), + subtitleStyle: SubtitleStyle = .mock(), + checkMessagesButtonStyle: CheckMessagesButtonStyle = .mock() + ) -> SecureConversations.ConfirmationStyle { + .init( + header: .mock(), + headerTitle: title, + confirmationImage: .mock, + confirmationImageTint: Color.baseLight, + titleStyle: titleStyle, + subtitleStyle: subtitleStyle, + checkMessagesButtonStyle: checkMessagesButtonStyle, + backgroundColor: Color.baseLight + ) + } } -extension SecureConversations.ConfirmationViewModel.Environment { - static let mock = SecureConversations.ConfirmationViewModel.Environment( - confirmationStyle: Theme().secureConversationsConfirmation - ) +extension SecureConversations.ConfirmationStyle.TitleStyle { + static func mock( + text: String = "Title label", + font: UIFont = ThemeFont().header1, + color: UIColor = Color.baseDark, + accessibility: Accessibility = .unsupported + ) -> SecureConversations.ConfirmationStyle.TitleStyle { + return .init( + text: text, + font: font, + color: color, + accessibility: accessibility + ) + } } + +extension SecureConversations.ConfirmationStyle.SubtitleStyle { + static func mock( + text: String = "Title label", + font: UIFont = ThemeFont().header1, + color: UIColor = Color.baseDark, + accessibility: Accessibility = .unsupported + ) -> SecureConversations.ConfirmationStyle.SubtitleStyle { + return .init( + text: text, + font: font, + color: color, + accessibility: accessibility + ) + } +} + +extension SecureConversations.ConfirmationStyle.CheckMessagesButtonStyle { + static func mock( + title: String = "Title label", + font: UIFont = ThemeFont().header1, + textColor: UIColor = Color.baseDark, + backgroundColor: UIColor = Color.baseLight, + accessibility: Accessibility = .unsupported + ) -> SecureConversations.ConfirmationStyle.CheckMessagesButtonStyle { + return .init( + title: title, + font: font, + textColor: textColor, + backgroundColor: backgroundColor, + accessibility: accessibility + ) + } +} + diff --git a/GliaWidgetsTests/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModelTests.swift b/GliaWidgetsTests/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModelTests.swift index ddf345042..6a8f9f949 100644 --- a/GliaWidgetsTests/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModelTests.swift +++ b/GliaWidgetsTests/SecureConversations/Confirmation/SecureConversations.ConfirmationViewModelTests.swift @@ -3,37 +3,31 @@ import XCTest @testable import GliaWidgets final class SecureConversationsConfirmationViewModelTests: XCTestCase { - typealias ConfirmationViewModel = SecureConversations.ConfirmationViewModel - var viewModel: ConfirmationViewModel = .mock + typealias ConfirmationViewModel = SecureConversations.ConfirmationViewSwiftUI.Model - override func setUp() { - viewModel = .mock - } + var viewModel: ConfirmationViewModel = .init( + environment: .init(uiApplication: .mock), + style: Theme().defaultSecureConversationsConfirmationStyle, + delegate: nil + ) } // Props extension SecureConversationsConfirmationViewModelTests { func testPropsDoNotGenerateABackButton() { - let props = viewModel.props().confirmationViewProps.header + let backButton = viewModel.style.header.backButton - XCTAssertNil(props.backButton) + XCTAssertNil(backButton) } func testPropsGenerateCorrectTitle() { let title = "Test" - var style = Theme().secureConversationsConfirmation - style.headerTitle = title - - viewModel.environment = .init(confirmationStyle: style) - - let props = viewModel.props() - XCTAssertEqual(props.confirmationViewProps.header.title, title) - } - - func testPropsGenerateEndButtonWithAccessibilityIdentifier() { - let props = viewModel.props().confirmationViewProps.header.endButton - - XCTAssertEqual(props.accessibilityIdentifier, "header_end_button") + let viewModel: ConfirmationViewModel = .init( + environment: .init(uiApplication: .mock), + style: .mock(title: title), + delegate: nil + ) + XCTAssertEqual(viewModel.style.headerTitle, title) } } @@ -58,11 +52,14 @@ extension SecureConversationsConfirmationViewModelTests { func testPressingCloseButtonCallsDelegate() throws { var receivedEvent: ConfirmationViewModel.DelegateEvent? - viewModel.delegate = { event in - receivedEvent = event - } - - viewModel.props().confirmationViewProps.header.closeButton.tap() + viewModel = .init( + environment: .init(uiApplication: .mock), + style: .mock(), + delegate: { event in + receivedEvent = event + } + ) + viewModel.delegate?(.closeTapped) switch try XCTUnwrap(receivedEvent) { case .closeTapped: @@ -74,11 +71,14 @@ extension SecureConversationsConfirmationViewModelTests { func testPressingCheckMessagesButtonCallsDelegate() throws { var receivedEvent: ConfirmationViewModel.DelegateEvent? - viewModel.delegate = { event in - receivedEvent = event - } - - viewModel.props().confirmationViewProps.checkMessageButtonTap() + viewModel = .init( + environment: .init(uiApplication: .mock), + style: .mock(), + delegate: { event in + receivedEvent = event + } + ) + viewModel.delegate?(.chatTranscriptScreenRequested) switch try XCTUnwrap(receivedEvent) { case .chatTranscriptScreenRequested: @@ -86,20 +86,4 @@ extension SecureConversationsConfirmationViewModelTests { default: XCTFail() } } - - func testReportingAChangeRendersProps() throws { - var receivedEvent: ConfirmationViewModel.DelegateEvent? - - viewModel.delegate = { event in - receivedEvent = event - } - - viewModel.reportChange() - - switch try XCTUnwrap(receivedEvent) { - case .renderProps(_): - XCTAssertTrue(true) - default: XCTFail() - } - } } diff --git a/SnapshotTests/SecureConversationsConfirmationScreenDynamicTypeFontTests.swift b/SnapshotTests/SecureConversationsConfirmationScreenDynamicTypeFontTests.swift index 134c38231..a1e479be4 100644 --- a/SnapshotTests/SecureConversationsConfirmationScreenDynamicTypeFontTests.swift +++ b/SnapshotTests/SecureConversationsConfirmationScreenDynamicTypeFontTests.swift @@ -7,12 +7,12 @@ final class SecureConversationsConfirmationScreenDynamicTypeFontTests: SnapshotT let theme = Theme.mock() func test_confirmationView_extra3Large() { - let props = Self.makeConfirmationProps(style: theme.secureConversationsConfirmation) - let viewController = SecureConversations.ConfirmationViewController( - viewModel: .init(environment: .init(confirmationStyle: theme.defaultSecureConversationsConfirmationStyle)), - viewFactory: .mock(theme: theme, messageRenderer: nil, environment: .mock), - props: props + let model: SecureConversations.ConfirmationViewSwiftUI.Model = .init( + environment: .init(uiApplication: .mock), + style: theme.defaultSecureConversationsConfirmationStyle, + delegate: nil ) + let viewController = SecureConversations.ConfirmationViewController(model: model) viewController.view.frame = UIScreen.main.bounds assertSnapshot( @@ -21,28 +21,5 @@ final class SecureConversationsConfirmationScreenDynamicTypeFontTests: SnapshotT named: self.nameForDevice() ) } - - // MARK: - Helpers - - static func headerProps() -> Header.Props { - .mock( - title: "Secure Conversations", - backButton: .init(style: .mock(image: Asset.back.image)), - closeButton: .init(style: .mock(image: Asset.close.image)) - ) - } - - static func makeConfirmationProps( - headerProps: Header.Props = headerProps(), - style: SecureConversations.ConfirmationStyle - ) -> SecureConversations.ConfirmationViewController.Props { - .init( - confirmationViewProps: .init( - style: style, - header: headerProps, - checkMessageButtonTap: .nop - ) - ) - } } // swiftlint:enable type_name diff --git a/SnapshotTests/SecureConversationsConfirmationScreenLayoutTests.swift b/SnapshotTests/SecureConversationsConfirmationScreenLayoutTests.swift index 41a2645fe..0bb597279 100644 --- a/SnapshotTests/SecureConversationsConfirmationScreenLayoutTests.swift +++ b/SnapshotTests/SecureConversationsConfirmationScreenLayoutTests.swift @@ -6,12 +6,12 @@ class SecureConversationsConfirmationScreenLayoutTests: SnapshotTestCase { let theme = Theme.mock() func test_confirmationView() { - let props = Self.makeConfirmationProps(style: theme.secureConversationsConfirmation) - let viewController = SecureConversations.ConfirmationViewController( - viewModel: .init(environment: .init(confirmationStyle: theme.defaultSecureConversationsConfirmationStyle)), - viewFactory: .mock(theme: theme, messageRenderer: nil, environment: .mock), - props: props + let model: SecureConversations.ConfirmationViewSwiftUI.Model = .init( + environment: .init(uiApplication: .mock), + style: theme.defaultSecureConversationsConfirmationStyle, + delegate: nil ) + let viewController = SecureConversations.ConfirmationViewController(model: model) viewController.view.frame = UIScreen.main.bounds assertSnapshot( @@ -20,27 +20,4 @@ class SecureConversationsConfirmationScreenLayoutTests: SnapshotTestCase { named: self.nameForDevice() ) } - - // MARK: - Helpers - - static func headerProps() -> Header.Props { - .mock( - title: "Secure Conversations", - backButton: .init(style: .mock(image: Asset.back.image)), - closeButton: .init(style: .mock(image: Asset.close.image)) - ) - } - - static func makeConfirmationProps( - headerProps: Header.Props = headerProps(), - style: SecureConversations.ConfirmationStyle - ) -> SecureConversations.ConfirmationViewController.Props { - .init( - confirmationViewProps: .init( - style: style, - header: headerProps, - checkMessageButtonTap: .nop - ) - ) - } } diff --git a/SnapshotTests/SecureConversationsConfirmationScreenTests.swift b/SnapshotTests/SecureConversationsConfirmationScreenTests.swift index 587292207..4636d6947 100644 --- a/SnapshotTests/SecureConversationsConfirmationScreenTests.swift +++ b/SnapshotTests/SecureConversationsConfirmationScreenTests.swift @@ -7,12 +7,12 @@ class SecureConversationsConfirmationScreenTests: SnapshotTestCase { let theme = Theme.mock() func test_confirmationView() { - let props = Self.makeConfirmationProps(style: theme.secureConversationsConfirmation) - let viewController = SecureConversations.ConfirmationViewController( - viewModel: .init(environment: .init(confirmationStyle: theme.defaultSecureConversationsConfirmationStyle)), - viewFactory: .mock(theme: theme, messageRenderer: nil, environment: .mock), - props: props + let model: SecureConversations.ConfirmationViewSwiftUI.Model = .init( + environment: .init(uiApplication: .mock), + style: theme.defaultSecureConversationsConfirmationStyle, + delegate: nil ) + let viewController = SecureConversations.ConfirmationViewController(model: model) viewController.view.frame = UIScreen.main.bounds assertSnapshot( @@ -21,27 +21,4 @@ class SecureConversationsConfirmationScreenTests: SnapshotTestCase { named: self.nameForDevice() ) } - - // MARK: - Helpers - - static func headerProps() -> Header.Props { - .mock( - title: "Secure Conversations", - backButton: .init(style: .mock(image: Asset.back.image)), - closeButton: .init(style: .mock(image: Asset.close.image)) - ) - } - - static func makeConfirmationProps( - headerProps: Header.Props = headerProps(), - style: SecureConversations.ConfirmationStyle - ) -> SecureConversations.ConfirmationViewController.Props { - .init( - confirmationViewProps: .init( - style: style, - header: headerProps, - checkMessageButtonTap: .nop - ) - ) - } }