remove deprecated data models

This commit is contained in:
Satindar Dhillon 2022-04-22 19:58:13 -07:00
parent 59533c9a2d
commit a63bf7928e
27 changed files with 269 additions and 399 deletions

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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()) }
}
}

View file

@ -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 }
}
}

View file

@ -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
)

View file

@ -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))
}
}

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -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),

View file

@ -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(

View file

@ -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

View 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 }

View file

@ -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)))
}

View file

@ -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")))
}

View file

@ -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))
}

View file

@ -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(),

View file

@ -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(),

View file

@ -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
}
}

View file

@ -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])
// }

View file

@ -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()

View file

@ -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)

View file

@ -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
)

View file

@ -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])

View file

@ -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)
// }
// }