Files
Pulse/Sources/PulseUI/Features/Console/ConsoleNetworkRequestTableCell.swift
2022-08-14 17:25:28 -04:00

195 lines
6.6 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// The MIT License (MIT)
//
// Copyright (c) 20202022 Alexander Grebenyuk (github.com/kean).
#if os(iOS)
import Pulse
import Combine
import UIKit
final class ConsoleNetworkRequestTableCell: UITableViewCell, UIContextMenuInteractionDelegate {
private let badge = CircleView()
private let title = UILabel()
private let typeIcon = UIImageView()
private let accessory = ConsoleMessageAccessoryView()
private let details = UILabel()
private let pin = PinIndicatorView()
private var state: NetworkTaskEntity.State?
private var viewModel: ConsoleNetworkRequestViewModel?
private var cancellable1: AnyCancellable?
private var cancellable2: AnyCancellable?
private var isAnimating = false
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
createView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func createView() {
selectionStyle = .gray
let topStack = UIView.hStack(alignment: .center, spacing: 8, [
badge, title, UIView(), pin, accessory
])
topStack.setCustomSpacing(4, after: pin)
let stack = UIView.vStack(spacing: 4, [topStack, details])
contentView.addSubview(stack)
stack.pinToSuperview(insets: .init(top: 10, left: 16, bottom: 10, right: 12))
title.font = .preferredFont(forTextStyle: .caption1)
title.textColor = .secondaryLabel
details.font = .systemFont(ofSize: 15)
details.numberOfLines = 4
let interaction = UIContextMenuInteraction(delegate: self)
self.addInteraction(interaction)
}
func display(_ viewModel: ConsoleNetworkRequestViewModel) {
self.viewModel = viewModel
self.state = nil
self.refresh()
self.cancellable1 = viewModel.objectWillChange.sink { [weak self] in
self?.refresh()
}
self.cancellable2 = viewModel.progress.objectWillChange.sink { [weak self] in
self?.refresh(onlyTitle: true)
}
}
override func prepareForReuse() {
super.prepareForReuse()
if isAnimating {
isAnimating = false
contentView.backgroundColor = .clear
layer.removeAllAnimations()
}
}
private func refresh(onlyTitle: Bool = false) {
guard let viewModel = viewModel else { return }
if let state = self.state, state != viewModel.state {
self.isAnimating = true
UIView.animate(withDuration: 0.33, delay: 0, options: [.allowUserInteraction]) {
self.contentView.backgroundColor = viewModel.uiBadgeColor.withAlphaComponent(0.15)
} completion: { _ in
guard self.isAnimating else { return }
self.isAnimating = false
UIView.animate(withDuration: 1.0, delay: 0.5, options: [.allowUserInteraction]) {
self.contentView.backgroundColor = .clear
}
}
}
self.state = viewModel.state
title.text = viewModel.fullTitle
if !onlyTitle {
badge.fillColor = viewModel.uiBadgeColor
details.text = viewModel.text
accessory.textLabel.text = viewModel.time
pin.bind(viewModel: viewModel.pinViewModel)
}
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
guard let viewModel = viewModel else {
return nil
}
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { _ in
return self.makeMenu(for: viewModel)
})
}
private func makeMenu(for viewModel: ConsoleNetworkRequestViewModel) -> UIMenu {
let shareAsText = UIAction(title: "Share as Plain Text", image: UIImage(systemName: "square.and.arrow.up")) { _ in
UIActivityViewController.show(with: viewModel.shareAsPlainText())
}
let shareAsMarkdown = UIAction(title: "Share as Markdown", image: UIImage(systemName: "square.and.arrow.up")) { _ in
UIActivityViewController.show(with: viewModel.shareAsMarkdown())
}
let shareAsHTML = UIAction(title: "Share as HTML", image: UIImage(systemName: "square.and.arrow.up")) { _ in
UIActivityViewController.show(with: viewModel.shareAsHTML())
}
let shareAsCURL = UIAction(title: "Share as cURL", image: UIImage(systemName: "square.and.arrow.up")) { _ in
UIActivityViewController.show(with: viewModel.shareAsCURL())
}
var copyItems: [UIAction] = []
if let url = viewModel.url {
copyItems.append(UIAction(title: "Copy URL", image: UIImage(systemName: "doc.on.doc")) { _ in
UXPasteboard.general.string = url
runHapticFeedback()
})
}
if let host = viewModel.host {
copyItems.append(UIAction(title: "Copy Host", image: UIImage(systemName: "doc.on.doc")) { _ in
UXPasteboard.general.string = host
runHapticFeedback()
})
}
if viewModel.containsResponseData {
copyItems.append(UIAction(title: "Copy Response", image: UIImage(systemName: "doc.on.doc")) { _ in
UXPasteboard.general.string = viewModel.responseString
runHapticFeedback()
})
}
let pin = UIAction.makePinAction(with: viewModel.pinViewModel)
let shareGroup = UIMenu(title: "Share", options: [.displayInline], children: [shareAsText, shareAsMarkdown, shareAsHTML, shareAsCURL])
let copyGroup = UIMenu(title: "Copy", options: [.displayInline], children: copyItems)
return UIMenu(title: "", options: [.displayInline], children: [shareGroup, copyGroup, pin])
}
}
private final class CircleView: UIView {
var fillColor: UIColor = .red {
didSet {
setNeedsDisplay()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
widthAnchor.constraint(equalToConstant: 10),
heightAnchor.constraint(equalToConstant: 10),
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
super.draw(rect)
let path = UIBezierPath(ovalIn: rect)
fillColor.setFill()
path.fill()
}
}
#endif