mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
move article content fetching into fewer functions
This commit is contained in:
parent
fb4312e52a
commit
0e8d5f23da
6 changed files with 67 additions and 106 deletions
|
|
@ -1,4 +1,3 @@
|
|||
import Combine
|
||||
import CoreData
|
||||
import Models
|
||||
import Services
|
||||
|
|
@ -26,8 +25,6 @@ import Views
|
|||
var searchIdx = 0
|
||||
var receivedIdx = 0
|
||||
|
||||
var subscriptions = Set<AnyCancellable>()
|
||||
|
||||
init() {}
|
||||
|
||||
func itemAppeared(item: LinkedItem, dataService: DataService) async {
|
||||
|
|
@ -83,7 +80,7 @@ import Views
|
|||
isLoading = false
|
||||
receivedIdx = thisSearchIdx
|
||||
cursor = queryResult.cursor
|
||||
dataService.prefetchPages(itemSlugs: newItems.map(\.unwrappedSlug))
|
||||
await dataService.prefetchPages(itemSlugs: newItems.map(\.unwrappedSlug))
|
||||
} else if searchTermIsEmpty {
|
||||
await dataService.viewContext.perform {
|
||||
let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
|
||||
|
|
|
|||
|
|
@ -190,10 +190,8 @@ import WebKit
|
|||
} else {
|
||||
Color.clear
|
||||
.contentShape(Rectangle())
|
||||
.onAppear {
|
||||
if !viewModel.isLoading {
|
||||
viewModel.loadContent(dataService: dataService, slug: item.unwrappedSlug)
|
||||
}
|
||||
.task {
|
||||
await viewModel.loadContent(dataService: dataService, slug: item.unwrappedSlug)
|
||||
}
|
||||
}
|
||||
if showFontSizePopover {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import Combine
|
||||
import Models
|
||||
import Services
|
||||
import SwiftUI
|
||||
|
|
@ -9,33 +8,15 @@ struct SafariWebLink: Identifiable {
|
|||
let url: URL
|
||||
}
|
||||
|
||||
final class WebReaderViewModel: ObservableObject {
|
||||
@Published var isLoading = false
|
||||
@MainActor final class WebReaderViewModel: ObservableObject {
|
||||
@Published var articleContent: ArticleContent?
|
||||
|
||||
var slug: String?
|
||||
var subscriptions = Set<AnyCancellable>()
|
||||
|
||||
func loadContent(dataService: DataService, slug: String) {
|
||||
func loadContent(dataService: DataService, slug: String) async {
|
||||
self.slug = slug
|
||||
isLoading = true
|
||||
|
||||
guard let username = dataService.currentViewer?.username else { return }
|
||||
|
||||
if let content = dataService.pageFromCache(slug: slug) {
|
||||
articleContent = content
|
||||
} else {
|
||||
dataService.articleContentPublisher(username: username, slug: slug).sink(
|
||||
receiveCompletion: { [weak self] completion in
|
||||
guard case .failure = completion else { return }
|
||||
self?.isLoading = false
|
||||
},
|
||||
receiveValue: { [weak self] articleContent in
|
||||
self?.articleContent = articleContent
|
||||
}
|
||||
)
|
||||
.store(in: &subscriptions)
|
||||
}
|
||||
articleContent = try? await dataService.articleContent(username: username, slug: slug, useCache: true)
|
||||
}
|
||||
|
||||
func createHighlight(
|
||||
|
|
@ -141,16 +122,12 @@ final class WebReaderViewModel: ObservableObject {
|
|||
|
||||
switch actionID {
|
||||
case "deleteHighlight":
|
||||
dataService.invalidateCachedPage(slug: slug)
|
||||
deleteHighlight(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService)
|
||||
case "createHighlight":
|
||||
dataService.invalidateCachedPage(slug: slug)
|
||||
createHighlight(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService)
|
||||
case "mergeHighlight":
|
||||
dataService.invalidateCachedPage(slug: slug)
|
||||
mergeHighlight(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService)
|
||||
case "updateHighlight":
|
||||
dataService.invalidateCachedPage(slug: slug)
|
||||
updateHighlight(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService)
|
||||
case "articleReadingProgress":
|
||||
updateReadingProgress(messageBody: messageBody, replyHandler: replyHandler, dataService: dataService)
|
||||
|
|
|
|||
|
|
@ -50,39 +50,3 @@ public final class DataService: ObservableObject {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension DataService {
|
||||
func prefetchPages(itemSlugs: [String]) {
|
||||
guard let username = currentViewer?.username else { return }
|
||||
|
||||
for slug in itemSlugs {
|
||||
articleContentPublisher(username: username, slug: slug).sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { _ in }
|
||||
)
|
||||
.store(in: &subscriptions)
|
||||
}
|
||||
}
|
||||
|
||||
func pageFromCache(slug: String) -> ArticleContent? {
|
||||
let linkedItemFetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
|
||||
linkedItemFetchRequest.predicate = NSPredicate(
|
||||
format: "slug == %@", slug
|
||||
)
|
||||
|
||||
guard let linkedItem = try? persistentContainer.viewContext.fetch(linkedItemFetchRequest).first else { return nil }
|
||||
guard let htmlContent = linkedItem.htmlContent else { return nil }
|
||||
|
||||
let highlights = linkedItem
|
||||
.highlights
|
||||
.asArray(of: Highlight.self)
|
||||
.filter { $0.serverSyncStatus != ServerSyncStatus.needsDeletion.rawValue }
|
||||
|
||||
return ArticleContent(
|
||||
htmlContent: htmlContent,
|
||||
highlightsJSONString: highlights.map { InternalHighlight.make(from: $0) }.asJSONString
|
||||
)
|
||||
}
|
||||
|
||||
func invalidateCachedPage(slug _: String?) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,28 @@
|
|||
import Combine
|
||||
import CoreData
|
||||
import Foundation
|
||||
import Models
|
||||
import SwiftGraphQL
|
||||
|
||||
public extension DataService {
|
||||
struct ArticleProps {
|
||||
let htmlContent: String
|
||||
let highlights: [InternalHighlight]
|
||||
extension DataService {
|
||||
public func prefetchPages(itemSlugs: [String]) async {
|
||||
guard let username = currentViewer?.username else { return }
|
||||
|
||||
for slug in itemSlugs {
|
||||
// TODO: maybe check for cached content before downloading again? check timestamp?
|
||||
_ = try? await articleContent(username: username, slug: slug, useCache: false)
|
||||
}
|
||||
}
|
||||
|
||||
func articleContentPublisher(username: String, slug: String) -> AnyPublisher<ArticleContent, ServerError> {
|
||||
public func articleContent(username: String, slug: String, useCache: Bool) async throws -> ArticleContent {
|
||||
struct ArticleProps {
|
||||
let htmlContent: String
|
||||
let highlights: [InternalHighlight]
|
||||
}
|
||||
|
||||
if useCache, let cachedContent = cachedArticleContent(slug: slug) {
|
||||
return cachedContent
|
||||
}
|
||||
|
||||
enum QueryResult {
|
||||
case success(result: ArticleProps)
|
||||
case error(error: String)
|
||||
|
|
@ -41,40 +53,34 @@ public extension DataService {
|
|||
let path = appEnvironment.graphqlPath
|
||||
let headers = networker.defaultHeaders
|
||||
|
||||
return Deferred {
|
||||
Future { promise in
|
||||
send(query, to: path, headers: headers) { [weak self] result in
|
||||
switch result {
|
||||
case let .success(payload):
|
||||
switch payload.data {
|
||||
case let .success(result: result):
|
||||
// store result in core data
|
||||
self?.persistArticleContent(
|
||||
htmlContent: result.htmlContent,
|
||||
slug: slug,
|
||||
highlights: result.highlights
|
||||
)
|
||||
promise(.success(
|
||||
ArticleContent(
|
||||
htmlContent: result.htmlContent,
|
||||
highlightsJSONString: result.highlights.asJSONString
|
||||
))
|
||||
)
|
||||
case .error:
|
||||
promise(.failure(.unknown))
|
||||
}
|
||||
case .failure:
|
||||
promise(.failure(.unknown))
|
||||
}
|
||||
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"))
|
||||
return
|
||||
}
|
||||
|
||||
switch payload.data {
|
||||
case let .success(result: result):
|
||||
self?.persistArticleContent(
|
||||
htmlContent: result.htmlContent,
|
||||
slug: slug,
|
||||
highlights: result.highlights
|
||||
)
|
||||
|
||||
let articleContent = ArticleContent(
|
||||
htmlContent: result.htmlContent,
|
||||
highlightsJSONString: result.highlights.asJSONString
|
||||
)
|
||||
|
||||
continuation.resume(returning: articleContent)
|
||||
case .error:
|
||||
continuation.resume(throwing: BasicError.message(messageText: "LinkedItem fetch error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
.receive(on: DispatchQueue.main)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
extension DataService {
|
||||
func persistArticleContent(htmlContent: String, slug: String, highlights: [InternalHighlight]) {
|
||||
backgroundContext.perform {
|
||||
let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
|
||||
|
|
@ -101,4 +107,24 @@ extension DataService {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cachedArticleContent(slug: String) -> ArticleContent? {
|
||||
let linkedItemFetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
|
||||
linkedItemFetchRequest.predicate = NSPredicate(
|
||||
format: "slug == %@", slug
|
||||
)
|
||||
|
||||
guard let linkedItem = try? persistentContainer.viewContext.fetch(linkedItemFetchRequest).first else { return nil }
|
||||
guard let htmlContent = linkedItem.htmlContent else { return nil }
|
||||
|
||||
let highlights = linkedItem
|
||||
.highlights
|
||||
.asArray(of: Highlight.self)
|
||||
.filter { $0.serverSyncStatus != ServerSyncStatus.needsDeletion.rawValue }
|
||||
|
||||
return ArticleContent(
|
||||
htmlContent: htmlContent,
|
||||
highlightsJSONString: highlights.map { InternalHighlight.make(from: $0) }.asJSONString
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import Combine
|
||||
import CoreData
|
||||
import Foundation
|
||||
import Models
|
||||
|
|
|
|||
Loading…
Reference in a new issue