From ef4ce02f7460f86ec8bc08b82a598f56debedad9 Mon Sep 17 00:00:00 2001 From: Boris Nikolic Date: Thu, 7 Nov 2024 11:25:20 +0100 Subject: [PATCH] Implement new dismissal mechanisms for DropIn --- .../Base.lproj/Main.storyboard | 56 ++++++++++++++----- ...hantSessionAndSettingsViewController.swift | 17 ++++++ .../Classes/Data Models/PrimerSettings.swift | 12 +++- .../Root/PrimerContainerViewController.swift | 2 +- .../Root/PrimerNavigationBar.swift | 49 +++++++++++++++- .../Root/PrimerRootViewController.swift | 1 + ...imerVoucherInfoPaymentViewController.swift | 2 +- 7 files changed, 120 insertions(+), 19 deletions(-) diff --git a/Debug App/Resources/Localized Views/Base.lproj/Main.storyboard b/Debug App/Resources/Localized Views/Base.lproj/Main.storyboard index 6535f85dc4..ad63005ba4 100644 --- a/Debug App/Resources/Localized Views/Base.lproj/Main.storyboard +++ b/Debug App/Resources/Localized Views/Base.lproj/Main.storyboard @@ -177,7 +177,7 @@ - + @@ -363,7 +363,7 @@ - + - + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - + @@ -1314,6 +1342,7 @@ + @@ -1328,6 +1357,7 @@ + diff --git a/Debug App/Sources/View Controllers/MerchantSessionAndSettingsViewController.swift b/Debug App/Sources/View Controllers/MerchantSessionAndSettingsViewController.swift index d9de2291dd..ad6cf445dc 100644 --- a/Debug App/Sources/View Controllers/MerchantSessionAndSettingsViewController.swift +++ b/Debug App/Sources/View Controllers/MerchantSessionAndSettingsViewController.swift @@ -68,6 +68,8 @@ class MerchantSessionAndSettingsViewController: UIViewController { @IBOutlet weak var vaultPaymentsSwitch: UISwitch! @IBOutlet weak var disableSuccessScreenSwitch: UISwitch! @IBOutlet weak var disableErrorScreenSwitch: UISwitch! + @IBOutlet weak var gesturesDismissalSwitch: UISwitch! + @IBOutlet weak var closeButtonDismissalSwitch: UISwitch! @IBOutlet weak var disableInitScreenSwitch: UISwitch! @IBOutlet weak var enableCVVRecaptureFlowSwitch: UISwitch! @@ -293,6 +295,9 @@ class MerchantSessionAndSettingsViewController: UIViewController { } } + gesturesDismissalSwitch.isOn = true // Default value + closeButtonDismissalSwitch.isOn = false // Default false + lineItemsStackView.removeAllArrangedSubviews() lineItemsStackView.alignment = .fill lineItemsStackView.distribution = .fill @@ -621,10 +626,22 @@ class MerchantSessionAndSettingsViewController: UIViewController { @IBAction func primerSDKButtonTapped(_ sender: Any) { customDefinedApiKey = (apiKeyTextField.text ?? "").isEmpty ? nil : apiKeyTextField.text + let selectedDismissalMechanisms: [DismissalMechanism] = { + var mechanisms = [DismissalMechanism]() + if gesturesDismissalSwitch.isOn { + mechanisms.append(.gestures) + } + if closeButtonDismissalSwitch.isOn { + mechanisms.append(.closeButton) + } + return mechanisms + }() + let uiOptions = PrimerUIOptions( isInitScreenEnabled: !disableInitScreenSwitch.isOn, isSuccessScreenEnabled: !disableSuccessScreenSwitch.isOn, isErrorScreenEnabled: !disableErrorScreenSwitch.isOn, + dismissalMechanism: selectedDismissalMechanisms, theme: applyThemingSwitch.isOn ? CheckoutTheme.tropical : nil) let mandateData = PrimerStripeOptions.MandateData.templateMandate(merchantName: "Primer Inc.") diff --git a/Sources/PrimerSDK/Classes/Data Models/PrimerSettings.swift b/Sources/PrimerSDK/Classes/Data Models/PrimerSettings.swift index fd10503abf..511e08309f 100644 --- a/Sources/PrimerSDK/Classes/Data Models/PrimerSettings.swift +++ b/Sources/PrimerSDK/Classes/Data Models/PrimerSettings.swift @@ -245,10 +245,15 @@ public class PrimerCardPaymentOptions: Codable { // MARK: - UI OPTIONS +public enum DismissalMechanism: Codable { + case gestures, closeButton +} + internal protocol PrimerUIOptionsProtocol { var isInitScreenEnabled: Bool { get } // Default: true var isSuccessScreenEnabled: Bool { get } // Default: true var isErrorScreenEnabled: Bool { get } // Default: true + var dismissalMechanism: [DismissalMechanism] { get } // Default: .gestures var theme: PrimerTheme { get } } @@ -257,21 +262,24 @@ public class PrimerUIOptions: PrimerUIOptionsProtocol, Codable { public internal(set) var isInitScreenEnabled: Bool public internal(set) var isSuccessScreenEnabled: Bool public internal(set) var isErrorScreenEnabled: Bool + public internal(set) var dismissalMechanism: [DismissalMechanism] public let theme: PrimerTheme private enum CodingKeys: String, CodingKey { - case isInitScreenEnabled, isSuccessScreenEnabled, isErrorScreenEnabled, theme + case isInitScreenEnabled, isSuccessScreenEnabled, isErrorScreenEnabled, dismissalMechanism, theme } public init( isInitScreenEnabled: Bool? = nil, isSuccessScreenEnabled: Bool? = nil, isErrorScreenEnabled: Bool? = nil, + dismissalMechanism: [DismissalMechanism]? = [.gestures], theme: PrimerTheme? = nil ) { self.isInitScreenEnabled = isInitScreenEnabled != nil ? isInitScreenEnabled! : true self.isSuccessScreenEnabled = isSuccessScreenEnabled != nil ? isSuccessScreenEnabled! : true self.isErrorScreenEnabled = isErrorScreenEnabled != nil ? isErrorScreenEnabled! : true + self.dismissalMechanism = dismissalMechanism ?? [.gestures] self.theme = theme ?? PrimerTheme() } @@ -280,6 +288,7 @@ public class PrimerUIOptions: PrimerUIOptionsProtocol, Codable { self.isInitScreenEnabled = try container.decode(Bool.self, forKey: .isInitScreenEnabled) self.isSuccessScreenEnabled = try container.decode(Bool.self, forKey: .isSuccessScreenEnabled) self.isErrorScreenEnabled = try container.decode(Bool.self, forKey: .isErrorScreenEnabled) + self.dismissalMechanism = try container.decode([DismissalMechanism].self, forKey: .dismissalMechanism) self.theme = PrimerTheme() } @@ -288,6 +297,7 @@ public class PrimerUIOptions: PrimerUIOptionsProtocol, Codable { try container.encode(isInitScreenEnabled, forKey: .isInitScreenEnabled) try container.encode(isSuccessScreenEnabled, forKey: .isSuccessScreenEnabled) try container.encode(isErrorScreenEnabled, forKey: .isErrorScreenEnabled) + try container.encode(dismissalMechanism, forKey: .dismissalMechanism) } } diff --git a/Sources/PrimerSDK/Classes/User Interface/Root/PrimerContainerViewController.swift b/Sources/PrimerSDK/Classes/User Interface/Root/PrimerContainerViewController.swift index 7f9aad32af..4d254fbba9 100644 --- a/Sources/PrimerSDK/Classes/User Interface/Root/PrimerContainerViewController.swift +++ b/Sources/PrimerSDK/Classes/User Interface/Root/PrimerContainerViewController.swift @@ -38,6 +38,7 @@ class PrimerContainerViewController: PrimerViewController { mockedNavigationBar.topAnchor.constraint(equalTo: view.topAnchor).isActive = true mockedNavigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true mockedNavigationBar.heightAnchor.constraint(equalToConstant: 44).isActive = true + mockedNavigationBar.addCancelButton() addChild(childViewController) scrollView.bounces = false @@ -102,5 +103,4 @@ extension UIView { leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: leading).isActive = true trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: trailing).isActive = true } - } diff --git a/Sources/PrimerSDK/Classes/User Interface/Root/PrimerNavigationBar.swift b/Sources/PrimerSDK/Classes/User Interface/Root/PrimerNavigationBar.swift index f87d98ae9f..18fbb797b8 100644 --- a/Sources/PrimerSDK/Classes/User Interface/Root/PrimerNavigationBar.swift +++ b/Sources/PrimerSDK/Classes/User Interface/Root/PrimerNavigationBar.swift @@ -24,16 +24,44 @@ class PrimerNavigationBar: PrimerView { didSet { rightBarButton?.tintColor = theme.text.system.color rightBarButton?.setTitleColor(theme.text.system.color, for: .normal) - rightBarButton?.frame = CGRect( - x: 0, y: 0, width: rightView.bounds.size.width, height: rightView.bounds.size.height - ) + // Remove any existing subviews from rightView rightView.subviews.forEach { view in view.removeFromSuperview() } if let rightBarButton = rightBarButton { rightView.addSubview(rightBarButton) + rightBarButton.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + rightBarButton.leadingAnchor.constraint(equalTo: rightView.leadingAnchor), + rightBarButton.trailingAnchor.constraint(equalTo: rightView.trailingAnchor), + rightBarButton.topAnchor.constraint(equalTo: rightView.topAnchor), + rightBarButton.bottomAnchor.constraint(equalTo: rightView.bottomAnchor) + ]) + } + } + } + + var leftBarButton: UIButton? { + didSet { + leftBarButton?.tintColor = theme.text.system.color + leftBarButton?.setTitleColor(theme.text.system.color, for: .normal) + + // Remove any existing subviews from rightView + leftView.subviews.forEach { view in + view.removeFromSuperview() + } + + if let rightBarButton = leftBarButton { + leftView.addSubview(rightBarButton) + rightBarButton.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + rightBarButton.leadingAnchor.constraint(equalTo: leftView.leadingAnchor), + rightBarButton.trailingAnchor.constraint(equalTo: leftView.trailingAnchor), + rightBarButton.topAnchor.constraint(equalTo: leftView.topAnchor), + rightBarButton.bottomAnchor.constraint(equalTo: leftView.bottomAnchor) + ]) } } } @@ -91,6 +119,21 @@ class PrimerNavigationBar: PrimerView { PrimerUIManager.primerRootViewController?.popViewController() } + func addCancelButton() { + // Add Close button to navigation bar + if PrimerSettings.current.uiOptions.dismissalMechanism.contains(.closeButton) { + let cancelButton = UIButton(type: .system) + cancelButton.setTitle(Strings.Generic.close, for: .normal) + cancelButton.setTitleColor(UIColor.gray, for: .disabled) + cancelButton.addTarget(self, action: #selector(didTapCloseButton), for: .touchUpInside) + rightBarButton = cancelButton + } + } + + @objc private func didTapCloseButton() { + PrimerInternal.shared.dismiss() + } + private func setup() { translatesAutoresizingMaskIntoConstraints = false heightAnchor.constraint(equalToConstant: PrimerDimensions.NavigationBar.default).isActive = true diff --git a/Sources/PrimerSDK/Classes/User Interface/Root/PrimerRootViewController.swift b/Sources/PrimerSDK/Classes/User Interface/Root/PrimerRootViewController.swift index 97c6a4d242..f38dc5094d 100644 --- a/Sources/PrimerSDK/Classes/User Interface/Root/PrimerRootViewController.swift +++ b/Sources/PrimerSDK/Classes/User Interface/Root/PrimerRootViewController.swift @@ -168,6 +168,7 @@ internal class PrimerRootViewController: PrimerViewController { } private func setupGestureRecognizers() { + guard PrimerSettings.current.uiOptions.dismissalMechanism.contains(.gestures) else { return } self.tapGesture = UITapGestureRecognizer( target: self, action: #selector(dismissGestureRecognizerAction)) diff --git a/Sources/PrimerSDK/Classes/User Interface/Root/PrimerVoucherInfoPaymentViewController.swift b/Sources/PrimerSDK/Classes/User Interface/Root/PrimerVoucherInfoPaymentViewController.swift index 0ebb9bddde..bf9308b4ef 100644 --- a/Sources/PrimerSDK/Classes/User Interface/Root/PrimerVoucherInfoPaymentViewController.swift +++ b/Sources/PrimerSDK/Classes/User Interface/Root/PrimerVoucherInfoPaymentViewController.swift @@ -51,7 +51,7 @@ internal class PrimerVoucherInfoPaymentViewController: PrimerFormViewController override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - (parent as? PrimerContainerViewController)?.mockedNavigationBar.rightBarButton = shareButton + (parent as? PrimerContainerViewController)?.mockedNavigationBar.leftBarButton = shareButton } }