mirror of
https://github.com/kean/Pulse.git
synced 2026-05-30 21:07:33 +00:00
102 lines
3.0 KiB
Swift
102 lines
3.0 KiB
Swift
// 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
|
||
}
|
||
}
|
||
}
|