mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Improve how we handle loading the initial items on iOS
This commit is contained in:
parent
64d6ff45e1
commit
c74911ea86
4 changed files with 73 additions and 37 deletions
|
|
@ -91,33 +91,45 @@ import Views
|
|||
items.insert(item, at: 0)
|
||||
}
|
||||
|
||||
func loadItems(dataService: DataService, audioController: AudioController, isRefresh: Bool) async {
|
||||
let syncStartTime = Date()
|
||||
let thisSearchIdx = searchIdx
|
||||
searchIdx += 1
|
||||
|
||||
isLoading = true
|
||||
showLoadingBar = true
|
||||
|
||||
func loadCurrentViewer(dataService: DataService) async {
|
||||
// Cache the viewer
|
||||
if dataService.currentViewer == nil {
|
||||
Task { _ = try? await dataService.fetchViewer() }
|
||||
_ = try? await dataService.fetchViewer()
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch labels if none are available locally
|
||||
func loadLabels(dataService: DataService) async {
|
||||
let fetchRequest: NSFetchRequest<Models.LinkedItemLabel> = LinkedItemLabel.fetchRequest()
|
||||
fetchRequest.fetchLimit = 1
|
||||
|
||||
if (try? dataService.viewContext.count(for: fetchRequest)) == 0 {
|
||||
_ = try? await dataService.labels()
|
||||
}
|
||||
}
|
||||
|
||||
// Sync items if necessary
|
||||
func syncItems(dataService: DataService, syncStartTime: Date) async {
|
||||
let lastSyncDate = dateFormatter.date(from: lastItemSyncTime) ?? Date(timeIntervalSinceReferenceDate: 0)
|
||||
let syncResult = try? await dataService.syncLinkedItems(since: lastSyncDate, cursor: nil)
|
||||
if syncResult != nil {
|
||||
lastItemSyncTime = dateFormatter.string(from: syncStartTime)
|
||||
}
|
||||
// If possible start prefetching new pages in the background
|
||||
if let itemIDs = syncResult?.updatedItemIDs,
|
||||
let username = dataService.currentViewer?.username,
|
||||
itemIDs.count > 0 {
|
||||
Task {
|
||||
await dataService.prefetchPages(itemIDs: itemIDs.map { $0.uriRepresentation().absoluteString }, username: username)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadSearchQuery(dataService: DataService, isRefresh: Bool) async {
|
||||
let thisSearchIdx = searchIdx
|
||||
searchIdx += 1
|
||||
|
||||
if thisSearchIdx > 0, thisSearchIdx <= receivedIdx {
|
||||
return
|
||||
}
|
||||
|
||||
let queryResult = try? await dataService.loadLinkedItems(
|
||||
limit: 10,
|
||||
|
|
@ -125,15 +137,6 @@ import Views
|
|||
cursor: isRefresh ? nil : cursor
|
||||
)
|
||||
|
||||
// Search results aren't guaranteed to return in order so this
|
||||
// will discard old results that are returned while a user is typing.
|
||||
// For example if a user types 'Canucks', often the search results
|
||||
// for 'C' are returned after 'Canucks' because it takes the backend
|
||||
// much longer to compute.
|
||||
if thisSearchIdx > 0, thisSearchIdx <= receivedIdx {
|
||||
return
|
||||
}
|
||||
|
||||
if let queryResult = queryResult {
|
||||
let newItems: [LinkedItem] = {
|
||||
var itemObjects = [LinkedItem]()
|
||||
|
|
@ -159,17 +162,33 @@ import Views
|
|||
cursor = queryResult.cursor
|
||||
if let username = dataService.currentViewer?.username {
|
||||
await dataService.prefetchPages(itemIDs: newItems.map(\.unwrappedID), username: username)
|
||||
// Only preload the first item in the list. We are doing this during the beta
|
||||
// because it will kick off the user's future items being automatically transcribed.
|
||||
// This happens because when an article is saved, we check if the user has a recent
|
||||
// listen. If they do, we will automatically transcribe their message.
|
||||
if let first = newItems.filter({ !$0.isPDF }).first?.id {
|
||||
_ = await audioController.preload(itemIDs: [first])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
updateFetchController(dataService: dataService)
|
||||
}
|
||||
}
|
||||
|
||||
func loadItems(dataService: DataService, audioController _: AudioController, isRefresh: Bool) async {
|
||||
let syncStartTime = Date()
|
||||
let start = CFAbsoluteTimeGetCurrent()
|
||||
|
||||
isLoading = true
|
||||
showLoadingBar = true
|
||||
|
||||
_ = await [
|
||||
loadCurrentViewer(dataService: dataService),
|
||||
loadLabels(dataService: dataService),
|
||||
syncItems(dataService: dataService, syncStartTime: syncStartTime)
|
||||
]
|
||||
print("fetched all prelim data", CFAbsoluteTimeGetCurrent() - start)
|
||||
|
||||
if searchTerm.replacingOccurrences(of: " ", with: "").isEmpty {
|
||||
updateFetchController(dataService: dataService)
|
||||
} else {
|
||||
print("PERFORMING SEARCH FOR ITEMS")
|
||||
await loadSearchQuery(dataService: dataService, isRefresh: isRefresh)
|
||||
}
|
||||
print("loaded the linked items", CFAbsoluteTimeGetCurrent() - start)
|
||||
|
||||
isLoading = false
|
||||
showLoadingBar = false
|
||||
|
|
|
|||
|
|
@ -12,6 +12,16 @@ public struct LinkedItemQueryResult {
|
|||
}
|
||||
}
|
||||
|
||||
public struct LinkedItemSyncResult {
|
||||
public let updatedItemIDs: [String]
|
||||
public let cursor: String?
|
||||
|
||||
public init(updatedItemIDs: [String], cursor: String?) {
|
||||
self.updatedItemIDs = updatedItemIDs
|
||||
self.cursor = cursor
|
||||
}
|
||||
}
|
||||
|
||||
public struct LinkedItemAudioProperties {
|
||||
public let itemID: String
|
||||
public let objectID: NSManagedObjectID
|
||||
|
|
|
|||
|
|
@ -5,9 +5,14 @@ import Utils
|
|||
|
||||
public extension DataService {
|
||||
func prefetchPages(itemIDs: [String], username: String) async {
|
||||
// TODO: make this concurrent
|
||||
for itemID in itemIDs {
|
||||
await prefetchPage(pendingLink: PendingLink(itemID: itemID, retryCount: 1), username: username)
|
||||
await withTaskGroup(of: Void.self) { group in
|
||||
print(" PREFETCHING PAGES: ", itemIDs)
|
||||
for itemID in itemIDs {
|
||||
group.addTask {
|
||||
await self.prefetchPage(pendingLink: PendingLink(itemID: itemID, retryCount: 1), username: username)
|
||||
}
|
||||
}
|
||||
await group.waitForAll()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ public extension DataService {
|
|||
func syncLinkedItems(
|
||||
since date: Date,
|
||||
cursor: String?,
|
||||
previousQueryResult: LinkedItemQueryResult? = nil
|
||||
) async throws -> LinkedItemQueryResult {
|
||||
previousQueryResult: LinkedItemSyncResult? = nil
|
||||
) async throws -> LinkedItemSyncResult? {
|
||||
if previousQueryResult == nil {
|
||||
// Send offline changes to server before fetching items
|
||||
// only on the first call of this function
|
||||
|
|
@ -26,16 +26,18 @@ public extension DataService {
|
|||
|
||||
LinkedItem.deleteItems(ids: fetchResult.deletedItemIDs, context: backgroundContext)
|
||||
|
||||
guard let itemIDs = fetchResult.items.persist(context: backgroundContext) else {
|
||||
if fetchResult.items.persist(context: backgroundContext) == nil {
|
||||
throw BasicError.message(messageText: "CoreData error")
|
||||
}
|
||||
|
||||
let result = LinkedItemQueryResult(
|
||||
itemIDs: itemIDs + (previousQueryResult?.itemIDs ?? []),
|
||||
|
||||
let prev = previousQueryResult?.updatedItemIDs ?? []
|
||||
let result = LinkedItemSyncResult(
|
||||
updatedItemIDs: prev + fetchResult.items.map { $0.id },
|
||||
cursor: fetchResult.cursor
|
||||
)
|
||||
|
||||
if fetchResult.hasMoreItems, (previousQueryResult?.itemIDs.count ?? 0) < 200 {
|
||||
if fetchResult.hasMoreItems, (previousQueryResult?.updatedItemIDs.count ?? 0) < 200 {
|
||||
return try await syncLinkedItems(
|
||||
since: date,
|
||||
cursor: fetchResult.cursor,
|
||||
|
|
@ -58,7 +60,7 @@ public extension DataService {
|
|||
cursor: String?
|
||||
) async throws -> LinkedItemQueryResult {
|
||||
// Send offline changes to server before fetching items
|
||||
try? await syncOfflineItemsWithServerIfNeeded()
|
||||
// try? await syncOfflineItemsWithServerIfNeeded()
|
||||
|
||||
let fetchResult = try await fetchLinkedItems(limit: limit, searchQuery: searchQuery, cursor: cursor)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue