Merge pull request #547 from omnivore-app/fix/pending-content-loading

Pending content loading
This commit is contained in:
Satindar Dhillon 2022-05-04 20:18:22 -07:00 committed by GitHub
commit ee8cae75cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 42 additions and 37 deletions

View file

@ -14,7 +14,7 @@ import WebKit
@State var safariWebLink: SafariWebLink?
@State private var navBarVisibilityRatio = 1.0
@State private var showDeleteConfirmation = false
@State private var showOverlay = true
@State private var progressViewOpacity = 0.0
@State var increaseFontActionID: UUID?
@State var decreaseFontActionID: UUID?
@State var annotationSaveTransactionID: UUID?
@ -156,21 +156,6 @@ import WebKit
annotationSaveTransactionID: $annotationSaveTransactionID,
annotation: $annotation
)
.overlay(
Group {
if showOverlay {
Color.systemBackground
.transition(.opacity)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
withAnimation(.linear(duration: 0.2)) {
showOverlay = false
}
}
}
}
}
)
.sheet(item: $safariWebLink) {
SafariView(url: $0.url)
}
@ -186,9 +171,16 @@ import WebKit
}
)
}
} else if let errorMessage = viewModel.errorMessage {
Text(errorMessage).padding()
} else {
Text(viewModel.contentFetchFailed ? "Unable to fetch content." : "Processing...")
.padding()
ProgressView()
.opacity(progressViewOpacity)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) {
progressViewOpacity = 1
}
}
.task {
await viewModel.loadContent(dataService: dataService, itemID: item.unwrappedID)
}

View file

@ -10,15 +10,24 @@ struct SafariWebLink: Identifiable {
@MainActor final class WebReaderViewModel: ObservableObject {
@Published var articleContent: ArticleContent?
@Published var contentFetchFailed = false
@Published var errorMessage: String?
func loadContent(dataService: DataService, itemID: String) async {
contentFetchFailed = false
errorMessage = nil
do {
articleContent = try await dataService.fetchArticleContent(itemID: itemID)
} catch {
contentFetchFailed = true
if let fetchError = error as? ContentFetchError {
switch fetchError {
case .network:
errorMessage = "We were unable to retrieve your content. Please ccheck network connectivity and try again."
default:
errorMessage = "We were unable to parse your content."
}
} else {
errorMessage = "We were unable to retrieve your content."
}
}
}

View file

@ -1,5 +1,7 @@
import Foundation
public typealias ContentFetchError = SaveArticleError
public enum SaveArticleError: Error {
case unauthorized
case network

View file

@ -20,7 +20,7 @@ extension DataService {
func prefetchPage(pendingLink: PendingLink, username: String) async {
let content = try? await articleContent(username: username, itemID: pendingLink.itemID, useCache: false)
if content?.contentStatus == .processing, pendingLink.retryCount < 6 {
if content?.contentStatus == .processing, pendingLink.retryCount < 7 {
let retryDelayInNanoSeconds = UInt64(pendingLink.retryCount * 2 * 1_000_000_000)
do {
@ -45,26 +45,24 @@ extension DataService {
username: String? = nil,
requestCount: Int = 1
) async throws -> ArticleContent {
guard let username = username ?? currentViewer?.username else {
throw BasicError.message(messageText: "username could not be fetched from core data")
guard requestCount < 7 else {
throw ContentFetchError.badData
}
guard let fetchedContent = try? await articleContent(username: username, itemID: itemID, useCache: true) else {
throw BasicError.message(messageText: "networking error")
guard let username = username ?? currentViewer?.username else {
throw ContentFetchError.unauthorized
}
let fetchedContent = try await articleContent(username: username, itemID: itemID, useCache: true)
switch fetchedContent.contentStatus {
case .failed:
throw BasicError.message(messageText: "content processing failed")
throw ContentFetchError.badData
case .processing:
do {
let retryDelayInNanoSeconds = UInt64(requestCount * 2 * 1_000_000_000)
try await Task.sleep(nanoseconds: retryDelayInNanoSeconds)
logger.debug("fetching content for \(itemID). request count: \(requestCount)")
return try await fetchArticleContent(itemID: itemID, username: username, requestCount: requestCount + 1)
} catch {
throw BasicError.message(messageText: "content fetch failed")
}
let retryDelayInNanoSeconds = UInt64(requestCount * 2 * 1_000_000_000)
try await Task.sleep(nanoseconds: retryDelayInNanoSeconds)
logger.debug("fetching content for \(itemID). request count: \(requestCount)")
return try await fetchArticleContent(itemID: itemID, username: username, requestCount: requestCount + 1)
case .succeeded, .unknown:
return fetchedContent
}
@ -120,7 +118,7 @@ extension DataService {
return try await withCheckedThrowingContinuation { continuation in
send(query, to: path, headers: headers) { [weak self] queryResult in
guard let payload = try? queryResult.get() else {
continuation.resume(throwing: BasicError.message(messageText: "network error"))
continuation.resume(throwing: ContentFetchError.network)
return
}
@ -129,6 +127,10 @@ extension DataService {
// Default to suceeded since older links will return a nil status
// (but the content is almost always there)
let status = result.contentStatus ?? .succeeded
if status == .failed {
continuation.resume(throwing: ContentFetchError.badData)
return
}
if status == .succeeded {
self?.persistArticleContent(
@ -146,7 +148,7 @@ extension DataService {
continuation.resume(returning: articleContent)
case .error:
continuation.resume(throwing: BasicError.message(messageText: "LinkedItem fetch error"))
continuation.resume(throwing: ContentFetchError.badData)
}
}
}