Sources/Harlow/Views/DebuggerScallableView.swift
//
// DebuggerScallableView.swift
//
// The MIT License (MIT)
//
// Copyright (c) 2018 Stanwood GmbH (www.stanwood.io)
//
// 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 Foundation
import UIKit
import SourceModel
typealias Completion = () -> Void
protocol DebuggerScallableViewDelegate: class {
func scallableViewIsExpanding(with filter: DebuggerFilterView.DebuggerFilter, completion: @escaping Completion)
func scallableViewDidDismiss(whileExpanding: Bool)
}
class DebuggerScallableView: UIView {
var currentFilter: DebuggerFilterView.DebuggerFilter = .analytics
private var size: CGSize {
let screenSize = UIScreen.main.bounds.size
return CGSize(width: screenSize.width - 18, height: screenSize.height / 1.8 )
}
@IBOutlet private weak var filterView: DebuggerFilterOutletView!
@IBOutlet private weak var tableView: UITableView!
@IBOutlet private weak var expandButton: UIButton!
@IBOutlet private weak var closeButton: UIButton!
weak var button: DebuggerUIButton!
weak var delegate: DebuggerScallableViewDelegate?
var presenter: DebuggerPresenter!
private var listDelegate: ListDelegate!
private var listDataSource: ListDataSource!
private var views: [UIView] {
return [tableView, filterView]
}
private var buttons: [UIView] {
return [expandButton, closeButton]
}
override func awakeFromNib() {
super.awakeFromNib()
backgroundColor = UIColor.white.withAlphaComponent(0.5)
alpha = 0
closeButton.addTarget(self, action: #selector(dismiss), for: .touchUpInside)
views.hide(animated: false)
buttons.hide(animated: false)
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 2)
layer.shadowRadius = 2
layer.shadowOpacity = 0.45
layer.masksToBounds = false
clipsToBounds = true
layer.cornerRadius = 5
layer.borderColor = UIColor.black.withAlphaComponent(0.3).cgColor
layer.borderWidth = 1
NotificationCenter.default.addObserver(self, selector: #selector(refresh), name: NSNotification.Name.DebuggerDidAddDebuggerItem, object: nil)
filterView.delegate = self
}
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
func show() {
center = button.center
button.isPulseEnabled = false
button.isEnabled = false
UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveEaseIn, animations: {
self.button.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
}) { _ in
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.0, options: .curveEaseOut, animations: {
self.frame = CGRect(origin: .zero, size: self.size)
self.center = UIApplication.shared.keyWindow?.center ?? .zero
self.alpha = 1
}, completion: { _ in
self.views.show(duration: 0.3)
self.buttons.show(duration: 0.3)
})
}
}
func configureTableView(with modelCollection: ModelCollection?) {
tableView.register(cells: ErrorCell.self, AnalyticsCell.self, NetworkingCell.self, LogCell.self, CrashCell.self, bundle: Bundle.debuggerBundle())
tableView.estimatedRowHeight = 75
tableView.rowHeight = UITableView.automaticDimension
tableView.backgroundColor = UIColor.white.withAlphaComponent(0.8)
tableView.tableFooterView = UIView(frame: .zero)
if (modelCollection?.numberOfItems ?? 0) == 0 {
let emptyView = DebuggerEmptyView.loadFromNib(withFrame: tableView.frame, bundle: Bundle.debuggerBundle())
emptyView?.setLabel(with: currentFilter)
tableView.backgroundView = emptyView
} else {
tableView.backgroundView = nil
}
listDataSource = ListDataSource(modelCollection: modelCollection)
listDelegate = ListDelegate(modelCollection: modelCollection)
listDelegate.presenter = self
tableView.dataSource = listDataSource
tableView.delegate = listDelegate
tableView.reloadData()
}
@objc func refresh() {
tableView.reloadData()
}
@objc func dismiss(fromExpandable isExpandable: Bool = false) {
views.hide(duration: 0.3)
buttons.hide(duration: 0.3)
UIView.animate(withDuration: 0.4, delay: 0.0, options: .curveEaseIn, animations: {
self.frame = CGRect(origin: .zero, size: .zero)
self.center = self.button.center
self.alpha = isExpandable ? 1 : 0
}) { _ in
UIView.animate(withDuration: 0.3, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.0, options: .curveEaseOut, animations: {
self.button.transform = .identity
self.button.isPulseEnabled = true
self.button.isEnabled = true
}, completion: { _ in
self.backgroundColor = UIColor.white.withAlphaComponent(0.5)
self.delegate?.scallableViewDidDismiss(whileExpanding: isExpandable)
})
}
}
@IBAction func expand(_ sender: Any) {
expand(with: nil)
}
private func expand(with item: Recordable?) {
buttons.hide(duration: 0.3)
UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseInOut, animations: {
self.frame = UIApplication.shared.keyWindow?.frame ?? .zero
self.backgroundColor = UIColor.white.withAlphaComponent(0.95)
}) { _ in
self.views.hide()
var filter: DebuggerFilterView.DebuggerFilter
switch self.currentFilter {
case .networking(item: nil): filter = DebuggerFilterView.DebuggerFilter.networking(item: item)
case .error(item: nil): filter = DebuggerFilterView.DebuggerFilter.error(item: item)
case .crashes(item: nil): filter = DebuggerFilterView.DebuggerFilter.crashes(item: item)
default: filter = self.currentFilter
}
self.delegate?.scallableViewIsExpanding(with: filter) {
DispatchQueue.main.async { [weak self] in
self?.dismiss(fromExpandable: true)
}
}
}
}
}
extension DebuggerScallableView: ItemPresentable {
func present(item: Recordable) {
expand(with: item)
}
}
extension DebuggerScallableView: DebuggerFilterViewDelegate {
func debuggerFilterViewDidFilter(_ filter: DebuggerFilterView.DebuggerFilter) {
self.currentFilter = filter
let modelCollection = presenter.parameterable.getDeguggerItems(for: filter)
if (modelCollection?.numberOfItems ?? 0) == 0 {
let emptyView = DebuggerEmptyView.loadFromNib(withFrame: tableView.frame, bundle: Bundle.debuggerBundle())
emptyView?.setLabel(with: filter)
tableView.backgroundView = emptyView
} else {
tableView.backgroundView = nil
}
listDataSource.update(modelCollection: modelCollection)
listDelegate.update(modelCollection: modelCollection)
tableView.reloadData()
}
}