Improve app intents, always set readAt when an article is opened
This commit is contained in:
@ -1,49 +0,0 @@
|
||||
#if os(iOS)
|
||||
import AppIntents
|
||||
import Services
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
public struct OmnivoreAppShorcuts: AppShortcutsProvider {
|
||||
@AppShortcutsBuilder public static var appShortcuts: [AppShortcut] {
|
||||
AppShortcut(intent: SaveToOmnivoreIntent(), phrases: ["Save URL to \(.applicationName)"])
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// @available(iOS 16.0, *)
|
||||
// struct ExportAllTransactionsIntent: AppIntent {
|
||||
// static var title: LocalizedStringResource = "Export all transactions"
|
||||
//
|
||||
// static var description =
|
||||
// IntentDescription("Exports your transaction history as CSV data.")
|
||||
// }
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct SaveToOmnivoreIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "Save to Omnivore"
|
||||
static var description: LocalizedStringResource = "Save a URL to your Omnivore library"
|
||||
|
||||
static var parameterSummary: some ParameterSummary {
|
||||
Summary("Save \(\.$link) to your Omnivore library.")
|
||||
}
|
||||
|
||||
@Parameter(title: "link")
|
||||
var link: URL
|
||||
|
||||
@MainActor
|
||||
func perform() async throws -> some IntentResult & ReturnsValue {
|
||||
do {
|
||||
let services = Services()
|
||||
let requestId = UUID().uuidString.lowercased()
|
||||
_ = try await services.dataService.saveURL(id: requestId, url: link.absoluteString)
|
||||
|
||||
return .result(dialog: "Link saved to Omnivore")
|
||||
} catch {
|
||||
print("error saving URL: ", error)
|
||||
}
|
||||
return .result(dialog: "Error saving link")
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -255,6 +255,12 @@ import Utils
|
||||
ApplyLabelsView(mode: .item(viewModel.pdfItem.item), onSave: { _ in
|
||||
showLabelsModal = false
|
||||
})
|
||||
}.task {
|
||||
viewModel.updateItemReadProgress(
|
||||
dataService: dataService,
|
||||
percent: viewModel.pdfItem.item.readingProgress,
|
||||
anchorIndex: Int(viewModel.pdfItem.item.readingProgressAnchor)
|
||||
)
|
||||
}
|
||||
} else if let errorMessage = errorMessage {
|
||||
Text(errorMessage)
|
||||
|
||||
@ -210,7 +210,6 @@ struct WebReaderContainerView: View {
|
||||
},
|
||||
label: { Label("Reset Read Location", systemImage: "arrow.counterclockwise.circle") }
|
||||
)
|
||||
audioMenuItem()
|
||||
|
||||
if viewModel.hasOriginalUrl(item) {
|
||||
Button(
|
||||
@ -399,9 +398,12 @@ struct WebReaderContainerView: View {
|
||||
.statusBar(hidden: prefersHideStatusBarInReader)
|
||||
#endif
|
||||
.onAppear {
|
||||
if item.isUnread {
|
||||
dataService.updateLinkReadingProgress(itemID: item.unwrappedID, readingProgress: 0.1, anchorIndex: 0, force: false)
|
||||
}
|
||||
dataService.updateLinkReadingProgress(
|
||||
itemID: item.unwrappedID,
|
||||
readingProgress: max(item.readingProgress, 0.1),
|
||||
anchorIndex: Int(item.readingProgressAnchor),
|
||||
force: false
|
||||
)
|
||||
Task {
|
||||
await audioController.preload(itemIDs: [item.unwrappedID])
|
||||
}
|
||||
|
||||
@ -286,7 +286,8 @@ public extension LibraryItem {
|
||||
newAuthor: String? = nil,
|
||||
listenPositionIndex: Int? = nil,
|
||||
listenPositionOffset: Double? = nil,
|
||||
listenPositionTime: Double? = nil
|
||||
listenPositionTime: Double? = nil,
|
||||
readAt: Date? = nil
|
||||
) {
|
||||
context.perform {
|
||||
if let newReadingProgress = newReadingProgress {
|
||||
@ -325,6 +326,10 @@ public extension LibraryItem {
|
||||
self.listenPositionTime = listenPositionTime
|
||||
}
|
||||
|
||||
if let readAt = readAt {
|
||||
self.readAt = readAt
|
||||
}
|
||||
|
||||
guard context.hasChanges else { return }
|
||||
self.updatedAt = Date()
|
||||
|
||||
|
||||
@ -9,17 +9,12 @@ extension DataService {
|
||||
guard let self = self else { return }
|
||||
guard let linkedItem = LibraryItem.lookup(byID: itemID, inContext: self.backgroundContext) else { return }
|
||||
|
||||
if let force = force, !force {
|
||||
if readingProgress != 0, readingProgress < linkedItem.readingProgress {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
print("updating reading progress: ", readingProgress, anchorIndex)
|
||||
linkedItem.update(
|
||||
inContext: self.backgroundContext,
|
||||
newReadingProgress: readingProgress,
|
||||
newAnchorIndex: anchorIndex
|
||||
newAnchorIndex: anchorIndex,
|
||||
readAt: Date()
|
||||
)
|
||||
|
||||
// Send update to server
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#if os(iOS)
|
||||
import App
|
||||
import AppIntents
|
||||
import CoreData
|
||||
import Firebase
|
||||
import FirebaseMessaging
|
||||
import Foundation
|
||||
@ -9,6 +10,72 @@
|
||||
import UIKit
|
||||
import Utils
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
func filterQuery(predicte: NSPredicate, sort: NSSortDescriptor, limit: Int = 10) async throws -> [LibraryItemEntity] {
|
||||
let context = await Services().dataService.viewContext
|
||||
let fetchRequest: NSFetchRequest<Models.LibraryItem> = LibraryItem.fetchRequest()
|
||||
fetchRequest.fetchLimit = limit
|
||||
fetchRequest.predicate = predicte
|
||||
fetchRequest.sortDescriptors = [sort]
|
||||
|
||||
return try context.performAndWait {
|
||||
do {
|
||||
return try context.fetch(fetchRequest).map { LibraryItemEntity(item: $0) }
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct LibraryItemEntity: AppEntity {
|
||||
static var defaultQuery = LibraryItemQuery()
|
||||
|
||||
let id: UUID
|
||||
|
||||
@Property(title: "Title")
|
||||
var title: String
|
||||
@Property(title: "Orignal URL")
|
||||
var originalURL: String?
|
||||
@Property(title: "Omnivore web URL")
|
||||
var omnivoreWebURL: String
|
||||
@Property(title: "Omnivore deeplink URL")
|
||||
var omnivoreShortcutURL: String
|
||||
|
||||
init(item: Models.LibraryItem) {
|
||||
self.id = UUID(uuidString: item.unwrappedID)!
|
||||
self.title = item.unwrappedTitle
|
||||
self.originalURL = item.pageURLString
|
||||
self.omnivoreWebURL = "https://omnivore.app/me/\(item.slug!)"
|
||||
self.omnivoreShortcutURL = "omnivore://read/\(item.unwrappedID)"
|
||||
}
|
||||
|
||||
static var typeDisplayRepresentation = TypeDisplayRepresentation(
|
||||
stringLiteral: "Library Item"
|
||||
)
|
||||
|
||||
var displayRepresentation: DisplayRepresentation {
|
||||
DisplayRepresentation(title: "\(title)")
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct LibraryItemQuery: EntityQuery {
|
||||
func entities(for itemIds: [UUID]) async throws -> [LibraryItemEntity] {
|
||||
let predicate = NSPredicate(format: "id IN %@", itemIds)
|
||||
let sort = FeaturedItemFilter.continueReading.sortDescriptor // sort by read recency
|
||||
return try await filterQuery(predicte: predicate, sort: sort)
|
||||
}
|
||||
|
||||
func suggestedEntities() async throws -> [LibraryItemEntity] {
|
||||
try await filterQuery(
|
||||
predicte: FeaturedItemFilter.continueReading.predicate,
|
||||
sort: FeaturedItemFilter.continueReading.sortDescriptor,
|
||||
limit: 10
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
public struct OmnivoreAppShorcuts: AppShortcutsProvider {
|
||||
@AppShortcutsBuilder public static var appShortcuts: [AppShortcut] {
|
||||
@ -16,15 +83,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// @available(iOS 16.0, *)
|
||||
// struct ExportAllTransactionsIntent: AppIntent {
|
||||
// static var title: LocalizedStringResource = "Export all transactions"
|
||||
//
|
||||
// static var description =
|
||||
// IntentDescription("Exports your transaction history as CSV data.")
|
||||
// }
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct SaveToOmnivoreIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "Save to Omnivore"
|
||||
@ -71,4 +129,77 @@
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.4, *)
|
||||
struct GetMostRecentLibraryItem: AppIntent {
|
||||
static let title: LocalizedStringResource = "Get most recently read library item"
|
||||
|
||||
func perform() async throws -> some IntentResult & ReturnsValue<LibraryItemEntity?> {
|
||||
let result = try await filterQuery(
|
||||
predicte: LinkedItemFilter.all.predicate,
|
||||
sort: FeaturedItemFilter.continueReading.sortDescriptor,
|
||||
limit: 10
|
||||
)
|
||||
|
||||
if let result = result.first {
|
||||
return .result(value: result)
|
||||
}
|
||||
return .result(value: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.4, *)
|
||||
struct GetContinueReadingLibraryItems: AppIntent {
|
||||
static let title: LocalizedStringResource = "Get your continue reading library items"
|
||||
|
||||
func perform() async throws -> some IntentResult & ReturnsValue<[LibraryItemEntity]> {
|
||||
let result = try await filterQuery(
|
||||
predicte: FeaturedItemFilter.continueReading.predicate,
|
||||
sort: FeaturedItemFilter.continueReading.sortDescriptor,
|
||||
limit: 10
|
||||
)
|
||||
|
||||
return .result(value: result)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.4, *)
|
||||
struct GetFollowingLibraryItems: AppIntent {
|
||||
static let title: LocalizedStringResource = "Get your following library items"
|
||||
|
||||
func perform() async throws -> some IntentResult & ReturnsValue<[LibraryItemEntity]> {
|
||||
let savedAtSort = NSSortDescriptor(key: #keyPath(Models.LibraryItem.savedAt), ascending: false)
|
||||
let folderPredicate = NSPredicate(
|
||||
format: "%K == %@", #keyPath(Models.LibraryItem.folder), "following"
|
||||
)
|
||||
|
||||
let result = try await filterQuery(
|
||||
predicte: folderPredicate,
|
||||
sort: savedAtSort,
|
||||
limit: 10
|
||||
)
|
||||
|
||||
return .result(value: result)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.4, *)
|
||||
struct GetSavedLibraryItems: AppIntent {
|
||||
static let title: LocalizedStringResource = "Get your saved library items"
|
||||
|
||||
func perform() async throws -> some IntentResult & ReturnsValue<[LibraryItemEntity]> {
|
||||
let savedAtSort = NSSortDescriptor(key: #keyPath(Models.LibraryItem.savedAt), ascending: false)
|
||||
let folderPredicate = NSPredicate(
|
||||
format: "%K == %@", #keyPath(Models.LibraryItem.folder), "inbox"
|
||||
)
|
||||
|
||||
let result = try await filterQuery(
|
||||
predicte: folderPredicate,
|
||||
sort: savedAtSort,
|
||||
limit: 10
|
||||
)
|
||||
|
||||
return .result(value: result)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user