Files
Pulse/Sources/PulseUI/Views/SpinnerView.swift

102 lines
3.0 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) 2020–2023 Alexander Grebenyuk (github.com/kean).
import SwiftUI
import CoreData
import Pulse
import Combine
// MARK: - View
struct SpinnerView: View {
@ObservedObject var viewModel: ProgressViewModel
var body: some View {
VStack {
ProgressView()
if let details = viewModel.details {
Text(details)
.padding(.top, 6)
}
}
.foregroundColor(.secondary)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
}
}
#if DEBUG
struct SpinnerView_Previews: PreviewProvider {
static var previews: some View {
SpinnerView(viewModel: .init(title: "Pending", details: "2.5 MB / 6.0 MB"))
.previewLayout(.fixed(width: 300, height: 300))
}
}
#endif
// MARK: - ViewModel
final class ProgressViewModel: ObservableObject {
let title: String
@Published private(set) var details: String?
private var observer1: AnyCancellable?
private var observer2: AnyCancellable?
init(title: String, details: String?) {
self.title = title
self.details = details
}
init(task: NetworkTaskEntity) {
self.title = ProgressViewModel.title(for: task)
if task.state == .pending {
observer1 = task.publisher(for: \.progress, options: [.initial, .new]).sink { [weak self] change in
if let progress = task.progress {
self?.register(for: progress)
}
}
}
}
static func title(for task: NetworkTaskEntity) -> String {
switch task.type ?? .dataTask {
case .downloadTask: return "Downloading"
case .uploadTask: return "Uploading"
default: return "Pending"
}
}
static func details(for task: NetworkTaskEntity) -> String? {
guard let progress = task.progress else {
return nil
}
let completed = progress.completedUnitCount
let total = progress.totalUnitCount
guard completed > 0 || total > 0 else {
return nil
}
let lhs = ByteCountFormatter.string(fromByteCount: max(0, completed))
let rhs = ByteCountFormatter.string(fromByteCount: total)
return total > 0 ? "\(lhs) / \(rhs)" : lhs
}
private func register(for progress: NetworkTaskProgressEntity) {
self.refresh(with: progress)
observer2 = progress.objectWillChange.sink { [weak self] in
self?.refresh(with: progress)
}
}
private func refresh(with progress: NetworkTaskProgressEntity) {
let completed = progress.completedUnitCount
let total = progress.totalUnitCount
if completed > 0 || total > 0 {
let lhs = ByteCountFormatter.string(fromByteCount: max(0, completed))
let rhs = ByteCountFormatter.string(fromByteCount: total)
self.details = total > 0 ? "\(lhs) / \(rhs)" : lhs
}
}
}