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

@@ -426,6 +426,7 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
func accountDidInitialize(_ account: Account) {
self.account = account
refreshProgress.name = account.nameForDisplay
accountZone.delegate = CloudKitAcountZoneDelegate(account: account, refreshProgress: refreshProgress, articlesZone: articlesZone)
articlesZone.delegate = CloudKitArticlesZoneDelegate(account: account, database: database, articlesZone: articlesZone)
@@ -484,6 +485,7 @@ private extension CloudKitAccountDelegate {
completion(.failure(error))
}
refreshProgress.isIndeterminate = true
refreshProgress.addToNumberOfTasksAndRemaining(3)
accountZone.fetchChangesInZone() { result in
self.refreshProgress.completeTask()
@@ -495,6 +497,7 @@ private extension CloudKitAccountDelegate {
case .success:
self.refreshArticleStatus(for: account) { result in
self.refreshProgress.completeTask()
self.refreshProgress.isIndeterminate = false
switch result {
case .success:
@@ -522,6 +525,7 @@ private extension CloudKitAccountDelegate {
func standardRefreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
let intialWebFeedsCount = account.flattenedWebFeeds().count
refreshProgress.isIndeterminate = true
refreshProgress.addToNumberOfTasksAndRemaining(3 + intialWebFeedsCount)
func fail(_ error: Error) {
@@ -542,6 +546,7 @@ private extension CloudKitAccountDelegate {
switch result {
case .success:
self.refreshProgress.completeTask()
self.refreshProgress.isIndeterminate = false
self.combinedRefresh(account, webFeeds) { result in
self.sendArticleStatus(for: account, showProgress: true) { _ in
self.refreshProgress.clear()

View File

@@ -18,25 +18,63 @@ public struct CombinedRefreshProgress {
public let numberRemaining: Int
public let numberCompleted: Int
public let isComplete: Bool
public let isIndeterminate: Bool
public let label: String
init(numberOfTasks: Int, numberRemaining: Int, numberCompleted: Int) {
init(numberOfTasks: Int, numberRemaining: Int, numberCompleted: Int, isIndeterminate: Bool, label: String) {
self.numberOfTasks = max(numberOfTasks, 0)
self.numberRemaining = max(numberRemaining, 0)
self.numberCompleted = max(numberCompleted, 0)
self.isComplete = numberRemaining < 1
self.isIndeterminate = isIndeterminate
self.label = label
}
public init(downloadProgressArray: [DownloadProgress]) {
var numberOfDownloadsPossible = 0
var numberOfDownloadsActive = 0
var numberOfTasks = 0
var numberRemaining = 0
var numberCompleted = 0
var isIndeterminate = false
var isInprecise = false
for downloadProgress in downloadProgressArray {
numberOfDownloadsPossible += 1
numberOfDownloadsActive += downloadProgress.isComplete ? 0 : 1
numberOfTasks += downloadProgress.numberOfTasks
numberRemaining += downloadProgress.numberRemaining
numberCompleted += downloadProgress.numberCompleted
if downloadProgress.isIndeterminate {
isIndeterminate = true
}
if !downloadProgress.isPrecise {
isInprecise = true
}
}
self.init(numberOfTasks: numberOfTasks, numberRemaining: numberRemaining, numberCompleted: numberCompleted)
var label = ""
if numberOfDownloadsActive > 0 {
if isInprecise {
if numberOfDownloadsActive == 1 {
if let activeName = downloadProgressArray.first(where: { $0.isComplete == false })?.name {
let formatString = NSLocalizedString("Refreshing %@", comment: "Status bar progress")
label = NSString(format: formatString as NSString, activeName) as String
}
} else {
let formatString = NSLocalizedString("Refreshing %@ accounts", comment: "Status bar progress")
label = NSString(format: formatString as NSString, NSNumber(value: numberOfDownloadsActive)) as String
}
} else {
let formatString = NSLocalizedString("%@ of %@", comment: "Status bar progress")
label = NSString(format: formatString as NSString, NSNumber(value: numberCompleted), NSNumber(value: numberOfTasks)) as String
}
}
self.init(numberOfTasks: numberOfTasks, numberRemaining: numberRemaining, numberCompleted: numberCompleted, isIndeterminate: isIndeterminate, label: label)
}
}

View File

@@ -576,6 +576,7 @@ final class FeedbinAccountDelegate: AccountDelegate, Logging {
func accountDidInitialize(_ account: Account) {
credentials = try? account.retrieveCredentials(type: .basic)
refreshProgress.name = account.nameForDisplay
}
func accountWillBeDeleted(_ account: Account) {

View File

@@ -527,6 +527,7 @@ final class FeedlyAccountDelegate: AccountDelegate, Logging {
func accountDidInitialize(_ account: Account) {
initializedAccount = account
credentials = try? account.retrieveCredentials(type: .oauthAccessToken)
refreshProgress.name = account.nameForDisplay
}
func accountWillBeDeleted(_ account: Account) {

View File

@@ -222,6 +222,8 @@ final class LocalAccountDelegate: AccountDelegate, Logging {
func accountDidInitialize(_ account: Account) {
self.account = account
refreshProgress.name = account.nameForDisplay
refreshProgress.isPrecise = true
}
func accountWillBeDeleted(_ account: Account) {

View File

@@ -603,6 +603,7 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging {
func accountDidInitialize(_ account: Account) {
credentials = try? account.retrieveCredentials(type: .newsBlurSessionId)
refreshProgress.name = account.nameForDisplay
}
func accountWillBeDeleted(_ account: Account) {

View File

@@ -628,6 +628,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate, Logging {
func accountDidInitialize(_ account: Account) {
credentials = try? account.retrieveCredentials(type: .readerAPIKey)
refreshProgress.name = account.nameForDisplay
}
func accountWillBeDeleted(_ account: Account) {

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="21225" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21225"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
<capability name="NSView safe area layout guides" minToolsVersion="12.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@@ -175,9 +175,9 @@
<constraint firstAttribute="width" constant="40" id="1Yw-ER-8pT"/>
</constraints>
</progressIndicator>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="iyL-pW-cT6">
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="iyL-pW-cT6">
<rect key="frame" x="62" y="6" width="160" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Label" id="dVE-XG-mlU">
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Label" id="dVE-XG-mlU">
<font key="font" metaFont="system"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>

View File

@@ -31,8 +31,8 @@ final class SidebarStatusBarView: NSView {
}
override func awakeFromNib() {
progressIndicator.isHidden = true
progressIndicator.usesThreadedAnimation = true
progressLabel.isHidden = true
let progressLabelFontSize = progressLabel.font?.pointSize ?? 13.0
@@ -43,20 +43,18 @@ final class SidebarStatusBarView: NSView {
}
@objc func updateUI() {
guard let progress = progress else {
stopProgressIfNeeded()
return
}
updateProgressIndicator(progress)
updateProgressLabel(progress)
progressLabel.stringValue = progress.label
}
// MARK: Notifications
@objc dynamic func progressDidChange(_ notification: Notification) {
progress = AccountManager.shared.combinedRefreshProgress
}
}
@@ -68,10 +66,10 @@ private extension SidebarStatusBarView {
static let animationDuration = 0.2
func stopProgressIfNeeded() {
if !isAnimatingProgress {
return
}
isAnimatingProgress = false
self.progressIndicator.stopAnimation(self)
progressIndicator.isHidden = true
@@ -88,10 +86,10 @@ private extension SidebarStatusBarView {
}
func startProgressIfNeeded() {
if isAnimatingProgress {
return
}
isAnimatingProgress = true
progressIndicator.isHidden = false
progressLabel.isHidden = false
@@ -108,12 +106,16 @@ private extension SidebarStatusBarView {
}
func updateProgressIndicator(_ progress: CombinedRefreshProgress) {
if progress.isComplete {
stopProgressIfNeeded()
return
}
if progressIndicator.isIndeterminate != progress.isIndeterminate {
stopProgressIfNeeded()
progressIndicator.isIndeterminate = progress.isIndeterminate
}
startProgressIfNeeded()
let maxValue = Double(progress.numberOfTasks)
@@ -127,16 +129,4 @@ private extension SidebarStatusBarView {
}
}
func updateProgressLabel(_ progress: CombinedRefreshProgress) {
if progress.isComplete {
progressLabel.stringValue = ""
return
}
let formatString = NSLocalizedString("%@ of %@", comment: "Status bar progress")
let s = NSString(format: formatString as NSString, NSNumber(value: progress.numberCompleted), NSNumber(value: progress.numberOfTasks))
progressLabel.stringValue = s as String
}
}

View File

@@ -119,7 +119,6 @@
511D43D2231FA62C00FB1562 /* GlobalKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 844B5B641FEA11F200C7C76A /* GlobalKeyboardShortcuts.plist */; };
511D43EF231FBDE900FB1562 /* LaunchScreenPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 511D43ED231FBDE800FB1562 /* LaunchScreenPad.storyboard */; };
511D4419231FC02D00FB1562 /* KeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511D4410231FC02D00FB1562 /* KeyboardManager.swift */; };
51236339236915B100951F16 /* RoundedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512363372369155100951F16 /* RoundedProgressView.swift */; };
512392BE24E33A3C00F11704 /* RedditSelectAccountTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE603246AF37B00731738 /* RedditSelectAccountTableViewController.swift */; };
512392BF24E33A3C00F11704 /* RedditSelectSortTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE607246AFC9900731738 /* RedditSelectSortTableViewController.swift */; };
512392C024E33A3C00F11704 /* RedditAdd.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 516AE5FF246AF34100731738 /* RedditAdd.storyboard */; };
@@ -398,8 +397,6 @@
51C4CFF224D37D1F00AF9874 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4CFEF24D37D1F00AF9874 /* Secrets.swift */; };
51C4CFF624D37DD500AF9874 /* Secrets in Frameworks */ = {isa = PBXBuildFile; productRef = 51C4CFF524D37DD500AF9874 /* Secrets */; };
51C9DE5823EA2EF4003D5A6D /* WrapperScriptMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C9DE5723EA2EF4003D5A6D /* WrapperScriptMessageHandler.swift */; };
51CE1C0923621EDA005548FC /* RefreshProgressView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51CE1C0823621EDA005548FC /* RefreshProgressView.xib */; };
51CE1C0B23622007005548FC /* RefreshProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CE1C0A23622006005548FC /* RefreshProgressView.swift */; };
51CE1C712367721A005548FC /* testURLsOfCurrentArticle.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EADB213660A100CF2DE4 /* testURLsOfCurrentArticle.applescript */; };
51D0214626ED617100FF2E0F /* core.css in Resources */ = {isa = PBXBuildFile; fileRef = 51D0214526ED617100FF2E0F /* core.css */; };
51D0214726ED617100FF2E0F /* core.css in Resources */ = {isa = PBXBuildFile; fileRef = 51D0214526ED617100FF2E0F /* core.css */; };
@@ -407,6 +404,7 @@
51D205EF28E3CF8D007C46EF /* LinkTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D205EE28E3CF8D007C46EF /* LinkTextField.swift */; };
51D205F028E3CF8D007C46EF /* LinkTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D205EE28E3CF8D007C46EF /* LinkTextField.swift */; };
51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; };
51D5D116291EEF5600AA1278 /* RefreshProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D5D114291EEDC600AA1278 /* RefreshProgressView.swift */; };
51D6A5BC23199C85001C27D8 /* MasterTimelineDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D6A5BB23199C85001C27D8 /* MasterTimelineDataSource.swift */; };
51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; };
51DC07982552083500A3F79F /* ArticleTextSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC07972552083500A3F79F /* ArticleTextSize.swift */; };
@@ -1198,7 +1196,6 @@
511B9805237DCAC90028BCAA /* UserInfoKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoKey.swift; sourceTree = "<group>"; };
511D43EE231FBDE800FB1562 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreenPad.storyboard; sourceTree = "<group>"; };
511D4410231FC02D00FB1562 /* KeyboardManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardManager.swift; sourceTree = "<group>"; };
512363372369155100951F16 /* RoundedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedProgressView.swift; sourceTree = "<group>"; };
5126EE96226CB48A00C22AFC /* SceneCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneCoordinator.swift; sourceTree = "<group>"; };
5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailKeyboardDelegate.swift; sourceTree = "<group>"; };
5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = DetailKeyboardShortcuts.plist; sourceTree = "<group>"; };
@@ -1348,10 +1345,9 @@
51CD32C424D2CF1D009ABAEF /* Articles */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Articles; sourceTree = "<group>"; };
51CD32C624D2DEF9009ABAEF /* Account */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Account; sourceTree = "<group>"; };
51CD32C724D2E06C009ABAEF /* Secrets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Secrets; sourceTree = "<group>"; };
51CE1C0823621EDA005548FC /* RefreshProgressView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RefreshProgressView.xib; sourceTree = "<group>"; };
51CE1C0A23622006005548FC /* RefreshProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshProgressView.swift; sourceTree = "<group>"; };
51D0214526ED617100FF2E0F /* core.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = core.css; sourceTree = "<group>"; };
51D205EE28E3CF8D007C46EF /* LinkTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkTextField.swift; sourceTree = "<group>"; };
51D5D114291EEDC600AA1278 /* RefreshProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshProgressView.swift; sourceTree = "<group>"; };
51D6A5BB23199C85001C27D8 /* MasterTimelineDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineDataSource.swift; sourceTree = "<group>"; };
51D87EE02311D34700E63F03 /* ActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityType.swift; sourceTree = "<group>"; };
51DC07972552083500A3F79F /* ArticleTextSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleTextSize.swift; sourceTree = "<group>"; };
@@ -2063,7 +2059,6 @@
5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */,
5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */,
51A9A6092382FD240033AADF /* PoppableGestureRecognizerDelegate.swift */,
512363372369155100951F16 /* RoundedProgressView.swift */,
51C45250226506F400C03939 /* String-Extensions.swift */,
5108F6D723763094001ABC45 /* TickMarkSlider.swift */,
C5A6ED6C23C9B0C800AB6BE2 /* UIActivityViewController-Extensions.swift */,
@@ -2086,8 +2081,7 @@
51C45264226508F600C03939 /* MasterFeedViewController.swift */,
51627A6623861DA3007B3B4B /* MasterFeedViewController+Drag.swift */,
51627A6823861DED007B3B4B /* MasterFeedViewController+Drop.swift */,
51CE1C0A23622006005548FC /* RefreshProgressView.swift */,
51CE1C0823621EDA005548FC /* RefreshProgressView.xib */,
51D5D114291EEDC600AA1278 /* RefreshProgressView.swift */,
5195C1D92720205F00888867 /* ShadowTableChanges.swift */,
51C45260226508F600C03939 /* Cell */,
);
@@ -3474,7 +3468,6 @@
49F40DF92335B71000552BF4 /* newsfoot.js in Resources */,
512392C024E33A3C00F11704 /* RedditAdd.storyboard in Resources */,
5177C21327B07CFE00643901 /* NewsFax.nnwtheme in Resources */,
51CE1C0923621EDA005548FC /* RefreshProgressView.xib in Resources */,
84C9FC9D2262A1A900D921D6 /* Assets.xcassets in Resources */,
514219582353C28900E07E2C /* main_ios.js in Resources */,
DFCE4F9528EF278300405869 /* Thanks.md in Resources */,
@@ -4036,7 +4029,6 @@
51E36E71239D6610006F47A5 /* AddFeedSelectFolderTableViewCell.swift in Sources */,
512DD4C92430086400C17B1F /* CloudKitAccountViewController.swift in Sources */,
840D617F2029031C009BC708 /* AppDelegate.swift in Sources */,
51236339236915B100951F16 /* RoundedProgressView.swift in Sources */,
512E08E72268801200BDCFDD /* WebFeedTreeControllerDelegate.swift in Sources */,
51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */,
51EF0F79227716380050506E /* ColorHash.swift in Sources */,
@@ -4135,6 +4127,7 @@
51EF0F802277A8330050506E /* MasterTimelineCellLayout.swift in Sources */,
51F85BF722749FA100C787DC /* UIFont-Extensions.swift in Sources */,
51C452AF2265108300C03939 /* ArticleArray.swift in Sources */,
51D5D116291EEF5600AA1278 /* RefreshProgressView.swift in Sources */,
51C4528E2265099C00C03939 /* SmartFeedsController.swift in Sources */,
51C9DE5823EA2EF4003D5A6D /* WrapperScriptMessageHandler.swift in Sources */,
51B5C87D23F2346200032075 /* ExtensionContainersFile.swift in Sources */,
@@ -4209,7 +4202,6 @@
51FFF0C4235EE8E5002762AA /* VibrantButton.swift in Sources */,
51C45259226508D300C03939 /* AppDefaults.swift in Sources */,
510FFAB326EEA22C00F32265 /* ArticleThemesTableViewController.swift in Sources */,
51CE1C0B23622007005548FC /* RefreshProgressView.swift in Sources */,
511D4419231FC02D00FB1562 /* KeyboardManager.swift in Sources */,
51A1699D235E10D700EB091F /* SettingsViewController.swift in Sources */,
51C45293226509C800C03939 /* StarredFeedDelegate.swift in Sources */,

View File

@@ -96,8 +96,8 @@
"repositoryURL": "https://github.com/Ranchero-Software/RSWeb.git",
"state": {
"branch": null,
"revision": "a69f0d74c8f7bc4abdad5407aafca471fc3277f1",
"version": "1.0.5"
"revision": "c8d6212b08ae86142105e828fda391a6503a2ea7",
"version": "1.0.6"
}
},
{

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
}
}
}