diff --git a/Samples/MobileBuyIntegration/MobileBuyIntegration.xcodeproj/project.pbxproj b/Samples/MobileBuyIntegration/MobileBuyIntegration.xcodeproj/project.pbxproj index f14192c5..ac43eb7b 100644 --- a/Samples/MobileBuyIntegration/MobileBuyIntegration.xcodeproj/project.pbxproj +++ b/Samples/MobileBuyIntegration/MobileBuyIntegration.xcodeproj/project.pbxproj @@ -441,6 +441,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = A7XGC83MZE; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = MobileBuyIntegration/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; diff --git a/Samples/MobileBuyIntegration/MobileBuyIntegration/AppConfiguration.swift b/Samples/MobileBuyIntegration/MobileBuyIntegration/AppConfiguration.swift index 8d3cefb1..1e17df23 100644 --- a/Samples/MobileBuyIntegration/MobileBuyIntegration/AppConfiguration.swift +++ b/Samples/MobileBuyIntegration/MobileBuyIntegration/AppConfiguration.swift @@ -24,7 +24,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SO import ShopifyCheckoutSheetKit public struct AppConfiguration { + /// Prefill buyer information public var useVaultedState: Bool = false + public var useNativeButton: Bool = false + + /// Logger to retain Web Pixel events internal let webPixelsLogger = FileLogger("analytics.txt") } diff --git a/Samples/MobileBuyIntegration/MobileBuyIntegration/AppDelegate.swift b/Samples/MobileBuyIntegration/MobileBuyIntegration/AppDelegate.swift index b0ee746a..8abc9aed 100644 --- a/Samples/MobileBuyIntegration/MobileBuyIntegration/AppDelegate.swift +++ b/Samples/MobileBuyIntegration/MobileBuyIntegration/AppDelegate.swift @@ -33,7 +33,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { $0.colorScheme = .automatic /// Enable preloading - $0.preloading.enabled = true + $0.preloading.enabled = false + + $0.progressBarEnabled = true + + /// Optional logger used for internal purposes $0.logger = FileLogger("log.txt") } diff --git a/Samples/MobileBuyIntegration/MobileBuyIntegration/LogsView.swift b/Samples/MobileBuyIntegration/MobileBuyIntegration/LogsView.swift index b49a9f68..ca71e5b5 100644 --- a/Samples/MobileBuyIntegration/MobileBuyIntegration/LogsView.swift +++ b/Samples/MobileBuyIntegration/MobileBuyIntegration/LogsView.swift @@ -49,7 +49,6 @@ struct LogsView: View { Button(action: clearLogs) { Text("Clear logs") .foregroundColor(.red) - .background(.white) .font(.system(size: 12)) } Spacer() diff --git a/Samples/MobileBuyIntegration/MobileBuyIntegration/SettingsViewController.swift b/Samples/MobileBuyIntegration/MobileBuyIntegration/SettingsViewController.swift index 879f9f9f..d667e152 100644 --- a/Samples/MobileBuyIntegration/MobileBuyIntegration/SettingsViewController.swift +++ b/Samples/MobileBuyIntegration/MobileBuyIntegration/SettingsViewController.swift @@ -28,9 +28,11 @@ import ShopifyCheckoutSheetKit struct SettingsView: View { @State private var preloadingEnabled = ShopifyCheckoutSheetKit.configuration.preloading.enabled @State private var useVaultedState = appConfiguration.useVaultedState + @State private var useNativePayButton = ShopifyCheckoutSheetKit.configuration.payButton.enabled @State private var logs: [String?] = LogReader.shared.readLogs() ?? [] @State private var selectedColorScheme = ShopifyCheckoutSheetKit.configuration.colorScheme @State private var colorScheme: ColorScheme = .light + @State private var useProgressBar = ShopifyCheckoutSheetKit.configuration.progressBarEnabled var body: some View { NavigationView { @@ -46,6 +48,18 @@ struct SettingsView: View { } } + Section(header: Text("Experiments")) { + Toggle("Native pay button", isOn: $useNativePayButton) + .onChange(of: useNativePayButton) { newValue in + appConfiguration.useNativeButton = newValue + ShopifyCheckoutSheetKit.configuration.payButton.enabled = newValue + } + Toggle("Progress bar", isOn: $useProgressBar) + .onChange(of: useProgressBar) { newValue in + ShopifyCheckoutSheetKit.configuration.progressBarEnabled = newValue + } + } + Section(header: Text("Theme")) { ForEach(Configuration.ColorScheme.allCases, id: \.self) { scheme in ColorSchemeView(scheme: scheme, isSelected: scheme == selectedColorScheme) @@ -158,6 +172,28 @@ extension Configuration.ColorScheme { } } + var payButtonBackgroundColor: UIColor { + switch self { + case .web: + return UIColor(red: 0.94, green: 0.94, blue: 0.91, alpha: 1.00) + default: + return .systemBackground + } + } + + var borderColor: UIColor { + switch self { + case .web: + return UIColor(red: 208/255, green: 208/255, blue: 205/255, alpha: 1.0) + case .light: + return UIColor(red: 222/255, green: 222/255, blue: 222/255, alpha: 1.0) + case .dark: + return UIColor(red: 68/255, green: 68/255, blue: 70/255, alpha: 1.0) + default: + return .systemGray5 + } + } + var backgroundColor: UIColor { switch self { case .web: diff --git a/Samples/MobileBuyIntegration/MobileBuyIntegration/WebPixelEventsView.swift b/Samples/MobileBuyIntegration/MobileBuyIntegration/WebPixelEventsView.swift index 89941103..a5666a1f 100644 --- a/Samples/MobileBuyIntegration/MobileBuyIntegration/WebPixelEventsView.swift +++ b/Samples/MobileBuyIntegration/MobileBuyIntegration/WebPixelEventsView.swift @@ -48,7 +48,6 @@ struct WebPixelsEventsView: View { Button(action: clearLogs) { Text("Clear logs") .foregroundColor(.red) - .background(.white) .font(.system(size: 12)) } Spacer() diff --git a/Samples/SwiftUIExample/.swiftlint.yml b/Samples/SwiftUIExample/.swiftlint.yml deleted file mode 120000 index 7ec6f3a8..00000000 --- a/Samples/SwiftUIExample/.swiftlint.yml +++ /dev/null @@ -1 +0,0 @@ -Samples/MobileBuyIntegration/.swiftlint.yml \ No newline at end of file diff --git a/Sources/ShopifyCheckoutSheetKit/CheckoutBridge.swift b/Sources/ShopifyCheckoutSheetKit/CheckoutBridge.swift index 933a19b2..ecbbe008 100644 --- a/Sources/ShopifyCheckoutSheetKit/CheckoutBridge.swift +++ b/Sources/ShopifyCheckoutSheetKit/CheckoutBridge.swift @@ -52,6 +52,15 @@ enum CheckoutBridge { } let script = dispatchMessageTemplate(body: dispatchMessageBody) webView.evaluateJavaScript(script) + webView.evaluateJavaScript(""" + function showElement(selector) { + const el = document.querySelector(selector) + if (el) el.style.display = "block"; + } + showElement("#sticky-pay-button-container"); + showElement("#checkout-sdk-pay-button-container"); + showElement(".XlHGh"); + """) } static func decode(_ message: WKScriptMessage) throws -> WebEvent { diff --git a/Sources/ShopifyCheckoutSheetKit/CheckoutWebView.swift b/Sources/ShopifyCheckoutSheetKit/CheckoutWebView.swift index ecf60013..87bf786f 100644 --- a/Sources/ShopifyCheckoutSheetKit/CheckoutWebView.swift +++ b/Sources/ShopifyCheckoutSheetKit/CheckoutWebView.swift @@ -103,7 +103,64 @@ class CheckoutWebView: WKWebView { configuration.userContentController .add(self, name: CheckoutBridge.messageHandler) + isBridgeAttached = true + + let script = """ + function hideElement(selector) { + const el = document.querySelector(selector) + if (el) el.style.display = "none"; + } + + function showElement(selector) { + const el = document.querySelector(selector) + if (el) el.style.display = "block"; + } + + function showStickyButtons() { + showElement("#sticky-pay-button-container"); + showElement("#checkout-sdk-pay-button-container"); + showElement(".XlHGh"); + } + + function hideStickyButtons() { + hideElement("#sticky-pay-button-container"); + hideElement("#checkout-sdk-pay-button-container"); + hideElement(".XlHGh"); + } + + document.addEventListener("DOMContentLoaded", () => { + const fullHeight = window.visualViewport.height; + showStickyButtons(); + + window.visualViewport.addEventListener('resize', () => { + if (window.visualViewport.height < fullHeight) { + hideStickyButtons(); + } + + if (window.visualViewport.height === fullHeight) { + showStickyButtons(); + } + }); + + document.addEventListener("focusout", () => { + showStickyButtons(); + }); + }); + """ + + configuration.userContentController.addUserScript(WKUserScript( + source: script, + injectionTime: WKUserScriptInjectionTime.atDocumentStart, + forMainFrameOnly: true + )) + + isOpaque = false + backgroundColor = ShopifyCheckoutSheetKit.configuration.backgroundColor + + if #available(iOS 15.0, *) { + underPageBackgroundColor = ShopifyCheckoutSheetKit.configuration.backgroundColor + } } deinit { @@ -158,7 +215,7 @@ extension CheckoutWebView: WKScriptMessageHandler { () } } catch { - viewDelegate?.checkoutViewDidFailWithError(error: .sdkError(underlying: error)) + viewDelegate?.checkoutViewDidFailWithError(error: .sdkError(underlying: error)) } } } @@ -182,15 +239,15 @@ extension CheckoutWebView: WKNavigationDelegate { decisionHandler(.allow) } - func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { - if let response = navigationResponse.response as? HTTPURLResponse { - decisionHandler(handleResponse(response)) - return - } - decisionHandler(.allow) - } + func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { + if let response = navigationResponse.response as? HTTPURLResponse { + decisionHandler(handleResponse(response)) + return + } + decisionHandler(.allow) + } - func handleResponse(_ response: HTTPURLResponse) -> WKNavigationResponsePolicy { + func handleResponse(_ response: HTTPURLResponse) -> WKNavigationResponsePolicy { if isCheckout(url: response.url) && response.statusCode >= 400 { CheckoutWebView.cache = nil switch response.statusCode { @@ -257,7 +314,7 @@ extension CheckoutWebView: WKNavigationDelegate { } urlComponents.queryItems = urlComponents.queryItems?.filter { !($0.name == "open_externally") } return urlComponents.url ?? url - } + } private func isMailOrTelLink(_ url: URL) -> Bool { return ["mailto", "tel"].contains(url.scheme) diff --git a/Sources/ShopifyCheckoutSheetKit/CheckoutWebViewController.swift b/Sources/ShopifyCheckoutSheetKit/CheckoutWebViewController.swift index 204dbe9b..4e20415c 100644 --- a/Sources/ShopifyCheckoutSheetKit/CheckoutWebViewController.swift +++ b/Sources/ShopifyCheckoutSheetKit/CheckoutWebViewController.swift @@ -38,6 +38,12 @@ class CheckoutWebViewController: UIViewController, UIAdaptivePresentationControl return spinner }() + internal lazy var progress: IndeterminateProgressBarView = { + let progress = IndeterminateProgressBarView(frame: .zero) + progress.translatesAutoresizingMaskIntoConstraints = false + return progress + }() + internal var initialNavigation: Bool = true private let checkoutURL: URL @@ -66,19 +72,29 @@ class CheckoutWebViewController: UIViewController, UIAdaptivePresentationControl navigationItem.rightBarButtonItem = closeBarButtonItem checkoutView.viewDelegate = self + + view.backgroundColor = ShopifyCheckoutSheetKit.configuration.backgroundColor } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + deinit { + if progressBarEnabled() { + checkoutView.removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress)) + } + } + // MARK: UIViewController Lifecycle + override public func viewWillAppear(_ animated: Bool) { + view.backgroundColor = ShopifyCheckoutSheetKit.configuration.backgroundColor + } + override public func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = ShopifyCheckoutSheetKit.configuration.backgroundColor - view.addSubview(checkoutView) NSLayoutConstraint.activate([ checkoutView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), @@ -87,28 +103,114 @@ class CheckoutWebViewController: UIViewController, UIAdaptivePresentationControl checkoutView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) - view.addSubview(spinner) - NSLayoutConstraint.activate([ - spinner.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor), - spinner.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor) - ]) - view.bringSubviewToFront(spinner) + if progressBarEnabled() { + view.addSubview(progress) + NSLayoutConstraint.activate([ + progress.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + progress.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), + progress.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), + progress.heightAnchor.constraint(equalToConstant: 6) + ]) + view.bringSubviewToFront(progress) + checkoutView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil) + } else { + view.addSubview(spinner) + NSLayoutConstraint.activate([ + spinner.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor), + spinner.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor) + ]) + view.bringSubviewToFront(spinner) + } + + if checkoutView.isLoading == false { + self.displayNativePayButton() + } loadCheckout() } + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if keyPath == #keyPath(WKWebView.estimatedProgress) { + let estimatedProgress = Float(checkoutView.estimatedProgress) + progress.setProgress(estimatedProgress, animated: true) + if estimatedProgress < 1.0 { + progress.startAnimating() + } else { + progress.stopAnimating() + } + } + } + func notifyPresented() { checkoutView.checkoutDidPresent = true } + private func displayNativePayButton() { + guard ShopifyCheckoutSheetKit.configuration.payButton.enabled else { + if let payButtonView = self.view.viewWithTag(1337) { + payButtonView.removeFromSuperview() + } + return + } + let payButtonView = PayButtonView() + payButtonView.tag = 1337 + payButtonView.translatesAutoresizingMaskIntoConstraints = false + + view.addSubview(payButtonView) + + NSLayoutConstraint.activate([ + payButtonView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + payButtonView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + payButtonView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + + self.checkoutView.evaluateJavaScript(""" + let style = document.createElement('style'); + document.head.appendChild(style); + style.appendChild(document.createTextNode('#pay-button-container { display: none !important; }')); + style.appendChild(document.createTextNode('#sticky-pay-button-container, .XlHGh, #checkout-sdk-pay-button-container { display: none !important; } footer {padding-bottom: 6em !important; padding-block-end: 9em !important}')); + + let shopPayButton = document.querySelector('button[aria-label="Pay now"]') + if (shopPayButton) shopPayButton.style.display = "none"; + """) + + payButtonView.buttonPressedAction = { + self.checkoutView.evaluateJavaScript("document.querySelector('#pay-button-container button')?.click()") + self.checkoutView.evaluateJavaScript("document.querySelector('button[aria-label=\"Pay now\"]')?.click()") + self.checkoutView.evaluateJavaScript("window.MobileCheckoutSdk.dispatchMessage('submitPayment');") + } + } + + public func removeNativePayButton() { + if ShopifyCheckoutSheetKit.configuration.payButton.enabled { + if let payButtonView = self.view.viewWithTag(1337) { + payButtonView.removeFromSuperview() + } + } + } + + private func progressBarEnabled() -> Bool { + return ShopifyCheckoutSheetKit.configuration.progressBarEnabled + } + private func loadCheckout() { if checkoutView.url == nil { - checkoutView.alpha = 0 + if !progressBarEnabled() { + checkoutView.alpha = 0 + } initialNavigation = true checkoutView.load(checkout: checkoutURL) + + if progressBarEnabled() { + progress.startAnimating() + } } else if checkoutView.isLoading && initialNavigation { - checkoutView.alpha = 0 - spinner.startAnimating() + if progressBarEnabled() { + progress.startAnimating() + } else { + checkoutView.alpha = 0 + spinner.startAnimating() + } } } @@ -129,24 +231,34 @@ class CheckoutWebViewController: UIViewController, UIAdaptivePresentationControl } extension CheckoutWebViewController: CheckoutWebViewDelegate { - func checkoutViewDidStartNavigation() { if initialNavigation && !checkoutView.checkoutDidLoad { - spinner.startAnimating() + if !progressBarEnabled() { + spinner.startAnimating() + } } } func checkoutViewDidFinishNavigation() { - spinner.stopAnimating() initialNavigation = false - UIView.animate(withDuration: UINavigationController.hideShowBarDuration) { [weak checkoutView] in - checkoutView?.alpha = 1 + + if !progressBarEnabled() { + spinner.stopAnimating() + UIView.animate(withDuration: UINavigationController.hideShowBarDuration) { [weak checkoutView] in + checkoutView?.alpha = 1 + if ShopifyCheckoutSheetKit.configuration.payButton.enabled { + self.displayNativePayButton() + } + } } } func checkoutViewDidCompleteCheckout() { ConfettiCannon.fire(in: view) CheckoutWebView.invalidate() + + self.removeNativePayButton() + delegate?.checkoutDidComplete() } diff --git a/Sources/ShopifyCheckoutSheetKit/Configuration.swift b/Sources/ShopifyCheckoutSheetKit/Configuration.swift index ab264e07..0e4bedb3 100644 --- a/Sources/ShopifyCheckoutSheetKit/Configuration.swift +++ b/Sources/ShopifyCheckoutSheetKit/Configuration.swift @@ -39,11 +39,17 @@ public struct Configuration { public var preloading = Configuration.Preloading() + public var payButton = Configuration.PayButton() + public var spinnerColor: UIColor = UIColor(red: 0.09, green: 0.45, blue: 0.69, alpha: 1.00) public var backgroundColor: UIColor = .systemBackground + public var borderColor: UIColor = .systemGray5 + public var logger: Logger = NoOpLogger() + + public var progressBarEnabled: Bool = false } extension Configuration { @@ -67,6 +73,12 @@ extension Configuration { } } +extension Configuration { + public struct PayButton { + public var enabled: Bool = false + } +} + extension Configuration { public struct Preloading { public var enabled: Bool = true { diff --git a/Sources/ShopifyCheckoutSheetKit/IndeterminateProgressBarView.swift b/Sources/ShopifyCheckoutSheetKit/IndeterminateProgressBarView.swift new file mode 100644 index 00000000..bdbb20a3 --- /dev/null +++ b/Sources/ShopifyCheckoutSheetKit/IndeterminateProgressBarView.swift @@ -0,0 +1,62 @@ +import UIKit + +class IndeterminateProgressBarView: UIView { + private lazy var progressBar: UIProgressView = { + let progressBar = UIProgressView(progressViewStyle: .bar) + progressBar.setProgress(0.0, animated: false) + progressBar.translatesAutoresizingMaskIntoConstraints = false + return progressBar + }() + + private var progressAnimation: UIViewPropertyAnimator? + + override init(frame: CGRect) { + super.init(frame: frame) + + addSubview(progressBar) + + NSLayoutConstraint.activate([ + progressBar.topAnchor.constraint(equalTo: topAnchor), + progressBar.heightAnchor.constraint(equalToConstant: 1), + ]) + + progressBar.tintColor = ShopifyCheckoutSheetKit.configuration.spinnerColor + } + + override func didMoveToSuperview() { + super.didMoveToSuperview() + + if let superview = superview { + progressBar.leadingAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.leadingAnchor).isActive = true + progressBar.trailingAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.trailingAnchor).isActive = true + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setProgress(_ progress: Float, animated: Bool = false) { + if (progress > progressBar.progress) { + progressBar.setProgress(progress, animated: animated) + } + } + + func startAnimating() { + alpha = 1 + isHidden = false + } + + func stopAnimating() { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: { + UIView.animate(withDuration: 0.2, animations: { + self.alpha = 0 + }) { _ in + self.isHidden = true + self.alpha = 1 + self.progressBar.setProgress(0.0, animated: false) + } + }) + + } +} diff --git a/Sources/ShopifyCheckoutSheetKit/PayButtonView.swift b/Sources/ShopifyCheckoutSheetKit/PayButtonView.swift new file mode 100644 index 00000000..cc990695 --- /dev/null +++ b/Sources/ShopifyCheckoutSheetKit/PayButtonView.swift @@ -0,0 +1,86 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import UIKit + +class PayButtonView: UIView { + private var button: UIButton! + var buttonPressedAction: (() -> Void)? + + override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupView() { + backgroundColor = ShopifyCheckoutSheetKit.configuration.backgroundColor + + let border = UIView() + border.backgroundColor = ShopifyCheckoutSheetKit.configuration.borderColor + border.autoresizingMask = [.flexibleWidth, .flexibleBottomMargin] + border.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: 1) + addSubview(border) + + button = UIButton(type: .custom) + button.setTitle("Pay now", for: .normal) + button.setTitleColor(.white, for: .normal) + button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18) + button.backgroundColor = ShopifyCheckoutSheetKit.configuration.spinnerColor + button.layer.cornerRadius = 8 + button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 21, bottom: 0, right: 21) + button.layer.borderWidth = 1 + button.layer.borderColor = ShopifyCheckoutSheetKit.configuration.borderColor.cgColor + + button.translatesAutoresizingMaskIntoConstraints = false + addSubview(button) + + NSLayoutConstraint.activate([ + button.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 21), + button.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -21), + button.topAnchor.constraint(equalTo: topAnchor, constant: 18), + button.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -32), + button.heightAnchor.constraint(equalToConstant: 55) + ]) + + button.addTarget(self, action: #selector(buttonTouchUp), for: .touchUpInside) + button.addTarget(self, action: #selector(buttonTouchDown), for: .touchDown) + } + + @objc private func buttonTouchUp() { + buttonPressedAction?() + + UIView.animate(withDuration: 0.15, delay: 0.15, options: .curveEaseOut) { + self.button.backgroundColor = UIColor(red: 23/255, green: 115/255, blue: 176/255, alpha: 1.0) + } + } + + @objc private func buttonTouchDown() { + UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveEaseOut) { + self.button.backgroundColor = UIColor(red: 16/255, green: 89/255, blue: 137/255, alpha: 1.0) + } + } +}