mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
PDF notebooks, more metadata for PDF items in the list view
This commit is contained in:
parent
ccff1f421f
commit
43905a47ba
6 changed files with 1499 additions and 32 deletions
|
|
@ -40,6 +40,8 @@ import Utils
|
|||
@State var readerView: Bool = false
|
||||
@State private var shareLink: ShareLink?
|
||||
@State private var errorMessage: String?
|
||||
@State private var showNotebookView = false
|
||||
@State private var hasPerformedHighlightMutations = false
|
||||
|
||||
init(viewModel: PDFViewerViewModel) {
|
||||
self.viewModel = viewModel
|
||||
|
|
@ -117,6 +119,12 @@ import Utils
|
|||
style: .plain,
|
||||
target: controller.searchButtonItem.target,
|
||||
action: controller.searchButtonItem.action
|
||||
),
|
||||
UIBarButtonItem(
|
||||
image: UIImage(named: "notebook", in: Bundle(url: ViewsPackage.bundleURL), with: nil),
|
||||
style: .plain,
|
||||
target: coordinator,
|
||||
action: #selector(PDFViewCoordinator.toggleNotebookView)
|
||||
)
|
||||
]
|
||||
|
||||
|
|
@ -183,6 +191,15 @@ import Utils
|
|||
.sheet(item: $shareLink) {
|
||||
ShareSheet(activityItems: [$0.url])
|
||||
}
|
||||
.fullScreenCover(isPresented: $showNotebookView, onDismiss: onNotebookViewDismissal) {
|
||||
NotebookView(
|
||||
itemObjectID: viewModel.pdfItem.objectID,
|
||||
hasHighlightMutations: $hasPerformedHighlightMutations,
|
||||
onDeleteHighlight: { highlightId in
|
||||
coordinator.removeHighlightFromPDF(highlightId: highlightId)
|
||||
}
|
||||
)
|
||||
}
|
||||
} else if let errorMessage = errorMessage {
|
||||
Text(errorMessage)
|
||||
} else {
|
||||
|
|
@ -202,6 +219,12 @@ import Utils
|
|||
}
|
||||
}
|
||||
|
||||
func onNotebookViewDismissal() {
|
||||
guard hasPerformedHighlightMutations else { return }
|
||||
|
||||
hasPerformedHighlightMutations.toggle()
|
||||
}
|
||||
|
||||
class PDFViewCoordinator: NSObject, PDFDocumentViewControllerDelegate, PDFViewControllerDelegate {
|
||||
let document: Document
|
||||
let viewModel: PDFViewerViewModel
|
||||
|
|
@ -239,6 +262,22 @@ import Utils
|
|||
}.store(in: &subscriptions)
|
||||
}
|
||||
|
||||
func removeHighlightFromPDF(highlightId: String) {
|
||||
for pageIndex in 0 ..< document.pageCount {
|
||||
let pageHighlights = document.annotations(at: pageIndex, type: HighlightAnnotation.self)
|
||||
|
||||
for annotation in pageHighlights {
|
||||
if let customHighlight = annotation.customData?["omnivoreHighlight"] as? [String: String] {
|
||||
if customHighlight["id"]?.lowercased() == highlightId {
|
||||
if !document.remove(annotations: [annotation]) {
|
||||
Snackbar.show(message: "Error removing highlight")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func highlightsOverlap(left: HighlightAnnotation, right: HighlightAnnotation) -> Bool {
|
||||
for rect in left.rects ?? [] {
|
||||
for hrrect in right.rects ?? [] {
|
||||
|
|
@ -382,6 +421,12 @@ import Utils
|
|||
}
|
||||
}
|
||||
|
||||
@objc public func toggleNotebookView() {
|
||||
if let viewer = self.viewer {
|
||||
viewer.showNotebookView = !viewer.showNotebookView
|
||||
}
|
||||
}
|
||||
|
||||
func shortHighlightIds(_ annotations: [HighlightAnnotation]) -> [String] {
|
||||
annotations.compactMap { ($0.customData?["omnivoreHighlight"] as? [String: String])?["shortId"] }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
import SwiftUI
|
||||
import Views
|
||||
|
||||
typealias DeleteHighlightAction = (String) -> Void
|
||||
|
||||
struct NotebookView: View {
|
||||
@EnvironmentObject var dataService: DataService
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
|
|
@ -21,6 +23,7 @@
|
|||
@State var setLabelsHighlight: Highlight?
|
||||
@State var showShareView: Bool = false
|
||||
@State var showConfirmNoteDelete = false
|
||||
@State var onDeleteHighlight: DeleteHighlightAction?
|
||||
|
||||
var emptyView: some View {
|
||||
Text(LocalText.highlightCardNoHighlightsOnPage)
|
||||
|
|
@ -134,6 +137,9 @@
|
|||
highlightID: highlightParams.highlightID,
|
||||
dataService: dataService
|
||||
)
|
||||
if let onDeleteHighlight = onDeleteHighlight {
|
||||
onDeleteHighlight(highlightParams.highlightID)
|
||||
}
|
||||
},
|
||||
onSetLabels: { highlightID in
|
||||
setLabelsHighlight = Highlight.lookup(byID: highlightID, inContext: dataService.viewContext)
|
||||
|
|
|
|||
|
|
@ -381,6 +381,7 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||
.padding(.top, 0)
|
||||
} else {
|
||||
Text((FeaturedItemFilter(rawValue: viewModel.featureFilter) ?? .continueReading).emptyMessage)
|
||||
.padding(.horizontal, UIDevice.isIPad ? 20 : 10)
|
||||
.font(Font.system(size: 14, weight: .regular))
|
||||
.foregroundColor(Color(hex: "#898989"))
|
||||
.frame(maxWidth: geo.size.width)
|
||||
|
|
|
|||
1402
apple/OmnivoreKit/Sources/Views/FeedItem/File.swift
Normal file
1402
apple/OmnivoreKit/Sources/Views/FeedItem/File.swift
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -33,44 +33,49 @@ public struct LibraryFeatureCard: View {
|
|||
|
||||
var imageBox: some View {
|
||||
ZStack(alignment: .bottomLeading) {
|
||||
Group {
|
||||
if let imageURL = item.imageURL {
|
||||
AsyncImage(url: imageURL) { phase in
|
||||
switch phase {
|
||||
case .empty:
|
||||
Color.systemBackground
|
||||
.frame(width: 146, height: 90)
|
||||
case let .success(image):
|
||||
image.resizable()
|
||||
.frame(width: 146, height: 90)
|
||||
.aspectRatio(contentMode: .fill)
|
||||
case .failure:
|
||||
Image(systemName: "photo")
|
||||
.frame(width: 146, height: 90)
|
||||
.foregroundColor(Color(hex: "#6A6968"))
|
||||
.background(Color(hex: "#EBEBEB"))
|
||||
if let imageURL = item.imageURL {
|
||||
AsyncImage(url: imageURL) { phase in
|
||||
switch phase {
|
||||
case .empty:
|
||||
EmptyView()
|
||||
case let .success(image):
|
||||
image.resizable()
|
||||
.frame(width: 146, height: 90)
|
||||
.aspectRatio(contentMode: .fill)
|
||||
case .failure:
|
||||
fallbackImage
|
||||
|
||||
@unknown default:
|
||||
// Since the AsyncImagePhase enum isn't frozen,
|
||||
// we need to add this currently unused fallback
|
||||
// to handle any new cases that might be added
|
||||
// in the future:
|
||||
EmptyView()
|
||||
}
|
||||
@unknown default:
|
||||
// Since the AsyncImagePhase enum isn't frozen,
|
||||
// we need to add this currently unused fallback
|
||||
// to handle any new cases that might be added
|
||||
// in the future:
|
||||
EmptyView()
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "photo")
|
||||
.frame(width: 146, height: 90)
|
||||
.foregroundColor(Color(hex: "#6A6968"))
|
||||
.background(Color(hex: "#EBEBEB"))
|
||||
}
|
||||
Color(hex: "#D9D9D9")?.opacity(0.65).frame(width: 146, height: 5)
|
||||
Color(hex: "#FFD234").frame(width: 146 * (item.readingProgress / 100), height: 5)
|
||||
} else {
|
||||
fallbackImage
|
||||
}
|
||||
Color(hex: "#D9D9D9")?.opacity(0.65).frame(width: 146, height: 5)
|
||||
Color(hex: "#FFD234").frame(width: 146 * (item.readingProgress / 100), height: 5)
|
||||
}
|
||||
.cornerRadius(5)
|
||||
}
|
||||
|
||||
var fallbackImage: some View {
|
||||
HStack {
|
||||
Text(item.unwrappedTitle.prefix(1))
|
||||
.font(Font.system(size: 128, weight: .bold))
|
||||
.offset(CGSize(width: -48, height: 32))
|
||||
.frame(alignment: .bottomLeading)
|
||||
.foregroundColor(Gradient.randomColor(str: item.unwrappedTitle, offset: 1))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Gradient.randomColor(str: item.unwrappedTitle, offset: 0))
|
||||
.background(LinearGradient(gradient: Gradient(fromStr: item.unwrappedTitle)!, startPoint: .top, endPoint: .bottom))
|
||||
.frame(width: 146, height: 90)
|
||||
}
|
||||
|
||||
var title: some View {
|
||||
Text(item.unwrappedTitle.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
.multilineTextAlignment(.leading)
|
||||
|
|
|
|||
|
|
@ -82,13 +82,21 @@ public struct LibraryItemCard: View {
|
|||
if item.wordsCount > 0 {
|
||||
return "\(String(format: "%d", Int(item.readingProgress)))%"
|
||||
}
|
||||
if item.isPDF {
|
||||
// base estimated reading time on page count
|
||||
return "\(String(format: "%d", Int(item.readingProgress)))%"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var hasMultipleInfoItems: Bool {
|
||||
item.wordsCount > 0 || item.highlights?.first { ($0 as? Highlight)?.annotation != nil } != nil
|
||||
}
|
||||
|
||||
var highlightsText: String {
|
||||
if let highlights = item.highlights, highlights.count > 0 {
|
||||
let fmted = LocalText.pluralizedText(key: "number_of_highlights", count: highlights.count)
|
||||
if item.wordsCount > 0 {
|
||||
if item.wordsCount > 0 || item.isPDF {
|
||||
return " • \(fmted)"
|
||||
}
|
||||
return fmted
|
||||
|
|
@ -106,7 +114,7 @@ public struct LibraryItemCard: View {
|
|||
|
||||
if let notes = notes, notes.count > 0 {
|
||||
let fmted = LocalText.pluralizedText(key: "number_of_notes", count: notes.count)
|
||||
if item.wordsCount > 0 {
|
||||
if hasMultipleInfoItems {
|
||||
return " • \(fmted)"
|
||||
}
|
||||
return fmted
|
||||
|
|
|
|||
Loading…
Reference in a new issue