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:
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user