Change how we display progress per #3566.

This commit is contained in:
Maurice Parker
2022-11-11 17:16:42 -06:00
parent 56aa302d3b
commit eba6c364da
16 changed files with 179 additions and 208 deletions

View File

@@ -7,6 +7,7 @@
//
import UIKit
import SwiftUI
import Account
import Articles
import RSCore
@@ -16,13 +17,15 @@ import SafariServices
class MasterFeedViewController: UITableViewController, UndoableCommandRunner, MainControllerIdentifiable {
@IBOutlet weak var filterButton: UIBarButtonItem!
private var refreshProgressView: RefreshProgressView?
@IBOutlet weak var addNewItemButton: UIBarButtonItem! {
didSet {
addNewItemButton.primaryAction = nil
}
}
let refreshProgressModel = RefreshProgressModel()
lazy var progressBarViewController = UIHostingController(rootView: RefreshProgressView(progressBarMode: refreshProgressModel))
var mainControllerIdentifer = MainControllerIdentifier.masterFeed
weak var coordinator: SceneCoordinator!
@@ -75,8 +78,12 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma
refreshControl = UIRefreshControl()
refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged)
refreshControl!.tintColor = .clear
progressBarViewController.view.backgroundColor = .clear
let refreshProgressItemButton = UIBarButtonItem(customView: progressBarViewController.view)
toolbarItems?.insert(refreshProgressItemButton, at: 2)
configureToolbar()
becomeFirstResponder()
}
@@ -595,7 +602,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma
} else {
setFilterButtonToInactive()
}
refreshProgressView?.update()
refreshProgressModel.update()
addNewItemButton?.isEnabled = !AccountManager.shared.activeAccounts.isEmpty
configureContextMenu()
@@ -728,16 +735,6 @@ extension MasterFeedViewController: MasterFeedTableViewCellDelegate {
private extension MasterFeedViewController {
func configureToolbar() {
guard let refreshProgressView = Bundle.main.loadNibNamed("RefreshProgressView", owner: self, options: nil)?[0] as? RefreshProgressView else {
return
}
self.refreshProgressView = refreshProgressView
let refreshProgressItemButton = UIBarButtonItem(customView: refreshProgressView)
toolbarItems?.insert(refreshProgressItemButton, at: 2)
}
func setFilterButtonToActive() {
filterButton?.image = AppAssets.filterActiveImage
filterButton?.accLabelText = NSLocalizedString("Selected - Filter Read Feeds", comment: "Selected - Filter Read Feeds")

View File

@@ -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() {

View File

@@ -1,61 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="ejl-zC-eNy" customClass="RefreshProgressView" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="461" height="90"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" progress="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="Ds3-59-ooT" customClass="RoundedProgressView" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="180.5" y="42.5" width="100" height="5"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="100" id="ReS-sT-7EN"/>
<constraint firstAttribute="height" constant="5" id="oDX-bb-24H"/>
</constraints>
</progressView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7mJ-VZ-zqU">
<rect key="frame" x="214" y="34" width="33" height="22"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
<color key="textColor" systemColor="secondaryLabelColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="sNo-8i-tO3"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="Ds3-59-ooT" firstAttribute="centerX" secondItem="ejl-zC-eNy" secondAttribute="centerX" id="5Rv-6l-HSL"/>
<constraint firstItem="Ds3-59-ooT" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="ejl-zC-eNy" secondAttribute="leading" id="Bck-uf-0G7"/>
<constraint firstItem="7mJ-VZ-zqU" firstAttribute="bottom" secondItem="sNo-8i-tO3" secondAttribute="bottom" id="DVn-hI-PhH"/>
<constraint firstItem="7mJ-VZ-zqU" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="sNo-8i-tO3" secondAttribute="leading" id="Sbp-yf-ts9"/>
<constraint firstItem="7mJ-VZ-zqU" firstAttribute="centerY" secondItem="ejl-zC-eNy" secondAttribute="centerY" id="Shb-X2-Fwc"/>
<constraint firstItem="7mJ-VZ-zqU" firstAttribute="centerX" secondItem="ejl-zC-eNy" secondAttribute="centerX" id="lFg-fm-YmV"/>
<constraint firstItem="sNo-8i-tO3" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="7mJ-VZ-zqU" secondAttribute="trailing" id="mZ2-XG-Kvg"/>
<constraint firstItem="Ds3-59-ooT" firstAttribute="centerY" secondItem="ejl-zC-eNy" secondAttribute="centerY" id="tIh-lb-KbY"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Ds3-59-ooT" secondAttribute="trailing" id="vSU-N6-Sk5"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="label" destination="7mJ-VZ-zqU" id="MHr-r4-qop"/>
<outlet property="progressView" destination="Ds3-59-ooT" id="TjM-db-LxM"/>
</connections>
<point key="canvasLocation" x="-75" y="-117"/>
</view>
</objects>
<resources>
<systemColor name="secondaryLabelColor">
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>

View File

@@ -7,6 +7,7 @@
//
import UIKit
import SwiftUI
import RSCore
import Account
import Articles
@@ -21,7 +22,9 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
@IBOutlet weak var markAllAsReadButton: UIBarButtonItem!
private var refreshProgressView: RefreshProgressView!
let refreshProgressModel = RefreshProgressModel()
lazy var progressBarViewController = UIHostingController(rootView: RefreshProgressView(progressBarMode: refreshProgressModel))
private var refreshProgressItemButton: UIBarButtonItem!
private var firstUnreadButton: UIBarButtonItem!
@@ -95,13 +98,13 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
refreshControl = UIRefreshControl()
refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged)
refreshControl!.tintColor = .clear
progressBarViewController.view.backgroundColor = .clear
refreshProgressItemButton = UIBarButtonItem(customView: progressBarViewController.view)
configureToolbar()
refreshProgressView = Bundle.main.loadNibNamed("RefreshProgressView", owner: self, options: nil)?[0] as? RefreshProgressView
refreshProgressItemButton = UIBarButtonItem(customView: refreshProgressView!)
resetUI(resetScroll: true)
// Load the table and then scroll to the saved position if available
@@ -243,7 +246,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
}
func updateUI() {
refreshProgressView?.update()
refreshProgressModel.update()
updateTitleUnreadCount()
updateToolbar()
}
@@ -614,12 +617,6 @@ private extension MasterTimelineViewController {
return
}
guard let refreshProgressView = Bundle.main.loadNibNamed("RefreshProgressView", owner: self, options: nil)?[0] as? RefreshProgressView else {
return
}
self.refreshProgressView = refreshProgressView
let refreshProgressItemButton = UIBarButtonItem(customView: refreshProgressView)
toolbarItems?.insert(refreshProgressItemButton, at: 2)
}

View File

@@ -1,21 +0,0 @@
//
// RoundedProgressView.swift
// NetNewsWire
//
// Created by Maurice Parker on 10/29/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import UIKit
class RoundedProgressView: UIProgressView {
override func layoutSubviews() {
super.layoutSubviews()
subviews.forEach { subview in
subview.layer.masksToBounds = true
subview.layer.cornerRadius = bounds.height / 2.0
}
}
}