mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Merge pull request #2210 from philviso/swiftui-progressview
Implement iOS progress view
This commit is contained in:
@@ -69,7 +69,7 @@ struct AppAssets {
|
||||
}()
|
||||
|
||||
static var faviconTemplateImage: RSImage = {
|
||||
return RSImage(named: "FaviconTemplateImage")!
|
||||
return RSImage(named: "faviconTemplateImage")!
|
||||
}()
|
||||
|
||||
static var settingsImage: Image = {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// PreviewProvider+RefreshProgressModel.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Phil Viso on 7/3/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Account
|
||||
import Foundation
|
||||
import RSWeb
|
||||
import SwiftUI
|
||||
|
||||
extension PreviewProvider {
|
||||
|
||||
static func refreshProgressModel(lastRefreshDate: Date?,
|
||||
tasksCompleted: Int,
|
||||
totalTasks: Int) -> RefreshProgressModel {
|
||||
return RefreshProgressModel { () -> Date? in
|
||||
return lastRefreshDate
|
||||
} combinedRefreshProgressProvider: { () -> CombinedRefreshProgress in
|
||||
let progress = DownloadProgress(numberOfTasks: totalTasks)
|
||||
progress.numberRemaining = totalTasks - tasksCompleted
|
||||
|
||||
return CombinedRefreshProgress(downloadProgressArray: [progress])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
101
Multiplatform/Shared/RefreshProgressModel.swift
Normal file
101
Multiplatform/Shared/RefreshProgressModel.swift
Normal file
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// RefreshProgressModel.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Phil Viso on 7/2/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Account
|
||||
import Combine
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
class RefreshProgressModel: ObservableObject {
|
||||
|
||||
enum State {
|
||||
case refreshProgress(Float)
|
||||
case lastRefreshDateText(String)
|
||||
case none
|
||||
}
|
||||
|
||||
@Published var state = State.none
|
||||
|
||||
private static var dateFormatter: RelativeDateTimeFormatter = {
|
||||
let formatter = RelativeDateTimeFormatter()
|
||||
formatter.dateTimeStyle = .named
|
||||
|
||||
return formatter
|
||||
}()
|
||||
|
||||
private let lastRefreshDate: () -> Date?
|
||||
private let combinedRefreshProgress: () -> CombinedRefreshProgress
|
||||
|
||||
private static let lastRefreshDateTextUpdateInterval = 60
|
||||
private static let lastRefreshDateTextRelativeDateFormattingThreshold = 60.0
|
||||
|
||||
init(lastRefreshDateProvider: @escaping () -> Date?,
|
||||
combinedRefreshProgressProvider: @escaping () -> CombinedRefreshProgress) {
|
||||
self.lastRefreshDate = lastRefreshDateProvider
|
||||
self.combinedRefreshProgress = combinedRefreshProgressProvider
|
||||
|
||||
updateState()
|
||||
|
||||
observeRefreshProgress()
|
||||
scheduleLastRefreshDateTextUpdate()
|
||||
}
|
||||
|
||||
// MARK: Observing account changes
|
||||
|
||||
private func observeRefreshProgress() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateState), name: .AccountRefreshProgressDidChange, object: nil)
|
||||
}
|
||||
|
||||
// MARK: Refreshing state
|
||||
|
||||
@objc private func updateState() {
|
||||
let progress = combinedRefreshProgress()
|
||||
|
||||
if !progress.isComplete {
|
||||
let fractionCompleted = Float(progress.numberCompleted) / Float(progress.numberOfTasks)
|
||||
self.state = .refreshProgress(fractionCompleted)
|
||||
} else if let lastRefreshDate = self.lastRefreshDate() {
|
||||
let text = localizedLastRefreshText(lastRefreshDate: lastRefreshDate)
|
||||
self.state = .lastRefreshDateText(text)
|
||||
} else {
|
||||
self.state = .none
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduleLastRefreshDateTextUpdate() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(Self.lastRefreshDateTextUpdateInterval)) {
|
||||
self.updateState()
|
||||
self.scheduleLastRefreshDateTextUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
private func localizedLastRefreshText(lastRefreshDate: Date) -> String {
|
||||
let now = Date()
|
||||
|
||||
if now > lastRefreshDate.addingTimeInterval(Self.lastRefreshDateTextRelativeDateFormattingThreshold) {
|
||||
let localizedDate = Self.dateFormatter.localizedString(for: lastRefreshDate, relativeTo: now)
|
||||
let formatString = NSLocalizedString("Updated %@", comment: "Updated") as NSString
|
||||
|
||||
return NSString.localizedStringWithFormat(formatString, localizedDate) as String
|
||||
} else {
|
||||
return NSLocalizedString("Updated Just Now", comment: "Updated Just Now")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension RefreshProgressModel {
|
||||
|
||||
convenience init() {
|
||||
self.init(
|
||||
lastRefreshDateProvider: { AccountManager.shared.lastArticleFetchEndTime },
|
||||
combinedRefreshProgressProvider: { AccountManager.shared.combinedRefreshProgress }
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,10 +11,19 @@ import Account
|
||||
|
||||
final class SceneModel: ObservableObject {
|
||||
|
||||
@Published var refreshProgressState = RefreshProgressModel.State.none
|
||||
|
||||
var sidebarModel: SidebarModel?
|
||||
var timelineModel: TimelineModel?
|
||||
var articleModel: ArticleModel?
|
||||
|
||||
private let refreshProgressModel: RefreshProgressModel
|
||||
|
||||
init(refreshProgressModel: RefreshProgressModel = RefreshProgressModel()) {
|
||||
self.refreshProgressModel = refreshProgressModel
|
||||
self.refreshProgressModel.$state.assign(to: self.$refreshProgressState)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: SidebarModelDelegate
|
||||
|
||||
57
Multiplatform/Shared/Sidebar/RefreshProgressView.swift
Normal file
57
Multiplatform/Shared/Sidebar/RefreshProgressView.swift
Normal file
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// RefreshProgressView.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Phil Viso on 7/2/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RefreshProgressView: View {
|
||||
|
||||
@EnvironmentObject var sceneModel: SceneModel
|
||||
|
||||
@ViewBuilder var body: some View {
|
||||
switch sceneModel.refreshProgressState {
|
||||
case .refreshProgress(let progress):
|
||||
ProgressView(value: progress)
|
||||
.frame(width: progressViewWidth())
|
||||
case .lastRefreshDateText(let text):
|
||||
Text(text)
|
||||
.lineLimit(1)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
case .none:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK -
|
||||
|
||||
private func progressViewWidth() -> CGFloat {
|
||||
#if os(iOS)
|
||||
return 100.0
|
||||
#endif
|
||||
|
||||
#if os(macOS)
|
||||
return 40.0
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct RefreshProgressView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
RefreshProgressView()
|
||||
.environmentObject(refreshProgressModel(lastRefreshDate: nil, tasksCompleted: 1, totalTasks: 2))
|
||||
.previewDisplayName("Refresh in progress")
|
||||
|
||||
RefreshProgressView()
|
||||
.environmentObject(refreshProgressModel(lastRefreshDate: Date(timeIntervalSinceNow: -120.0), tasksCompleted: 0, totalTasks: 0))
|
||||
.previewDisplayName("Last refreshed with date")
|
||||
}
|
||||
.previewLayout(.sizeThatFits)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
|
||||
struct SidebarToolbar: ViewModifier {
|
||||
|
||||
@EnvironmentObject private var appSettings: AppDefaults
|
||||
@@ -25,23 +24,25 @@ struct SidebarToolbar: ViewModifier {
|
||||
AppAssets.settingsImage
|
||||
.font(.title3)
|
||||
.foregroundColor(.accentColor)
|
||||
Spacer()
|
||||
}).help("Settings")
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .automatic, content: {
|
||||
ToolbarItem {
|
||||
Spacer()
|
||||
Text("Last updated")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .automatic) {
|
||||
RefreshProgressView()
|
||||
}
|
||||
|
||||
ToolbarItem {
|
||||
Spacer()
|
||||
})
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .automatic, content: {
|
||||
Button(action: {
|
||||
viewModel.showActionSheet = true
|
||||
}, label: {
|
||||
Spacer()
|
||||
AppAssets.addMenuImage
|
||||
.font(.title3)
|
||||
.foregroundColor(.accentColor)
|
||||
|
||||
Reference in New Issue
Block a user