Skip to content
Snippets Groups Projects
HUD.swift 4.77 KiB
Newer Older
Bruno Muniz's avatar
Bruno Muniz committed
import UIKit
import Theme
import Shared
import Combine
import SnapKit

private enum Constants {
    static let title = Localized.Hud.Error.title
    static let action = Localized.Hud.Error.action
}

public enum HUDStatus: Equatable {
Ahmed Shehata's avatar
Ahmed Shehata committed
    case on(String?)
Bruno Muniz's avatar
Bruno Muniz committed
    case none
    case error(HUDError)

    var isPresented: Bool {
        switch self {
        case .none:
            return false
        case .on, .error:
            return true
        }
    }
}

public struct HUDError: Equatable {
    var title: String
    var content: String
    var buttonTitle: String
    var dismissable: Bool

    public init(
        content: String,
        title: String? = nil,
        buttonTitle: String? = nil,
        dismissable: Bool = true
    ) {
        self.content = content
        self.title = title ?? Constants.title
        self.buttonTitle = buttonTitle ?? Constants.action
        self.dismissable = dismissable
    }

    public init(with error: Error) {
        self.title = Constants.title
        self.buttonTitle = Constants.action
        self.content = error.localizedDescription
        self.dismissable = true
    }
}

public protocol HUDType {
    func update(with status: HUDStatus)
}

public final class HUD: HUDType {
    private(set) var window: UIWindow?
    private(set) var errorView: ErrorView?
Ahmed Shehata's avatar
Ahmed Shehata committed
    private(set) var titleLabel: UILabel?
Bruno Muniz's avatar
Bruno Muniz committed
    private(set) var animation: DotAnimation?
    private var cancellables = Set<AnyCancellable>()

    private var status: HUDStatus = .none {
        didSet {
            if oldValue.isPresented == true && status.isPresented == true {
                self.errorView = nil
                self.animation = nil
                self.window = nil
Ahmed Shehata's avatar
Ahmed Shehata committed
                self.titleLabel = nil
Bruno Muniz's avatar
Bruno Muniz committed

                switch status {
Ahmed Shehata's avatar
Ahmed Shehata committed
                case .on(let text):
Bruno Muniz's avatar
Bruno Muniz committed
                    animation = DotAnimation()
Ahmed Shehata's avatar
Ahmed Shehata committed

                    if let text = text {
                        titleLabel = UILabel()
                        titleLabel!.text = text
                    }
Bruno Muniz's avatar
Bruno Muniz committed
                case .error(let error):
                    errorView = ErrorView(with: error)
                case .none:
                    break
                }

                showWindow()
            }

            if oldValue.isPresented == false && status.isPresented == true {
                switch status {
Ahmed Shehata's avatar
Ahmed Shehata committed
                case .on(let text):
Bruno Muniz's avatar
Bruno Muniz committed
                    animation = DotAnimation()
Ahmed Shehata's avatar
Ahmed Shehata committed

                    if let text = text {
                        titleLabel = UILabel()
                        titleLabel!.text = text
                    }
Bruno Muniz's avatar
Bruno Muniz committed
                case .error(let error):
                    errorView = ErrorView(with: error)
                case .none:
                    break
                }

                showWindow()
            }

            if oldValue.isPresented == true && status.isPresented == false {
                hideWindow()
            }
        }
    }

    public init() {}

    public func update(with status: HUDStatus) {
        self.status = status
    }

    private func showWindow() {
        window = Window()
        window?.backgroundColor = UIColor.black.withAlphaComponent(0.5)
        window?.rootViewController = StatusBarViewController(nil)

        if let animation = animation {
            window?.addSubview(animation)
            animation.setColor(.white)
            animation.snp.makeConstraints { $0.center.equalToSuperview() }
        }

Ahmed Shehata's avatar
Ahmed Shehata committed
        if let titleLabel = titleLabel {
            window?.addSubview(titleLabel)
            titleLabel.textAlignment = .center
            titleLabel.numberOfLines = 0
            titleLabel.snp.makeConstraints { make in
                make.left.equalToSuperview().offset(18)
                make.center.equalToSuperview().offset(50)
                make.right.equalToSuperview().offset(-18)
            }
        }

Bruno Muniz's avatar
Bruno Muniz committed
        if let errorView = errorView {
            window?.addSubview(errorView)
            errorView.snp.makeConstraints { make in
                make.left.equalToSuperview().offset(18)
                make.center.equalToSuperview()
                make.right.equalToSuperview().offset(-18)
            }

            errorView.button
                .publisher(for: .touchUpInside)
                .receive(on: DispatchQueue.main)
                .sink { [unowned self] in hideWindow() }
                .store(in: &cancellables)
        }

        window?.alpha = 0.0
        window?.makeKeyAndVisible()

        UIView.animate(withDuration: 0.3) { self.window?.alpha = 1.0 }
    }

    private func hideWindow() {
        UIView.animate(withDuration: 0.3) {
            self.window?.alpha = 0.0
        } completion: { _ in
            self.cancellables.removeAll()
            self.errorView = nil
            self.animation = nil
Ahmed Shehata's avatar
Ahmed Shehata committed
            self.titleLabel = nil
Bruno Muniz's avatar
Bruno Muniz committed
            self.window = nil
        }
    }
}