mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Change how we display progress per #3566.
This commit is contained in:
@@ -1,27 +1,80 @@
|
||||
//
|
||||
// RefeshProgressView.swift
|
||||
// NetNewsWire-iOS
|
||||
// ProgressBarView.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Maurice Parker on 10/24/19.
|
||||
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||
// Created by Maurice Parker on 11/11/22.
|
||||
// Copyright © 2022 Ranchero Software. All rights reserved.
|
||||
//
|
||||
// IndetermineProgressView inspired by https://daringsnowball.net/articles/indeterminate-linear-progress-view/
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import Account
|
||||
|
||||
class RefreshProgressView: UIView {
|
||||
struct RefreshProgressView: View {
|
||||
|
||||
@IBOutlet weak var progressView: UIProgressView!
|
||||
@IBOutlet weak var label: UILabel!
|
||||
static let width: CGFloat = 100
|
||||
static let height: CGFloat = 5
|
||||
|
||||
override func awakeFromNib() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil)
|
||||
update()
|
||||
scheduleUpdateRefreshLabel()
|
||||
@ObservedObject var refreshProgressModel: RefreshProgressModel
|
||||
@State private var offset: CGFloat = 0
|
||||
|
||||
isAccessibilityElement = true
|
||||
accessibilityTraits = [.updatesFrequently, .notEnabled]
|
||||
init(progressBarMode: RefreshProgressModel) {
|
||||
self.refreshProgressModel = progressBarMode
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if refreshProgressModel.isRefreshing {
|
||||
if refreshProgressModel.isIndeterminate {
|
||||
indeterminateProgressView
|
||||
} else {
|
||||
ProgressView(value: refreshProgressModel.progress)
|
||||
.progressViewStyle(LinearProgressViewStyle())
|
||||
.frame(width: Self.width, height: Self.height)
|
||||
}
|
||||
} else {
|
||||
Text(refreshProgressModel.label)
|
||||
.accessibilityLabel(refreshProgressModel.label)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
.frame(width: 200, height: 44)
|
||||
}
|
||||
|
||||
var indeterminateProgressView: some View {
|
||||
Rectangle()
|
||||
.foregroundColor(.gray.opacity(0.15))
|
||||
.overlay(
|
||||
Rectangle()
|
||||
.foregroundColor(Color.accentColor)
|
||||
.frame(width: Self.width * 0.26, height: Self.height)
|
||||
.clipShape(Capsule())
|
||||
.offset(x: -Self.width * 0.6, y: 0)
|
||||
.offset(x: Self.width * 1.2 * self.offset, y: 0)
|
||||
.animation(.default.repeatForever().speed(0.265), value: self.offset)
|
||||
.onAppear{
|
||||
withAnimation {
|
||||
self.offset = 1
|
||||
}
|
||||
}
|
||||
)
|
||||
.clipShape(Capsule())
|
||||
.animation(.default, value: refreshProgressModel.isRefreshing)
|
||||
.frame(width: Self.width, height: Self.height)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class RefreshProgressModel: ObservableObject {
|
||||
|
||||
@Published var isRefreshing = false
|
||||
@Published var isIndeterminate = false
|
||||
@Published var progress = 0.0
|
||||
@Published var label = String()
|
||||
|
||||
init() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil)
|
||||
}
|
||||
|
||||
func update() {
|
||||
@@ -31,52 +84,32 @@ class RefreshProgressView: UIView {
|
||||
updateRefreshLabel()
|
||||
}
|
||||
}
|
||||
|
||||
override func didMoveToSuperview() {
|
||||
progressChanged(animated: false)
|
||||
}
|
||||
|
||||
|
||||
@objc func progressDidChange(_ note: Notification) {
|
||||
progressChanged(animated: true)
|
||||
}
|
||||
|
||||
@objc func contentSizeCategoryDidChange(_ note: Notification) {
|
||||
// This hack is probably necessary because custom views in the toolbar don't get
|
||||
// notifications that the content size changed.
|
||||
label.font = UIFont.preferredFont(forTextStyle: .footnote)
|
||||
}
|
||||
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private extension RefreshProgressView {
|
||||
|
||||
private extension RefreshProgressModel {
|
||||
|
||||
func progressChanged(animated: Bool) {
|
||||
// Layout may crash if not in the view hierarchy.
|
||||
// https://github.com/Ranchero-Software/NetNewsWire/issues/1764
|
||||
let isInViewHierarchy = self.superview != nil
|
||||
|
||||
let progress = AccountManager.shared.combinedRefreshProgress
|
||||
|
||||
if progress.isComplete {
|
||||
if isInViewHierarchy {
|
||||
progressView.setProgress(1, animated: animated)
|
||||
}
|
||||
let combinedRefreshProgress = AccountManager.shared.combinedRefreshProgress
|
||||
isIndeterminate = combinedRefreshProgress.isIndeterminate
|
||||
|
||||
if combinedRefreshProgress.isComplete {
|
||||
isRefreshing = false
|
||||
progress = 1
|
||||
|
||||
func completeLabel() {
|
||||
// Check that there are no pending downloads.
|
||||
if AccountManager.shared.combinedRefreshProgress.isComplete {
|
||||
self.updateRefreshLabel()
|
||||
self.label.isHidden = false
|
||||
self.progressView.isHidden = true
|
||||
if self.superview != nil {
|
||||
self.progressView.setProgress(0, animated: animated)
|
||||
}
|
||||
updateRefreshLabel()
|
||||
progress = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,19 +121,16 @@ private extension RefreshProgressView {
|
||||
completeLabel()
|
||||
}
|
||||
} else {
|
||||
label.isHidden = true
|
||||
progressView.isHidden = false
|
||||
if isInViewHierarchy {
|
||||
let percent = Float(progress.numberCompleted) / Float(progress.numberOfTasks)
|
||||
isRefreshing = true
|
||||
let percent = Double(combinedRefreshProgress.numberCompleted) / Double(combinedRefreshProgress.numberOfTasks)
|
||||
|
||||
// Don't let the progress bar go backwards unless we need to go back more than 25%
|
||||
if percent > progressView.progress || progressView.progress - percent > 0.25 {
|
||||
progressView.setProgress(percent, animated: animated)
|
||||
}
|
||||
// Don't let the progress bar go backwards unless we need to go back more than 25%
|
||||
if percent > progress || (progress - percent) > 0.25 {
|
||||
progress = percent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func updateRefreshLabel() {
|
||||
if let accountLastArticleFetchEndTime = AccountManager.shared.lastArticleFetchEndTime {
|
||||
|
||||
@@ -111,17 +141,15 @@ private extension RefreshProgressView {
|
||||
let refreshed = relativeDateTimeFormatter.localizedString(for: accountLastArticleFetchEndTime, relativeTo: Date())
|
||||
let localizedRefreshText = NSLocalizedString("Updated %@", comment: "Updated")
|
||||
let refreshText = NSString.localizedStringWithFormat(localizedRefreshText as NSString, refreshed) as String
|
||||
label.text = refreshText
|
||||
label = refreshText
|
||||
|
||||
} else {
|
||||
label.text = NSLocalizedString("Updated Just Now", comment: "Updated Just Now")
|
||||
label = NSLocalizedString("Updated Just Now", comment: "Updated Just Now")
|
||||
}
|
||||
|
||||
} else {
|
||||
label.text = ""
|
||||
label = ""
|
||||
}
|
||||
|
||||
accessibilityLabel = label.text
|
||||
}
|
||||
|
||||
func scheduleUpdateRefreshLabel() {
|
||||
|
||||
Reference in New Issue
Block a user