mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
remove deprecated data models
This commit is contained in:
parent
59533c9a2d
commit
a63bf7928e
27 changed files with 269 additions and 399 deletions
|
|
@ -8,20 +8,20 @@ public final class PDFViewerViewModel: ObservableObject {
|
|||
@Published public var errorMessage: String?
|
||||
@Published public var readerView: Bool = false
|
||||
|
||||
public var feedItem: FeedItemDep
|
||||
public var linkedItem: LinkedItem
|
||||
|
||||
var subscriptions = Set<AnyCancellable>()
|
||||
let services: Services
|
||||
|
||||
public init(services: Services, feedItem: FeedItemDep) {
|
||||
public init(services: Services, linkedItem: LinkedItem) {
|
||||
self.services = services
|
||||
self.feedItem = feedItem
|
||||
self.linkedItem = linkedItem
|
||||
}
|
||||
|
||||
public func loadHighlights(completion onComplete: @escaping ([String]) -> Void) {
|
||||
let fetchRequest: NSFetchRequest<Models.Highlight> = Highlight.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(
|
||||
format: "linkedItemId == %@", feedItem.id
|
||||
format: "linkedItemId == %@", linkedItem.unwrappedID
|
||||
)
|
||||
|
||||
let highlights = (try? services.dataService.viewContext.fetch(fetchRequest)) ?? []
|
||||
|
|
@ -35,7 +35,7 @@ public final class PDFViewerViewModel: ObservableObject {
|
|||
highlightID: highlightID,
|
||||
quote: quote,
|
||||
patch: patch,
|
||||
articleId: feedItem.id
|
||||
articleId: linkedItem.unwrappedID
|
||||
)
|
||||
.sink { [weak self] completion in
|
||||
guard case let .failure(error) = completion else { return }
|
||||
|
|
@ -57,7 +57,7 @@ public final class PDFViewerViewModel: ObservableObject {
|
|||
highlightID: highlightID,
|
||||
quote: quote,
|
||||
patch: patch,
|
||||
articleId: feedItem.id,
|
||||
articleId: linkedItem.unwrappedID,
|
||||
overlapHighlightIdList: overlapHighlightIdList
|
||||
)
|
||||
.sink { [weak self] completion in
|
||||
|
|
@ -83,7 +83,7 @@ public final class PDFViewerViewModel: ObservableObject {
|
|||
public func updateItemReadProgress(percent: Double, anchorIndex: Int) {
|
||||
services.dataService
|
||||
.updateArticleReadingProgressPublisher(
|
||||
itemID: feedItem.id,
|
||||
itemID: linkedItem.unwrappedID,
|
||||
readingProgress: percent,
|
||||
anchorIndex: anchorIndex
|
||||
)
|
||||
|
|
@ -100,7 +100,7 @@ public final class PDFViewerViewModel: ObservableObject {
|
|||
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)
|
||||
|
||||
if let username = services.dataService.currentViewer?.username {
|
||||
components?.path = "/\(username)/\(feedItem.slug)/highlights/\(shortId)"
|
||||
components?.path = "/\(username)/\(linkedItem.unwrappedSlug)/highlights/\(shortId)"
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import Views
|
|||
struct FeedCardNavigationLink: View {
|
||||
@EnvironmentObject var dataService: DataService
|
||||
|
||||
let item: FeedItemDep
|
||||
let item: LinkedItem
|
||||
|
||||
@ObservedObject var viewModel: HomeFeedViewModel
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ struct GridCardNavigationLink: View {
|
|||
|
||||
@State private var scale = 1.0
|
||||
|
||||
let item: FeedItemDep
|
||||
let item: LinkedItem
|
||||
let actionHandler: (GridCardAction) -> Void
|
||||
|
||||
@Binding var isContextMenuOpen: Bool
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ import Views
|
|||
}
|
||||
.sheet(item: $viewModel.itemUnderLabelEdit) { item in
|
||||
ApplyLabelsView(mode: .item(item)) { labels in
|
||||
viewModel.updateLabels(itemID: item.id, labels: labels)
|
||||
viewModel.updateLabels(itemID: item.unwrappedID, labels: labels)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,12 +57,13 @@ import Views
|
|||
viewModel.loadItems(dataService: dataService, isRefresh: true)
|
||||
}
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: Notification.Name("PushFeedItem"))) { notification in
|
||||
if let feedItem = notification.userInfo?["feedItem"] as? FeedItemDep {
|
||||
viewModel.pushFeedItem(item: feedItem)
|
||||
viewModel.selectedLinkItem = feedItem
|
||||
}
|
||||
}
|
||||
// TODO: fix this
|
||||
// .onReceive(NotificationCenter.default.publisher(for: Notification.Name("PushFeedItem"))) { notification in
|
||||
// if let feedItem = notification.userInfo?["feedItem"] as? FeedItemD---ep {
|
||||
// viewModel.pushFeedItem(item: feedItem)
|
||||
// viewModel.selectedLinkItem = feedItem
|
||||
// }
|
||||
// }
|
||||
.formSheet(isPresented: $viewModel.snoozePresented) {
|
||||
SnoozeView(snoozePresented: $viewModel.snoozePresented, itemToSnoozeID: $viewModel.itemToSnoozeID) {
|
||||
viewModel.snoozeUntil(
|
||||
|
|
@ -145,7 +146,7 @@ import Views
|
|||
@EnvironmentObject var dataService: DataService
|
||||
@Binding var prefersListLayout: Bool
|
||||
|
||||
@State private var itemToRemove: FeedItemDep?
|
||||
@State private var itemToRemove: LinkedItem?
|
||||
@State private var confirmationShown = false
|
||||
|
||||
@ObservedObject var viewModel: HomeFeedViewModel
|
||||
|
|
@ -165,7 +166,7 @@ import Views
|
|||
)
|
||||
Button(action: {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: !item.isArchived)
|
||||
viewModel.setLinkArchived(dataService: dataService, linkId: item.unwrappedID, archived: !item.isArchived)
|
||||
}
|
||||
}, label: {
|
||||
Label(
|
||||
|
|
@ -193,7 +194,7 @@ import Views
|
|||
if !item.isArchived {
|
||||
Button {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: true)
|
||||
viewModel.setLinkArchived(dataService: dataService, linkId: item.unwrappedID, archived: true)
|
||||
}
|
||||
} label: {
|
||||
Label("Archive", systemImage: "archivebox")
|
||||
|
|
@ -201,7 +202,7 @@ import Views
|
|||
} else {
|
||||
Button {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: false)
|
||||
viewModel.setLinkArchived(dataService: dataService, linkId: item.unwrappedID, archived: false)
|
||||
}
|
||||
} label: {
|
||||
Label("Unarchive", systemImage: "tray.and.arrow.down.fill")
|
||||
|
|
@ -223,7 +224,7 @@ import Views
|
|||
Button("Remove Link", role: .destructive) {
|
||||
if let itemToRemove = itemToRemove {
|
||||
withAnimation {
|
||||
viewModel.removeLink(dataService: dataService, linkId: itemToRemove.id)
|
||||
viewModel.removeLink(dataService: dataService, linkId: itemToRemove.unwrappedID)
|
||||
}
|
||||
}
|
||||
self.itemToRemove = nil
|
||||
|
|
@ -266,16 +267,16 @@ import Views
|
|||
struct HomeFeedGridView: View {
|
||||
@EnvironmentObject var dataService: DataService
|
||||
|
||||
@State private var itemToRemove: FeedItemDep?
|
||||
@State private var itemToRemove: LinkedItem?
|
||||
@State private var confirmationShown = false
|
||||
@State var isContextMenuOpen = false
|
||||
|
||||
@ObservedObject var viewModel: HomeFeedViewModel
|
||||
|
||||
func contextMenuActionHandler(item: FeedItemDep, action: GridCardAction) {
|
||||
func contextMenuActionHandler(item: LinkedItem, action: GridCardAction) {
|
||||
switch action {
|
||||
case .toggleArchiveStatus:
|
||||
viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: !item.isArchived)
|
||||
viewModel.setLinkArchived(dataService: dataService, linkId: item.unwrappedID, archived: !item.isArchived)
|
||||
case .delete:
|
||||
itemToRemove = item
|
||||
confirmationShown = true
|
||||
|
|
@ -298,7 +299,7 @@ import Views
|
|||
Button("Remove Link", role: .destructive) {
|
||||
if let itemToRemove = itemToRemove {
|
||||
withAnimation {
|
||||
viewModel.removeLink(dataService: dataService, linkId: itemToRemove.id)
|
||||
viewModel.removeLink(dataService: dataService, linkId: itemToRemove.unwrappedID)
|
||||
}
|
||||
}
|
||||
self.itemToRemove = nil
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import Views
|
|||
#if os(macOS)
|
||||
struct HomeFeedView: View {
|
||||
@EnvironmentObject var dataService: DataService
|
||||
@State private var itemToRemove: FeedItemDep?
|
||||
@State private var itemToRemove: LinkedItem?
|
||||
@State private var confirmationShown = false
|
||||
|
||||
@ObservedObject var viewModel: HomeFeedViewModel
|
||||
|
|
|
|||
|
|
@ -12,17 +12,17 @@ import Views
|
|||
var uncommittedReadingProgressUpdates = [String: Double]()
|
||||
|
||||
/// Track label updates to be committed when user navigates back to grid view
|
||||
var uncommittedLabelUpdates = [String: [FeedItemLabelDep]]()
|
||||
var uncommittedLabelUpdates = [String: [LinkedItemLabel]]()
|
||||
|
||||
@Published var items = [FeedItemDep]()
|
||||
@Published var items = [LinkedItem]()
|
||||
@Published var isLoading = false
|
||||
@Published var showPushNotificationPrimer = false
|
||||
@Published var itemUnderLabelEdit: FeedItemDep?
|
||||
@Published var itemUnderLabelEdit: LinkedItem?
|
||||
@Published var searchTerm = ""
|
||||
@Published var selectedLabels = [FeedItemLabelDep]()
|
||||
@Published var selectedLabels = [LinkedItemLabel]()
|
||||
@Published var snoozePresented = false
|
||||
@Published var itemToSnoozeID: String?
|
||||
@Published var selectedLinkItem: FeedItemDep?
|
||||
@Published var selectedLinkItem: LinkedItem?
|
||||
|
||||
var cursor: String?
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ import Views
|
|||
|
||||
init() {}
|
||||
|
||||
func itemAppeared(item: FeedItemDep, dataService: DataService) {
|
||||
func itemAppeared(item: LinkedItem, dataService: DataService) {
|
||||
if isLoading { return }
|
||||
let itemIndex = items.firstIndex(where: { $0.id == item.id })
|
||||
let thresholdIndex = items.index(items.endIndex, offsetBy: -5)
|
||||
|
|
@ -46,7 +46,7 @@ import Views
|
|||
}
|
||||
}
|
||||
|
||||
func pushFeedItem(item: FeedItemDep) {
|
||||
func pushFeedItem(item: LinkedItem) {
|
||||
items.insert(item, at: 0)
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ import Views
|
|||
guard case .failure = completion else { return }
|
||||
self?.isLoading = false
|
||||
// return cachedItems found in CoreData when request fails
|
||||
self?.items = dataService.cachedFeedItems()
|
||||
// self?.items = dataService.cachedFeedItems() // TODO: fetch items from core data
|
||||
self?.cursor = nil
|
||||
},
|
||||
receiveValue: { [weak self] result in
|
||||
|
|
@ -86,9 +86,15 @@ import Views
|
|||
return
|
||||
}
|
||||
|
||||
dataService.prefetchPages(items: result.items)
|
||||
|
||||
self?.items = isRefresh ? result.items : (self?.items ?? []) + result.items
|
||||
self?.items = {
|
||||
let itemIDs = isRefresh ? result.items : (self?.items ?? []).map(\.objectID) + result.items
|
||||
var itemObjects = [LinkedItem]()
|
||||
dataService.viewContext.performAndWait {
|
||||
itemObjects = itemIDs.compactMap { dataService.viewContext.object(with: $0) as? LinkedItem }
|
||||
}
|
||||
return itemObjects
|
||||
}()
|
||||
dataService.prefetchPages(itemSlugs: (self?.items ?? []).map(\.unwrappedSlug))
|
||||
self?.isLoading = false
|
||||
self?.receivedIdx = thisSearchIdx
|
||||
self?.cursor = result.cursor
|
||||
|
|
@ -193,18 +199,19 @@ import Views
|
|||
}
|
||||
}
|
||||
|
||||
func updateLabels(itemID: String, labels: [FeedItemLabelDep]) {
|
||||
// If item is being being displayed then delay the state update of labels until
|
||||
// user is no longer reading the item.
|
||||
if selectedLinkItem != nil {
|
||||
uncommittedLabelUpdates[itemID] = labels
|
||||
return
|
||||
}
|
||||
|
||||
guard let item = items.first(where: { $0.id == itemID }) else { return }
|
||||
if let index = items.firstIndex(of: item) {
|
||||
items[index].labels = labels
|
||||
}
|
||||
func updateLabels(itemID _: String, labels _: [LinkedItemLabel]) {
|
||||
// TODO: fix
|
||||
// // If item is being being displayed then delay the state update of labels until
|
||||
// // user is no longer reading the item.
|
||||
// if selectedLinkItem != nil {
|
||||
// uncommittedLabelUpdates[itemID] = labels
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// guard let item = items.first(where: { $0.id == itemID }) else { return }
|
||||
// if let index = items.firstIndex(of: item) {
|
||||
// items[index].labels = labels
|
||||
// }
|
||||
}
|
||||
|
||||
private var searchQuery: String? {
|
||||
|
|
@ -216,7 +223,7 @@ import Views
|
|||
|
||||
if !selectedLabels.isEmpty {
|
||||
query.append(" label:")
|
||||
query.append(selectedLabels.map(\.name).joined(separator: ","))
|
||||
query.append(selectedLabels.map { $0.name ?? "" }.joined(separator: ","))
|
||||
}
|
||||
|
||||
return query
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import Views
|
|||
|
||||
struct ApplyLabelsView: View {
|
||||
enum Mode {
|
||||
case item(FeedItemDep)
|
||||
case list([FeedItemLabelDep])
|
||||
case item(LinkedItem)
|
||||
case list([LinkedItemLabel])
|
||||
|
||||
var navTitle: String {
|
||||
switch self {
|
||||
|
|
@ -28,7 +28,7 @@ struct ApplyLabelsView: View {
|
|||
}
|
||||
|
||||
let mode: Mode
|
||||
let commitLabelChanges: ([FeedItemLabelDep]) -> Void
|
||||
let commitLabelChanges: ([LinkedItemLabel]) -> Void
|
||||
|
||||
@EnvironmentObject var dataService: DataService
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
|
|
@ -101,7 +101,7 @@ struct ApplyLabelsView: View {
|
|||
action: {
|
||||
switch mode {
|
||||
case let .item(feedItem):
|
||||
viewModel.saveItemLabelChanges(itemID: feedItem.id, dataService: dataService) { labels in
|
||||
viewModel.saveItemLabelChanges(itemID: feedItem.unwrappedID, dataService: dataService) { labels in
|
||||
commitLabelChanges(labels)
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
|
|
@ -147,11 +147,11 @@ struct ApplyLabelsView: View {
|
|||
}
|
||||
}
|
||||
|
||||
private extension Sequence where Element == FeedItemLabelDep {
|
||||
func applySearchFilter(_ searchFilter: String) -> [FeedItemLabelDep] {
|
||||
private extension Sequence where Element == LinkedItemLabel {
|
||||
func applySearchFilter(_ searchFilter: String) -> [LinkedItemLabel] {
|
||||
if searchFilter.isEmpty {
|
||||
return map { $0 } // return the identity of the sequence
|
||||
}
|
||||
return filter { $0.name.lowercased().contains(searchFilter.lowercased()) }
|
||||
return filter { ($0.name ?? "").lowercased().contains(searchFilter.lowercased()) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@ import Services
|
|||
import SwiftUI
|
||||
import Views
|
||||
|
||||
// TODO: fix file
|
||||
final class LabelsViewModel: ObservableObject {
|
||||
private var hasLoadedInitialLabels = false
|
||||
@Published var isLoading = false
|
||||
@Published var selectedLabels = [FeedItemLabelDep]()
|
||||
@Published var unselectedLabels = [FeedItemLabelDep]()
|
||||
@Published var labels = [FeedItemLabelDep]()
|
||||
@Published var selectedLabels = [LinkedItemLabel]()
|
||||
@Published var unselectedLabels = [LinkedItemLabel]()
|
||||
@Published var labels = [LinkedItemLabel]()
|
||||
@Published var showCreateEmailModal = false
|
||||
|
||||
var subscriptions = Set<AnyCancellable>()
|
||||
|
|
@ -20,55 +21,55 @@ final class LabelsViewModel: ObservableObject {
|
|||
/// - item: Optional `FeedItem` for applying labels to a single item
|
||||
/// - initiallySelectedLabels: Optional `[FeedItemLabel]` for filtering a list of items
|
||||
func loadLabels(
|
||||
dataService: DataService,
|
||||
item: FeedItemDep? = nil,
|
||||
initiallySelectedLabels: [FeedItemLabelDep]? = nil
|
||||
dataService _: DataService,
|
||||
item _: LinkedItem? = nil,
|
||||
initiallySelectedLabels _: [LinkedItemLabel]? = nil
|
||||
) {
|
||||
guard !hasLoadedInitialLabels else { return }
|
||||
isLoading = true
|
||||
|
||||
dataService.labelsPublisher().sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { [weak self] allLabels in
|
||||
self?.isLoading = false
|
||||
self?.labels = allLabels
|
||||
self?.hasLoadedInitialLabels = true
|
||||
if let item = item {
|
||||
self?.selectedLabels = item.labels
|
||||
self?.unselectedLabels = allLabels.filter { label in
|
||||
!item.labels.contains(where: { $0.id == label.id })
|
||||
}
|
||||
}
|
||||
if let initiallySelectedLabels = initiallySelectedLabels {
|
||||
self?.selectedLabels = initiallySelectedLabels
|
||||
self?.unselectedLabels = allLabels.filter { label in
|
||||
!initiallySelectedLabels.contains(where: { $0.id == label.id })
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.store(in: &subscriptions)
|
||||
// dataService.labelsPublisher().sink(
|
||||
// receiveCompletion: { _ in },
|
||||
// receiveValue: { [weak self] allLabels in
|
||||
// self?.isLoading = false
|
||||
// self?.labels = allLabels
|
||||
// self?.hasLoadedInitialLabels = true
|
||||
// if let item = item {
|
||||
// self?.selectedLabels = item.labels.asArray(of: LinkedItemLabel.self)
|
||||
// self?.unselectedLabels = allLabels.filter { label in
|
||||
// !item.labels.contains(where: { $0.id == label.id })
|
||||
// }
|
||||
// }
|
||||
// if let initiallySelectedLabels = initiallySelectedLabels {
|
||||
// self?.selectedLabels = initiallySelectedLabels
|
||||
// self?.unselectedLabels = allLabels.filter { label in
|
||||
// !initiallySelectedLabels.contains(where: { $0.id == label.id })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
// .store(in: &subscriptions)
|
||||
}
|
||||
|
||||
func createLabel(dataService: DataService, name: String, color: Color, description: String?) {
|
||||
isLoading = true
|
||||
|
||||
dataService.createLabelPublisher(
|
||||
name: name,
|
||||
color: color.hex ?? "",
|
||||
description: description
|
||||
).sink(
|
||||
receiveCompletion: { [weak self] _ in
|
||||
self?.isLoading = false
|
||||
},
|
||||
receiveValue: { [weak self] result in
|
||||
self?.isLoading = false
|
||||
self?.labels.insert(result, at: 0)
|
||||
self?.unselectedLabels.insert(result, at: 0)
|
||||
self?.showCreateEmailModal = false
|
||||
}
|
||||
)
|
||||
.store(in: &subscriptions)
|
||||
func createLabel(dataService _: DataService, name _: String, color _: Color, description _: String?) {
|
||||
// isLoading = true
|
||||
//
|
||||
// dataService.createLabelPublisher(
|
||||
// name: name,
|
||||
// color: color.hex ?? "",
|
||||
// description: description
|
||||
// ).sink(
|
||||
// receiveCompletion: { [weak self] _ in
|
||||
// self?.isLoading = false
|
||||
// },
|
||||
// receiveValue: { [weak self] result in
|
||||
// self?.isLoading = false
|
||||
// self?.labels.insert(result, at: 0)
|
||||
// self?.unselectedLabels.insert(result, at: 0)
|
||||
// self?.showCreateEmailModal = false
|
||||
// }
|
||||
// )
|
||||
// .store(in: &subscriptions)
|
||||
}
|
||||
|
||||
func deleteLabel(dataService: DataService, labelID: String) {
|
||||
|
|
@ -87,27 +88,27 @@ final class LabelsViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
func saveItemLabelChanges(
|
||||
itemID: String,
|
||||
dataService: DataService,
|
||||
onComplete: @escaping ([FeedItemLabelDep]) -> Void
|
||||
itemID _: String,
|
||||
dataService _: DataService,
|
||||
onComplete _: @escaping ([LinkedItemLabel]) -> Void
|
||||
) {
|
||||
isLoading = true
|
||||
dataService.updateArticleLabelsPublisher(itemID: itemID, labelIDs: selectedLabels.map(\.id)).sink(
|
||||
receiveCompletion: { [weak self] _ in
|
||||
self?.isLoading = false
|
||||
},
|
||||
receiveValue: { onComplete($0) }
|
||||
)
|
||||
.store(in: &subscriptions)
|
||||
// isLoading = true
|
||||
// dataService.updateArticleLabelsPublisher(itemID: itemID, labelIDs: selectedLabels.map(\.id)).sink(
|
||||
// receiveCompletion: { [weak self] _ in
|
||||
// self?.isLoading = false
|
||||
// },
|
||||
// receiveValue: { onComplete($0) }
|
||||
// )
|
||||
// .store(in: &subscriptions)
|
||||
}
|
||||
|
||||
func addLabelToItem(_ label: FeedItemLabelDep) {
|
||||
selectedLabels.insert(label, at: 0)
|
||||
unselectedLabels.removeAll { $0.id == label.id }
|
||||
func addLabelToItem(_: LinkedItemLabel) {
|
||||
// selectedLabels.insert(label, at: 0)
|
||||
// unselectedLabels.removeAll { $0.id == label.id }
|
||||
}
|
||||
|
||||
func removeLabelFromItem(_ label: FeedItemLabelDep) {
|
||||
unselectedLabels.insert(label, at: 0)
|
||||
selectedLabels.removeAll { $0.id == label.id }
|
||||
func removeLabelFromItem(_: LinkedItemLabel) {
|
||||
// unselectedLabels.insert(label, at: 0)
|
||||
// selectedLabels.removeAll { $0.id == label.id }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,33 +6,33 @@ import Utils
|
|||
import Views
|
||||
|
||||
enum PDFProvider {
|
||||
static var pdfViewerProvider: ((URL, FeedItemDep) -> AnyView)?
|
||||
static var pdfViewerProvider: ((URL, LinkedItem) -> AnyView)?
|
||||
}
|
||||
|
||||
@MainActor final class LinkItemDetailViewModel: ObservableObject {
|
||||
let homeFeedViewModel: HomeFeedViewModel
|
||||
@Published var item: FeedItemDep
|
||||
@Published var item: LinkedItem
|
||||
@Published var webAppWrapperViewModel: WebAppWrapperViewModel?
|
||||
|
||||
var subscriptions = Set<AnyCancellable>()
|
||||
|
||||
init(item: FeedItemDep, homeFeedViewModel: HomeFeedViewModel) {
|
||||
init(item: LinkedItem, homeFeedViewModel: HomeFeedViewModel) {
|
||||
self.item = item
|
||||
self.homeFeedViewModel = homeFeedViewModel
|
||||
}
|
||||
|
||||
func handleArchiveAction(dataService: DataService) {
|
||||
homeFeedViewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: !item.isArchived)
|
||||
homeFeedViewModel.setLinkArchived(dataService: dataService, linkId: item.unwrappedID, archived: !item.isArchived)
|
||||
}
|
||||
|
||||
func handleDeleteAction(dataService: DataService) {
|
||||
homeFeedViewModel.removeLink(dataService: dataService, linkId: item.id)
|
||||
homeFeedViewModel.removeLink(dataService: dataService, linkId: item.unwrappedID)
|
||||
}
|
||||
|
||||
func updateItemReadStatus(dataService: DataService) {
|
||||
dataService
|
||||
.updateArticleReadingProgressPublisher(
|
||||
itemID: item.id,
|
||||
itemID: item.unwrappedID,
|
||||
readingProgress: item.isRead ? 0 : 100,
|
||||
anchorIndex: 0
|
||||
)
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ public final class RootViewModel: ObservableObject {
|
|||
func configurePDFProvider(pdfViewerProvider: @escaping (URL, PDFViewerViewModel) -> AnyView) {
|
||||
guard PDFProvider.pdfViewerProvider == nil else { return }
|
||||
|
||||
PDFProvider.pdfViewerProvider = { [weak self] url, feedItem in
|
||||
PDFProvider.pdfViewerProvider = { [weak self] url, linkedItem in
|
||||
guard let self = self else { return AnyView(Text("")) }
|
||||
return pdfViewerProvider(url, PDFViewerViewModel(services: self.services, feedItem: feedItem))
|
||||
return pdfViewerProvider(url, PDFViewerViewModel(services: self.services, linkedItem: linkedItem))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import WebKit
|
|||
struct WebReader: UIViewRepresentable {
|
||||
let htmlContent: String
|
||||
let highlightsJSONString: String
|
||||
let item: FeedItemDep
|
||||
let item: LinkedItem
|
||||
let openLinkAction: (URL) -> Void
|
||||
let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void
|
||||
let navBarVisibilityRatioUpdater: (Double) -> Void
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import WebKit
|
|||
|
||||
#if os(iOS)
|
||||
struct WebReaderContainerView: View {
|
||||
let item: FeedItemDep
|
||||
let item: LinkedItem
|
||||
let homeFeedViewModel: HomeFeedViewModel
|
||||
|
||||
@State private var showFontSizePopover = false
|
||||
|
|
@ -37,7 +37,7 @@ import WebKit
|
|||
let messageBody = message.body as? [String: Double]
|
||||
|
||||
if let messageBody = messageBody, let progress = messageBody["progress"] {
|
||||
homeFeedViewModel.uncommittedReadingProgressUpdates[item.id] = Double(progress)
|
||||
homeFeedViewModel.uncommittedReadingProgressUpdates[item.unwrappedID] = Double(progress)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ import WebKit
|
|||
if message.name == WebViewAction.readingProgressUpdate.rawValue {
|
||||
guard let messageBody = message.body as? [String: Double] else { return }
|
||||
guard let progress = messageBody["progress"] else { return }
|
||||
homeFeedViewModel.uncommittedReadingProgressUpdates[item.id] = Double(progress)
|
||||
homeFeedViewModel.uncommittedReadingProgressUpdates[item.unwrappedID] = Double(progress)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ import WebKit
|
|||
action: {
|
||||
homeFeedViewModel.setLinkArchived(
|
||||
dataService: dataService,
|
||||
linkId: item.id,
|
||||
linkId: item.unwrappedID,
|
||||
archived: !item.isArchived
|
||||
)
|
||||
},
|
||||
|
|
@ -139,7 +139,7 @@ import WebKit
|
|||
}
|
||||
.alert("Are you sure?", isPresented: $showDeleteConfirmation) {
|
||||
Button("Remove Link", role: .destructive) {
|
||||
homeFeedViewModel.removeLink(dataService: dataService, linkId: item.id)
|
||||
homeFeedViewModel.removeLink(dataService: dataService, linkId: item.unwrappedID)
|
||||
}
|
||||
Button("Cancel", role: .cancel, action: {})
|
||||
}
|
||||
|
|
@ -206,7 +206,7 @@ import WebKit
|
|||
.contentShape(Rectangle())
|
||||
.onAppear {
|
||||
if !viewModel.isLoading {
|
||||
viewModel.loadContent(dataService: dataService, slug: item.slug)
|
||||
viewModel.loadContent(dataService: dataService, slug: item.unwrappedSlug)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ struct WebReaderContent {
|
|||
let textFontSize: Int
|
||||
let htmlContent: String
|
||||
let highlightsJSONString: String
|
||||
let item: FeedItemDep
|
||||
let item: LinkedItem
|
||||
let themeKey: String
|
||||
|
||||
init(
|
||||
htmlContent: String,
|
||||
highlightsJSONString: String,
|
||||
item: FeedItemDep,
|
||||
item: LinkedItem,
|
||||
isDark: Bool,
|
||||
fontSize: Int
|
||||
) {
|
||||
|
|
@ -25,8 +25,8 @@ struct WebReaderContent {
|
|||
|
||||
// swiftlint:disable line_length
|
||||
var styledContent: String {
|
||||
let savedAt = "new Date(\(item.savedAt.timeIntervalSince1970 * 1000)).toISOString()"
|
||||
let createdAt = "new Date(\(item.createdAt.timeIntervalSince1970 * 1000)).toISOString()"
|
||||
let savedAt = "new Date(\(item.unwrappedSavedAt.timeIntervalSince1970 * 1000)).toISOString()"
|
||||
let createdAt = "new Date(\(item.unwrappedCreatedAt.timeIntervalSince1970 * 1000)).toISOString()"
|
||||
let publishedAt = item.publishDate != nil ? "new Date(\(item.publishDate!.timeIntervalSince1970 * 1000)).toISOString()" : "undefined"
|
||||
|
||||
return """
|
||||
|
|
@ -53,16 +53,16 @@ struct WebReaderContent {
|
|||
}
|
||||
|
||||
window.omnivoreArticle = {
|
||||
id: "\(item.id)",
|
||||
linkId: "\(item.id)",
|
||||
slug: "\(item.slug)",
|
||||
id: "\(item.unwrappedID)",
|
||||
linkId: "\(item.unwrappedID)",
|
||||
slug: "\(item.unwrappedSlug)",
|
||||
createdAt: \(createdAt),
|
||||
savedAt: \(savedAt),
|
||||
publishedAt: \(publishedAt),
|
||||
url: `\(item.pageURLString)`,
|
||||
title: `\(item.title.replacingOccurrences(of: "`", with: "\\`"))`,
|
||||
url: `\(item.unwrappedPageURLString)`,
|
||||
title: `\(item.unwrappedTitle.replacingOccurrences(of: "`", with: "\\`"))`,
|
||||
content: document.getElementById('_omnivore-htmlContent').innerHTML,
|
||||
originalArticleUrl: "\(item.pageURLString)",
|
||||
originalArticleUrl: "\(item.unwrappedPageURLString)",
|
||||
contentReader: "WEB",
|
||||
readingProgressPercent: \(item.readingProgress),
|
||||
readingProgressAnchorIndex: \(item.readingProgressAnchor),
|
||||
|
|
|
|||
|
|
@ -2,105 +2,15 @@ import CoreData
|
|||
import Foundation
|
||||
|
||||
public struct HomeFeedData {
|
||||
public let items: [FeedItemDep]
|
||||
public let items: [NSManagedObjectID]
|
||||
public let cursor: String?
|
||||
|
||||
public init(items: [FeedItemDep], cursor: String?) {
|
||||
public init(items: [NSManagedObjectID], cursor: String?) {
|
||||
self.items = items
|
||||
self.cursor = cursor
|
||||
}
|
||||
}
|
||||
|
||||
public struct FeedItemDep: Identifiable, Hashable {
|
||||
public let id: String
|
||||
public let title: String
|
||||
public let createdAt: Date
|
||||
public let savedAt: Date
|
||||
public var readingProgress: Double
|
||||
public var readingProgressAnchor: Int
|
||||
public let imageURLString: String?
|
||||
public let onDeviceImageURLString: String?
|
||||
public let documentDirectoryPath: String?
|
||||
public let pageURLString: String
|
||||
public let descriptionText: String?
|
||||
public let publisherURLString: String?
|
||||
public let author: String?
|
||||
public let publishDate: Date?
|
||||
public let slug: String
|
||||
public let isArchived: Bool
|
||||
public let contentReader: String?
|
||||
public var labels: [FeedItemLabelDep]
|
||||
|
||||
public init(
|
||||
id: String,
|
||||
title: String,
|
||||
createdAt: Date,
|
||||
savedAt: Date,
|
||||
readingProgress: Double,
|
||||
readingProgressAnchor: Int,
|
||||
imageURLString: String?,
|
||||
onDeviceImageURLString: String?,
|
||||
documentDirectoryPath: String?,
|
||||
pageURLString: String,
|
||||
descriptionText: String?,
|
||||
publisherURLString: String?,
|
||||
author: String?,
|
||||
publishDate: Date?,
|
||||
slug: String,
|
||||
isArchived: Bool,
|
||||
contentReader: String?,
|
||||
labels: [FeedItemLabelDep]
|
||||
) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.createdAt = createdAt
|
||||
self.savedAt = savedAt
|
||||
self.readingProgress = readingProgress
|
||||
self.readingProgressAnchor = readingProgressAnchor
|
||||
self.imageURLString = imageURLString
|
||||
self.onDeviceImageURLString = onDeviceImageURLString
|
||||
self.documentDirectoryPath = documentDirectoryPath
|
||||
self.pageURLString = pageURLString
|
||||
self.descriptionText = descriptionText
|
||||
self.publisherURLString = publisherURLString
|
||||
self.author = author
|
||||
self.publishDate = publishDate
|
||||
self.slug = slug
|
||||
self.isArchived = isArchived
|
||||
self.contentReader = contentReader
|
||||
self.labels = labels
|
||||
}
|
||||
|
||||
// public static func fromJsonArticle(linkData: Data) -> FeedItemDe-----p? {
|
||||
// try? JSONDecoder().decode(JSONArticle.self, from: linkData).feedItem
|
||||
// }
|
||||
|
||||
public var isRead: Bool {
|
||||
readingProgress >= 0.98
|
||||
}
|
||||
|
||||
public var isPDF: Bool {
|
||||
if let contentReader = contentReader {
|
||||
return contentReader == "PDF"
|
||||
}
|
||||
return pageURLString.hasSuffix("pdf")
|
||||
}
|
||||
|
||||
public var publisherHostname: String? {
|
||||
URL(string: publisherURLString ?? pageURLString)?.host
|
||||
}
|
||||
|
||||
public var imageURL: URL? {
|
||||
imageURLString.flatMap { URL(string: $0) }
|
||||
}
|
||||
|
||||
public var pdfURL: URL? {
|
||||
guard isPDF else { return nil }
|
||||
let documentDirectoryURL = documentDirectoryPath.flatMap { URL(string: $0) }
|
||||
return documentDirectoryURL ?? URL(string: pageURLString)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: delete this
|
||||
// Internal model used for parsing a push notification object only
|
||||
// struct JSONArticle: Decodable {
|
||||
|
|
@ -116,8 +26,8 @@ public struct FeedItemDep: Identifiable, Hashable {
|
|||
// let url: String
|
||||
// let isArchived: Bool
|
||||
//
|
||||
// var feedItem: FeedItem-----Dep {
|
||||
// FeedItem--------Dep(
|
||||
// var feedItem: FeedItem-----De---p {
|
||||
// FeedItem--------De----p(
|
||||
// id: id,
|
||||
// title: title,
|
||||
// createdAt: createdAt,
|
||||
|
|
@ -140,80 +50,38 @@ public struct FeedItemDep: Identifiable, Hashable {
|
|||
// }
|
||||
// }
|
||||
|
||||
public extension FeedItemDep {
|
||||
func asManagedObject(inContext context: NSManagedObjectContext) -> LinkedItem {
|
||||
let existingItem = LinkedItem.lookup(byID: id, inContext: context)
|
||||
let linkedItem = existingItem ?? LinkedItem(entity: LinkedItem.entity(), insertInto: context)
|
||||
|
||||
linkedItem.id = id
|
||||
linkedItem.title = title
|
||||
linkedItem.createdAt = createdAt
|
||||
linkedItem.savedAt = savedAt
|
||||
linkedItem.readingProgress = readingProgress
|
||||
linkedItem.readingProgressAnchor = Int64(readingProgressAnchor)
|
||||
linkedItem.imageURLString = imageURLString
|
||||
linkedItem.onDeviceImageURLString = onDeviceImageURLString
|
||||
linkedItem.pageURLString = pageURLString
|
||||
linkedItem.descriptionText = descriptionText
|
||||
linkedItem.publisherURLString = publisherURLString
|
||||
linkedItem.author = author
|
||||
linkedItem.publishDate = publishDate
|
||||
linkedItem.slug = slug
|
||||
linkedItem.isArchived = isArchived
|
||||
linkedItem.contentReader = contentReader
|
||||
|
||||
// for label in labels {
|
||||
// // TODO: fix
|
||||
// }
|
||||
|
||||
return linkedItem
|
||||
}
|
||||
|
||||
static func make(from item: LinkedItem) -> FeedItemDep {
|
||||
FeedItemDep(
|
||||
id: item.id ?? "",
|
||||
title: item.title ?? "",
|
||||
createdAt: item.createdAt ?? Date(),
|
||||
savedAt: item.savedAt ?? Date(),
|
||||
readingProgress: item.readingProgress,
|
||||
readingProgressAnchor: Int(item.readingProgressAnchor),
|
||||
imageURLString: item.imageURLString,
|
||||
onDeviceImageURLString: item.onDeviceImageURLString,
|
||||
documentDirectoryPath: nil,
|
||||
pageURLString: item.pageURLString ?? "",
|
||||
descriptionText: item.title,
|
||||
publisherURLString: item.publisherURLString,
|
||||
author: item.author,
|
||||
publishDate: item.publishDate,
|
||||
slug: item.slug ?? "",
|
||||
isArchived: item.isArchived,
|
||||
contentReader: item.contentReader,
|
||||
labels: []
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Sequence where Element == FeedItemDep {
|
||||
func persist(context: NSManagedObjectContext) -> [LinkedItem]? {
|
||||
var result: [LinkedItem]?
|
||||
|
||||
context.performAndWait {
|
||||
let linkedItems = map { $0.asManagedObject(inContext: context) }
|
||||
|
||||
do {
|
||||
try context.save()
|
||||
result = linkedItems
|
||||
logger.debug("LinkedItems saved succesfully")
|
||||
} catch {
|
||||
context.rollback()
|
||||
logger.debug("Failed to save LinkedItems: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
public extension LinkedItem {
|
||||
var unwrappedID: String { id ?? "" }
|
||||
var unwrappedSlug: String { slug ?? "" }
|
||||
var unwrappedTitle: String { title ?? "" }
|
||||
var unwrappedPageURLString: String { pageURLString ?? "" }
|
||||
var unwrappedSavedAt: Date { savedAt ?? Date() }
|
||||
var unwrappedCreatedAt: Date { createdAt ?? Date() }
|
||||
|
||||
var isRead: Bool {
|
||||
readingProgress >= 0.98
|
||||
}
|
||||
|
||||
var isPDF: Bool {
|
||||
if let contentReader = contentReader {
|
||||
return contentReader == "PDF"
|
||||
}
|
||||
return (pageURLString ?? "").hasSuffix("pdf")
|
||||
}
|
||||
|
||||
var publisherHostname: String? {
|
||||
URL(string: publisherURLString ?? pageURLString ?? "")?.host
|
||||
}
|
||||
|
||||
var imageURL: URL? {
|
||||
imageURLString.flatMap { URL(string: $0) }
|
||||
}
|
||||
|
||||
var pdfURL: URL? {
|
||||
guard isPDF else { return nil }
|
||||
return URL(string: pageURLString ?? "")
|
||||
}
|
||||
|
||||
static func lookup(byID itemID: String, inContext context: NSManagedObjectContext) -> LinkedItem? {
|
||||
let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(
|
||||
|
|
|
|||
|
|
@ -1,23 +1 @@
|
|||
import Foundation
|
||||
|
||||
public struct FeedItemLabelDep: Decodable, Hashable {
|
||||
public let id: String
|
||||
public let name: String
|
||||
public let color: String
|
||||
public let createdAt: Date?
|
||||
public let labelDescription: String?
|
||||
|
||||
public init(
|
||||
id: String,
|
||||
name: String,
|
||||
color: String,
|
||||
createdAt: Date?,
|
||||
labelDescription: String?
|
||||
) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.color = color
|
||||
self.createdAt = createdAt
|
||||
self.labelDescription = labelDescription
|
||||
}
|
||||
}
|
||||
// TODO: delete file
|
||||
|
|
|
|||
|
|
@ -52,11 +52,10 @@ public final class DataService: ObservableObject {
|
|||
}
|
||||
|
||||
public extension DataService {
|
||||
func prefetchPages(items: [FeedItemDep]) {
|
||||
func prefetchPages(itemSlugs: [String]) {
|
||||
guard let username = currentViewer?.username else { return }
|
||||
|
||||
for item in items {
|
||||
let slug = item.slug
|
||||
for slug in itemSlugs {
|
||||
articleContentPublisher(username: username, slug: slug).sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { _ in }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import Combine
|
||||
import CoreData
|
||||
import Foundation
|
||||
import Models
|
||||
import SwiftGraphQL
|
||||
|
|
@ -8,9 +9,9 @@ public extension DataService {
|
|||
name: String,
|
||||
color: String,
|
||||
description: String?
|
||||
) -> AnyPublisher<FeedItemLabelDep, BasicError> {
|
||||
) -> AnyPublisher<NSManagedObjectID, BasicError> {
|
||||
enum MutationResult {
|
||||
case saved(label: FeedItemLabelDep)
|
||||
case saved(label: InternalLinkedItemLabel)
|
||||
case error(errorCode: Enums.CreateLabelErrorCode)
|
||||
}
|
||||
|
||||
|
|
@ -46,8 +47,9 @@ public extension DataService {
|
|||
|
||||
switch payload.data {
|
||||
case let .saved(label: label):
|
||||
// TODO: update CoreData
|
||||
promise(.success(label))
|
||||
// TODO: update CoreData and fix this label thing
|
||||
// promise(.success(label))
|
||||
promise(.failure(.message(messageText: "")))
|
||||
case let .error(errorCode: errorCode):
|
||||
promise(.failure(.message(messageText: errorCode.rawValue)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import Combine
|
||||
import CoreData
|
||||
import Foundation
|
||||
import Models
|
||||
import SwiftGraphQL
|
||||
|
|
@ -7,9 +8,9 @@ public extension DataService {
|
|||
func updateArticleLabelsPublisher(
|
||||
itemID: String,
|
||||
labelIDs: [String]
|
||||
) -> AnyPublisher<[FeedItemLabelDep], BasicError> {
|
||||
) -> AnyPublisher<[NSManagedObjectID], BasicError> {
|
||||
enum MutationResult {
|
||||
case saved(feedItem: [FeedItemLabelDep])
|
||||
case saved(feedItem: [InternalLinkedItemLabel])
|
||||
case error(errorCode: Enums.SetLabelsErrorCode)
|
||||
}
|
||||
|
||||
|
|
@ -44,8 +45,9 @@ public extension DataService {
|
|||
|
||||
switch payload.data {
|
||||
case let .saved(labels):
|
||||
// TODO: update CoreData
|
||||
promise(.success(labels))
|
||||
// TODO: update CoreData and fix dis
|
||||
promise(.failure(.message(messageText: "failed to set labels")))
|
||||
// promise(.success(labels))
|
||||
case .error:
|
||||
promise(.failure(.message(messageText: "failed to set labels")))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import Combine
|
||||
import CoreData
|
||||
import Foundation
|
||||
import Models
|
||||
import SwiftGraphQL
|
||||
|
||||
public extension DataService {
|
||||
func labelsPublisher() -> AnyPublisher<[FeedItemLabelDep], ServerError> {
|
||||
func labelsPublisher() -> AnyPublisher<[NSManagedObjectID], ServerError> {
|
||||
enum QueryResult {
|
||||
case success(result: [FeedItemLabelDep])
|
||||
case success(result: [InternalLinkedItemLabel])
|
||||
case error(error: String)
|
||||
}
|
||||
|
||||
|
|
@ -33,8 +34,9 @@ public extension DataService {
|
|||
case let .success(payload):
|
||||
switch payload.data {
|
||||
case let .success(result: result):
|
||||
// TODO: update CoreData
|
||||
promise(.success(result))
|
||||
// TODO: update CoreData and fix this
|
||||
// promise(.success(result))
|
||||
promise(.failure(.unknown))
|
||||
case .error:
|
||||
promise(.failure(.unknown))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,13 @@ public extension DataService {
|
|||
searchQuery: String?,
|
||||
cursor: String?
|
||||
) -> AnyPublisher<HomeFeedData, ServerError> {
|
||||
struct InternalHomeFeedData {
|
||||
let items: [InternalLinkedItem]
|
||||
let cursor: String?
|
||||
}
|
||||
|
||||
enum QueryResult {
|
||||
case success(result: HomeFeedData)
|
||||
case success(result: InternalHomeFeedData)
|
||||
case error(error: String)
|
||||
}
|
||||
|
||||
|
|
@ -21,7 +26,7 @@ public extension DataService {
|
|||
try $0.on(
|
||||
articlesSuccess: .init {
|
||||
QueryResult.success(
|
||||
result: HomeFeedData(
|
||||
result: InternalHomeFeedData(
|
||||
items: try $0.edges(selection: articleEdgeSelection.list),
|
||||
cursor: try $0.pageInfo(selection: Selection.PageInfo {
|
||||
try $0.endCursor()
|
||||
|
|
@ -61,9 +66,11 @@ public extension DataService {
|
|||
case let .success(payload):
|
||||
switch payload.data {
|
||||
case let .success(result: result):
|
||||
// save items to coredata
|
||||
_ = result.items.persist(context: self.backgroundContext)
|
||||
promise(.success(result))
|
||||
if let items = result.items.persist(context: self.backgroundContext) {
|
||||
promise(.success(HomeFeedData(items: items.map(\.objectID), cursor: result.cursor)))
|
||||
} else {
|
||||
promise(.failure(.unknown))
|
||||
}
|
||||
case .error:
|
||||
promise(.failure(.unknown))
|
||||
}
|
||||
|
|
@ -77,22 +84,23 @@ public extension DataService {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func cachedFeedItems() -> [FeedItemDep] {
|
||||
var result = [FeedItemDep]()
|
||||
|
||||
backgroundContext.performAndWait {
|
||||
let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \LinkedItem.savedAt, ascending: false)]
|
||||
let items = (try? backgroundContext.fetch(fetchRequest)) ?? []
|
||||
result = items.map { FeedItemDep.make(from: $0) }
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
// TODO: delete
|
||||
// func cachedFeedItems() -> [FeedItem---D----ep] {
|
||||
// var result = [FeedItem------D---ep]()
|
||||
//
|
||||
// backgroundContext.performAndWait {
|
||||
// let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
|
||||
// fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \LinkedItem.savedAt, ascending: false)]
|
||||
// let items = (try? backgroundContext.fetch(fetchRequest)) ?? []
|
||||
// result = items.map { FeedItem-----D----ep.make(from: $0) }
|
||||
// }
|
||||
//
|
||||
// return result
|
||||
// }
|
||||
}
|
||||
|
||||
let homeFeedItemSelection = Selection.Article {
|
||||
FeedItemDep(
|
||||
InternalLinkedItem(
|
||||
id: try $0.id(),
|
||||
title: try $0.title(),
|
||||
createdAt: try $0.createdAt().value ?? Date(),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import Models
|
|||
import SwiftGraphQL
|
||||
|
||||
let feedItemLabelSelection = Selection.Label {
|
||||
FeedItemLabelDep(
|
||||
InternalLinkedItemLabel(
|
||||
id: try $0.id(),
|
||||
name: try $0.name(),
|
||||
color: try $0.color(),
|
||||
|
|
|
|||
|
|
@ -77,16 +77,18 @@ struct InternalLinkedItem {
|
|||
extension Sequence where Element == InternalLinkedItem {
|
||||
// TODO: use batch update?
|
||||
func persist(context: NSManagedObjectContext) -> [LinkedItem]? {
|
||||
let linkedItems = map { $0.asManagedObject(inContext: context) }
|
||||
var linkedItems: [LinkedItem]?
|
||||
context.performAndWait {
|
||||
linkedItems = map { $0.asManagedObject(inContext: context) }
|
||||
|
||||
do {
|
||||
try context.save()
|
||||
print("LinkedItems saved succesfully")
|
||||
return linkedItems
|
||||
} catch {
|
||||
context.rollback()
|
||||
print("Failed to save LinkedItems: \(error.localizedDescription)")
|
||||
return nil
|
||||
do {
|
||||
try context.save()
|
||||
print("LinkedItems saved succesfully")
|
||||
} catch {
|
||||
context.rollback()
|
||||
print("Failed to save LinkedItems: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
return linkedItems
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public extension NSNotification {
|
|||
}
|
||||
|
||||
// TODO: re-enable later
|
||||
// static func pushFeedItem(feedItem: FeedItem-----Dep) {
|
||||
// static func pushFeedItem(feedItem: FeedItem-----D---ep) {
|
||||
// NotificationCenter.default.post(name: NSNotification.PushFeedItem, object: nil, userInfo: ["feedItem": feedItem])
|
||||
// }
|
||||
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ public enum GridCardAction {
|
|||
|
||||
public struct GridCard: View {
|
||||
@Binding var isContextMenuOpen: Bool
|
||||
let item: FeedItemDep
|
||||
let item: LinkedItem
|
||||
let actionHandler: (GridCardAction) -> Void
|
||||
let tapAction: () -> Void
|
||||
|
||||
public init(
|
||||
item: FeedItemDep,
|
||||
item: LinkedItem,
|
||||
isContextMenuOpen: Binding<Bool>,
|
||||
actionHandler: @escaping (GridCardAction) -> Void,
|
||||
tapAction: @escaping () -> Void
|
||||
|
|
@ -77,7 +77,7 @@ public struct GridCard: View {
|
|||
// Title, Subtitle, Menu Button
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack {
|
||||
Text(item.title)
|
||||
Text(item.unwrappedTitle)
|
||||
.font(.appHeadline)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
.lineLimit(1)
|
||||
|
|
@ -117,7 +117,7 @@ public struct GridCard: View {
|
|||
|
||||
// Link description and image
|
||||
HStack(alignment: .top) {
|
||||
Text(item.descriptionText ?? item.title)
|
||||
Text(item.descriptionText ?? item.unwrappedTitle)
|
||||
.font(.appSubheadline)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
.lineLimit(nil)
|
||||
|
|
@ -152,7 +152,7 @@ public struct GridCard: View {
|
|||
if FeatureFlag.enableLabels {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack {
|
||||
ForEach(item.labels, id: \.self) {
|
||||
ForEach(item.labels.asArray(of: LinkedItemLabel.self), id: \.self) {
|
||||
TextChip(feedItemLabel: $0)
|
||||
}
|
||||
Spacer()
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@ import SwiftUI
|
|||
import Utils
|
||||
|
||||
public struct FeedCard: View {
|
||||
let item: FeedItemDep
|
||||
let item: LinkedItem
|
||||
|
||||
public init(item: FeedItemDep) {
|
||||
public init(item: LinkedItem) {
|
||||
self.item = item
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
HStack(alignment: .top, spacing: 6) {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text(item.title)
|
||||
Text(item.unwrappedTitle)
|
||||
.font(.appSubheadline)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
.lineLimit(2)
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ public struct TextChip: View {
|
|||
self.color = color
|
||||
}
|
||||
|
||||
public init?(feedItemLabel: FeedItemLabelDep) {
|
||||
guard let color = Color(hex: feedItemLabel.color) else { return nil }
|
||||
public init?(feedItemLabel: LinkedItemLabel) {
|
||||
guard let color = Color(hex: feedItemLabel.color ?? "") else { return nil }
|
||||
|
||||
self.text = feedItemLabel.name
|
||||
self.text = feedItemLabel.name ?? ""
|
||||
self.color = color
|
||||
}
|
||||
|
||||
|
|
@ -41,12 +41,12 @@ public struct TextChipButton: View {
|
|||
}
|
||||
|
||||
public static func makeRemovableLabelButton(
|
||||
feedItemLabel: FeedItemLabelDep,
|
||||
feedItemLabel: LinkedItemLabel,
|
||||
onTap: @escaping () -> Void
|
||||
) -> TextChipButton {
|
||||
TextChipButton(
|
||||
title: feedItemLabel.name,
|
||||
color: Color(hex: feedItemLabel.color) ?? .appButtonBackground,
|
||||
title: feedItemLabel.name ?? "",
|
||||
color: Color(hex: feedItemLabel.color ?? "") ?? .appButtonBackground,
|
||||
actionType: .remove,
|
||||
onTap: onTap
|
||||
)
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@ import Utils
|
|||
|
||||
coordinator.viewer = self
|
||||
|
||||
if viewModel.feedItem.readingProgressAnchor > 0 {
|
||||
let pageIndex = UInt(viewModel.feedItem.readingProgressAnchor)
|
||||
if viewModel.linkedItem.readingProgressAnchor > 0 {
|
||||
let pageIndex = UInt(viewModel.linkedItem.readingProgressAnchor)
|
||||
controller.setPageIndex(pageIndex, animated: false)
|
||||
}
|
||||
|
||||
|
|
@ -145,7 +145,7 @@ import Utils
|
|||
let pageIndex = Int(event.pageIndex)
|
||||
if let totalPageCount = controller.document?.pageCount {
|
||||
let percent = min(100, max(0, ((Double(pageIndex) + 1.0) / Double(totalPageCount)) * 100.0))
|
||||
if percent > self.viewModel.feedItem.readingProgress {
|
||||
if percent > self.viewModel.linkedItem.readingProgress {
|
||||
self.viewModel.updateItemReadProgress(percent: percent, anchorIndex: pageIndex)
|
||||
}
|
||||
}
|
||||
|
|
@ -199,7 +199,7 @@ import Utils
|
|||
"id": highlightID,
|
||||
"shortId": shortId,
|
||||
"quote": quote,
|
||||
"articleId": viewModel.feedItem.id
|
||||
"articleId": viewModel.linkedItem.unwrappedID
|
||||
]
|
||||
]
|
||||
document.add(annotations: [highlight])
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
|
|||
// TODO: fix
|
||||
// if let linkData = userInfo["link"] as? String {
|
||||
// guard let jsonData = Data(base64Encoded: linkData) else { return }
|
||||
// if let item = FeedItemDep.fromJsonArticle(linkData: jsonData) {
|
||||
// if let item = FeedItem---D--ep.fromJsonArticle(linkData: jsonData) {
|
||||
// NSNotification.pushFeedItem(feedItem: item)
|
||||
// }
|
||||
// }
|
||||
|
|
|
|||
Loading…
Reference in a new issue