diff --git a/PuraceDemo/PuraceDemo/Examples/Basic/SnackbarExample.swift b/PuraceDemo/PuraceDemo/Examples/Basic/SnackbarExample.swift index 4a45930..12a0c9e 100644 --- a/PuraceDemo/PuraceDemo/Examples/Basic/SnackbarExample.swift +++ b/PuraceDemo/PuraceDemo/Examples/Basic/SnackbarExample.swift @@ -10,16 +10,14 @@ import SwiftUI import Purace struct SnackBarExample: View { - @State var showSnackbar = false - var body: some View { VStack { Text("`PuraceSnackbarView(title: ...)`") .padding() - PuraceButtonView(!showSnackbar ? "Mostrar snackbar" : "Ocultar snackbar", fontSize: 14) { - showSnackbar = !showSnackbar + PuraceButtonView("Mostrar snackbar", fontSize: 14) { + PuraceSnackbarManager.instance.show(withTitle: "Test", type: .info) } Spacer() - }.snackBar(title: "Parece que ha ocurrido un error", isVisible: $showSnackbar, type: .info, buttonTitle: "REINTENTAR", duration: .long, dismissOnDrag: true) + } } } diff --git a/Sources/Purace/Common/Extensions/UIApplication+keyWindow.swift b/Sources/Purace/Common/Extensions/UIApplication+keyWindow.swift new file mode 100644 index 0000000..bcccfb0 --- /dev/null +++ b/Sources/Purace/Common/Extensions/UIApplication+keyWindow.swift @@ -0,0 +1,18 @@ +// +// UIApplication+keyWindow.swift +// +// +// Created by Juan Hurtado on 21/11/22. +// + +import UIKit + +extension UIApplication { + var keyWindow: UIWindow? { + return UIApplication.shared.connectedScenes + .filter { $0.activationState == .foregroundActive } + .first(where: { $0 is UIWindowScene }) + .flatMap({ $0 as? UIWindowScene })?.windows + .first(where: \.isKeyWindow) + } +} diff --git a/Sources/Purace/Common/Extensions/UIView+NibLoadable.swift b/Sources/Purace/Common/Extensions/UIView+NibLoadable.swift new file mode 100644 index 0000000..df7a79f --- /dev/null +++ b/Sources/Purace/Common/Extensions/UIView+NibLoadable.swift @@ -0,0 +1,34 @@ +// +// File.swift +// +// +// Created by Juan Hurtado on 19/11/22. +// + +import Foundation +import UIKit + +public protocol NibLoadable { + static var nibName: String { get } +} + +public extension NibLoadable where Self: UIView { + static var nibName: String { + return String(describing: Self.self) + } + + static var nib: UINib { + let bundle = Bundle.module + return UINib(nibName: Self.nibName, bundle: bundle) + } + + func setupFromNib() { + guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") } + addSubview(view) + view.translatesAutoresizingMaskIntoConstraints = false + view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true + view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true + view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true + view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true + } +} diff --git a/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.swift b/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.swift new file mode 100644 index 0000000..d34f9dc --- /dev/null +++ b/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.swift @@ -0,0 +1,51 @@ +// +// PuraceSnackbar.swift +// +// +// Created by Juan Hurtado on 19/11/22. +// + +import UIKit + +class PuraceSnackbar: UIView, NibLoadable { + @IBOutlet var contentView: UIView! + @IBOutlet weak var actionButton: UIButton! + @IBOutlet weak var titleLabel: UILabel! + + override init(frame: CGRect) { + super.init(frame: frame) + setupFromNib() + translatesAutoresizingMaskIntoConstraints = false + setupSubviews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupSubviews() { + titleLabel.font = UIFont(name: "Poppins-Regular", size: 14) + titleLabel.textColor = .white + actionButton.titleLabel?.textColor = .white + actionButton.titleLabel?.font = UIFont(name: "Poppins-Medium", size: 14) + contentView.layer.cornerRadius = 10 + } + + func setup(with title: String, type: PuraceSnackbarType) { + switch type { + case .info: + contentView.backgroundColor = .init(PuraceStyle.Color.G1) + case .alert: + contentView.backgroundColor = .init(PuraceStyle.Color.B1) + case .error: + contentView.backgroundColor = .init(PuraceStyle.Color.R1) + } + titleLabel.text = title + } +} + +// MARK: - Config constants +extension PuraceSnackbar { + static let height: CGFloat = 60 + static let padding: CGFloat = 20 +} diff --git a/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.xib b/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.xib new file mode 100644 index 0000000..d79b10b --- /dev/null +++ b/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.xib @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/Purace/Views/Basic/Snackbar v2/Models/PuraceSnackbarConfig.swift b/Sources/Purace/Views/Basic/Snackbar v2/Models/PuraceSnackbarConfig.swift new file mode 100644 index 0000000..f891095 --- /dev/null +++ b/Sources/Purace/Views/Basic/Snackbar v2/Models/PuraceSnackbarConfig.swift @@ -0,0 +1,15 @@ +// +// PuraceSnackbarConfig.swift +// +// +// Created by Juan Hurtado on 21/11/22. +// + +import Foundation + +struct PuraceSnackbarConfig { + let title: String + let type: PuraceSnackbarType = .info + let action: (() -> Void)? + let actionTitle: String? +} diff --git a/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarManager.swift b/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarManager.swift new file mode 100644 index 0000000..955fc8d --- /dev/null +++ b/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarManager.swift @@ -0,0 +1,110 @@ +// +// PuraceSnackbarManager.swift +// +// +// Created by Juan Hurtado on 18/11/22. +// + +import Foundation +import UIKit + +public class PuraceSnackbarManager { + private static var _instance: PuraceSnackbarManager? = nil + public static var instance: PuraceSnackbarManager { + if _instance == nil { + _instance = PuraceSnackbarManager() + } + return _instance! + } + + private var verbose = false + + var isPresented = false + var queue = [() -> ()]() + var snackbar: PuraceSnackbar? + + private init() {} + + private func setupSnackbarView(withTitle title: String, type: PuraceSnackbarType) { + guard let window = UIApplication.shared.keyWindow else { return } + let bottomPadding = window.safeAreaInsets.bottom + + snackbar = PuraceSnackbar(frame: .init(x: 0, y: 0, width: 100, height: 0)) + snackbar?.setup(with: title, type: type) + window.addSubview(snackbar!) + + // Height constriant + snackbar?.heightAnchor.constraint(equalToConstant: PuraceSnackbar.height).isActive = true + + // Bottom constriant + snackbar?.topAnchor.constraint(equalTo: window.safeAreaLayoutGuide.bottomAnchor, constant: bottomPadding).isActive = true + + // Left constraint + snackbar?.leadingAnchor.constraint(equalTo: window.leadingAnchor, constant: PuraceSnackbar.padding).isActive = true + // Right constraint + snackbar?.trailingAnchor.constraint(equalTo: window.trailingAnchor, constant: -PuraceSnackbar.padding).isActive = true + } + + + /// When called this function, some verbose logs will be printed on the console. + /// + /// Call this function **only** for debug purpuses. + public func debug() -> PuraceSnackbarManager { + verbose = true + return self + } + + public func show(withTitle title: String, type: PuraceSnackbarType) { + if isPresented { + enqueue(label: "show") { [weak self] in + guard let self else { return } + self.show(withTitle: title, type: type) + } + return + } + + if verbose { + print("showing snackbar") + } + + setupSnackbarView(withTitle: title, type: type) + UIView.animate(withDuration: 0.4, delay: 0, animations: { [weak self] in + guard let self else { return } + self.snackbar?.transform = .init(translationX: 0, y: -PuraceSnackbar.height - PuraceSnackbar.padding) + }) { _ in + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in + guard let self else { return } + if self.isPresented { + self.hide() + } + } + } + isPresented = true + + } + + public func hide() { + if verbose { + print("hiding snackbar") + } + guard isPresented else { return } + UIView.animate(withDuration: 0.4, delay: 0, animations: { [weak self] in + guard let self else { return } + self.snackbar?.transform = .init(translationX: 0, y: PuraceSnackbar.height) + }) { _ in + self.snackbar?.removeFromSuperview() + self.isPresented = false + guard !self.queue.isEmpty else { return } + let action = self.queue.removeFirst() + action() + } + } + + private func enqueue(label: String, action: @escaping () -> ()) { + queue.append(action) + if verbose { + print("Enqueuing action: \(label)") + print("queue length: \(queue.count)") + } + } +}