mirror of
https://github.com/kean/Pulse.git
synced 2026-05-30 21:07:33 +00:00
182 lines
5.2 KiB
Swift
182 lines
5.2 KiB
Swift
// The MIT License (MIT)
|
||
//
|
||
// Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean).
|
||
|
||
import SwiftUI
|
||
import Pulse
|
||
import Combine
|
||
import CoreData
|
||
|
||
final class ConsoleNetworkRequestViewModel: Pinnable, ObservableObject {
|
||
private(set) lazy var time = ConsoleMessageViewModel.timeFormatter.string(from: task.createdAt)
|
||
#if os(iOS)
|
||
var uiBadgeColor: UIColor = .gray
|
||
#endif
|
||
|
||
private(set) var badgeColor: Color = .gray
|
||
private(set) var title: String = ""
|
||
private(set) var text: String = ""
|
||
|
||
var fullTitle: String {
|
||
var title = self.title
|
||
#if !os(watchOS)
|
||
if state == .pending, let details = progress.details {
|
||
title += " · \(details)"
|
||
}
|
||
#endif
|
||
#if os(macOS) || os(tvOS)
|
||
title += " · \(time)"
|
||
#endif
|
||
return title
|
||
}
|
||
|
||
private(set) var state: NetworkTaskEntity.State = .pending
|
||
|
||
private(set) lazy var progress = ProgressViewModel(task: task)
|
||
|
||
private let task: NetworkTaskEntity
|
||
private var cancellable: AnyCancellable?
|
||
|
||
static let timeFormatter: DateFormatter = {
|
||
let formatter = DateFormatter()
|
||
formatter.locale = Locale(identifier: "en_US")
|
||
formatter.dateFormat = "HH:mm:ss.SSS"
|
||
return formatter
|
||
}()
|
||
|
||
init(task: NetworkTaskEntity) {
|
||
self.task = task
|
||
self.progress = ProgressViewModel(task: task)
|
||
|
||
self.refresh()
|
||
|
||
self.cancellable = task.objectWillChange.sink { [weak self] in
|
||
self?.refresh()
|
||
withAnimation {
|
||
self?.objectWillChange.send()
|
||
}
|
||
}
|
||
}
|
||
|
||
private func refresh() {
|
||
let state = task.state
|
||
|
||
var title: String = task.httpMethod ?? "GET"
|
||
switch task.state {
|
||
case .pending:
|
||
title += " · "
|
||
title += progress.title.uppercased()
|
||
case .success:
|
||
title += " · "
|
||
title += StatusCodeFormatter.string(for: Int(task.statusCode))
|
||
#if !os(watchOS)
|
||
switch task.type ?? .dataTask {
|
||
case .uploadTask:
|
||
if task.requestBodySize > 0 {
|
||
let sizeText = ByteCountFormatter.string(fromByteCount: task.requestBodySize)
|
||
title += " · "
|
||
title += task.isFromCache ? "Cache" : sizeText
|
||
}
|
||
case .dataTask, .downloadTask:
|
||
if task.responseBodySize > 0 {
|
||
let sizeText = ByteCountFormatter.string(fromByteCount: task.responseBodySize)
|
||
title += " · "
|
||
title += task.isFromCache ? "Cache" : sizeText
|
||
}
|
||
case .streamTask, .webSocketTask:
|
||
break
|
||
}
|
||
#endif
|
||
case .failure:
|
||
title += " · "
|
||
if task.errorCode != 0 {
|
||
if task.errorDomain == URLError.errorDomain {
|
||
title += "\(task.errorCode) \(descriptionForURLErrorCode(Int(task.errorCode)))"
|
||
} else if task.errorDomain == NetworkLogger.DecodingError.domain {
|
||
title += "Decoding Failed"
|
||
} else {
|
||
title += "Error"
|
||
}
|
||
} else {
|
||
title += StatusCodeFormatter.string(for: Int(task.statusCode))
|
||
}
|
||
}
|
||
|
||
if task.duration > 0 {
|
||
title += " · "
|
||
title += DurationFormatter.string(from: task.duration, isPrecise: false)
|
||
}
|
||
|
||
self.title = title
|
||
|
||
self.text = task.url ?? "URL Unavailable"
|
||
|
||
#if os(iOS)
|
||
switch state {
|
||
case .pending: self.uiBadgeColor = .systemYellow
|
||
case .success: self.uiBadgeColor = .systemGreen
|
||
case .failure: self.uiBadgeColor = .systemRed
|
||
}
|
||
#endif
|
||
switch state {
|
||
case .pending: self.badgeColor = .yellow
|
||
case .success: self.badgeColor = .green
|
||
case .failure: self.badgeColor = .red
|
||
}
|
||
|
||
self.state = task.state
|
||
}
|
||
|
||
// MARK: Pins
|
||
|
||
lazy var pinViewModel = PinButtonViewModel(task: task)
|
||
|
||
// MARK: Context Menu
|
||
|
||
#if os(iOS) || os(macOS)
|
||
|
||
func shareAsPlainText() -> ShareItems {
|
||
ShareItems([ConsoleShareService.share(task, output: .plainText)])
|
||
}
|
||
|
||
func shareAsMarkdown() -> ShareItems {
|
||
let text = ConsoleShareService.share(task, output: .markdown)
|
||
let directory = TemporaryDirectory()
|
||
let fileURL = directory.write(text: text, extension: "markdown")
|
||
return ShareItems([fileURL], cleanup: directory.remove)
|
||
}
|
||
|
||
func shareAsHTML() -> ShareItems {
|
||
let text = ConsoleShareService.share(task, output: .html)
|
||
let directory = TemporaryDirectory()
|
||
let fileURL = directory.write(text: text, extension: "html")
|
||
return ShareItems([fileURL], cleanup: directory.remove)
|
||
}
|
||
|
||
func shareAsCURL() -> ShareItems {
|
||
ShareItems([task.cURLDescription()])
|
||
}
|
||
|
||
var containsResponseData: Bool {
|
||
task.responseBodySize > 0
|
||
}
|
||
|
||
// WARNING: This call is relatively expensive.
|
||
var responseString: String? {
|
||
task.responseBody?.data.flatMap { String(data: $0, encoding: .utf8) }
|
||
}
|
||
|
||
var url: String? {
|
||
task.url
|
||
}
|
||
|
||
var host: String? {
|
||
task.host?.value
|
||
}
|
||
|
||
var cURLDescription: String {
|
||
task.cURLDescription()
|
||
}
|
||
#endif
|
||
}
|