diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index bc4b757ac..0d40f279f 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -163,7 +163,7 @@ import Views withAnimation(.linear(duration: 0.4)) { viewModel.setLinkArchived( dataService: dataService, - linkId: item.unwrappedID, + objectID: item.objectID, archived: !item.isArchived ) } @@ -193,7 +193,7 @@ import Views if !item.isArchived { Button { withAnimation(.linear(duration: 0.4)) { - viewModel.setLinkArchived(dataService: dataService, linkId: item.unwrappedID, archived: true) + viewModel.setLinkArchived(dataService: dataService, objectID: item.objectID, archived: true) } } label: { Label("Archive", systemImage: "archivebox") @@ -201,7 +201,7 @@ import Views } else { Button { withAnimation(.linear(duration: 0.4)) { - viewModel.setLinkArchived(dataService: dataService, linkId: item.unwrappedID, archived: false) + viewModel.setLinkArchived(dataService: dataService, objectID: item.objectID, archived: false) } } label: { Label("Unarchive", systemImage: "tray.and.arrow.down.fill") @@ -223,7 +223,7 @@ import Views Button("Remove Link", role: .destructive) { if let itemToRemove = itemToRemove { withAnimation { - viewModel.removeLink(dataService: dataService, linkId: itemToRemove.unwrappedID) + viewModel.removeLink(dataService: dataService, objectID: itemToRemove.objectID) } } self.itemToRemove = nil @@ -275,7 +275,7 @@ import Views func contextMenuActionHandler(item: LinkedItem, action: GridCardAction) { switch action { case .toggleArchiveStatus: - viewModel.setLinkArchived(dataService: dataService, linkId: item.unwrappedID, archived: !item.isArchived) + viewModel.setLinkArchived(dataService: dataService, objectID: item.objectID, archived: !item.isArchived) case .delete: itemToRemove = item confirmationShown = true @@ -298,7 +298,7 @@ import Views Button("Remove Link", role: .destructive) { if let itemToRemove = itemToRemove { withAnimation { - viewModel.removeLink(dataService: dataService, linkId: itemToRemove.unwrappedID) + viewModel.removeLink(dataService: dataService, objectID: itemToRemove.objectID) } } self.itemToRemove = nil diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index 38a68cd71..d89517f82 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -106,35 +106,19 @@ import Views .store(in: &subscriptions) } - func setLinkArchived(dataService: DataService, linkId: String, archived: Bool) { + func setLinkArchived(dataService: DataService, objectID: NSManagedObjectID, archived: Bool) { // TODO: remove this by making list always fetch from Coredata - if let itemIndex = items.firstIndex(where: { $0.id == linkId }) { - items.remove(at: itemIndex) - } - dataService.archiveLink(itemID: linkId, archived: archived) + guard let itemIndex = items.firstIndex(where: { $0.objectID == objectID }) else { return } + items.remove(at: itemIndex) + dataService.archiveLink(objectID: objectID, archived: archived) Snackbar.show(message: archived ? "Link archived" : "Link moved to Inbox") } - func removeLink(dataService: DataService, linkId: String) { - isLoading = true - - if let itemIndex = items.firstIndex(where: { $0.id == linkId }) { - items.remove(at: itemIndex) - } - - dataService.removeLinkPublisher(itemID: linkId) - .sink( - receiveCompletion: { [weak self] completion in - guard case .failure = completion else { return } - self?.isLoading = false - Snackbar.show(message: "Failed to remove link") - }, - receiveValue: { [weak self] _ in - self?.isLoading = false - Snackbar.show(message: "Link removed") - } - ) - .store(in: &subscriptions) + func removeLink(dataService: DataService, objectID: NSManagedObjectID) { + guard let itemIndex = items.firstIndex(where: { $0.objectID == objectID }) else { return } + items.remove(at: itemIndex) + Snackbar.show(message: "Link removed") + dataService.removeLink(objectID: objectID) } func snoozeUntil(dataService: DataService, linkId: String, until: Date, successMessage: String?) { @@ -164,13 +148,6 @@ import Views .store(in: &subscriptions) } - private func updateProgress(itemID: String, progress: Double) { - guard let item = items.first(where: { $0.id == itemID }) else { return } - if let index = items.firstIndex(of: item) { - items[index].readingProgress = progress - } - } - private var searchQuery: String? { if searchTerm.isEmpty, selectedLabels.isEmpty { return nil diff --git a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift index c6465a8ec..b56ba2304 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift @@ -22,11 +22,15 @@ enum PDFProvider { } func handleArchiveAction(dataService: DataService) { - homeFeedViewModel.setLinkArchived(dataService: dataService, linkId: item.unwrappedID, archived: !item.isArchived) + homeFeedViewModel.setLinkArchived( + dataService: dataService, + objectID: item.objectID, + archived: !item.isArchived + ) } func handleDeleteAction(dataService: DataService) { - homeFeedViewModel.removeLink(dataService: dataService, linkId: item.unwrappedID) + homeFeedViewModel.removeLink(dataService: dataService, objectID: item.objectID) } func updateItemReadStatus(dataService: DataService) { diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index 3c7323d6d..67a45339b 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -93,7 +93,7 @@ import WebKit action: { homeFeedViewModel.setLinkArchived( dataService: dataService, - linkId: item.unwrappedID, + objectID: item.objectID, archived: !item.isArchived ) }, @@ -125,7 +125,7 @@ import WebKit } .alert("Are you sure?", isPresented: $showDeleteConfirmation) { Button("Remove Link", role: .destructive) { - homeFeedViewModel.removeLink(dataService: dataService, linkId: item.unwrappedID) + homeFeedViewModel.removeLink(dataService: dataService, objectID: item.objectID) } Button("Cancel", role: .cancel, action: {}) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/ArchiveLink.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/ArchiveLink.swift index 34479dd96..2781b7a48 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/ArchiveLink.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/ArchiveLink.swift @@ -1,19 +1,22 @@ +import CoreData import Foundation import Models import SwiftGraphQL extension DataService { - public func archiveLink(itemID: String, archived: Bool) { + public func archiveLink(objectID: NSManagedObjectID, archived: Bool) { // Update CoreData - if let linkedItem = LinkedItem.lookup(byID: itemID, inContext: backgroundContext) { - linkedItem.update(inContext: backgroundContext, newIsArchivedValue: archived) - } + backgroundContext.perform { [weak self] in + guard let self = self else { return } + guard let linkedItem = self.backgroundContext.object(with: objectID) as? LinkedItem else { return } + linkedItem.update(inContext: self.backgroundContext, newIsArchivedValue: archived) - // Send update to server - syncLinkArchiveStatus(itemID: itemID, archived: archived) + // Send update to server + self.syncLinkArchiveStatus(itemID: linkedItem.unwrappedID, objectID: objectID, archived: archived) + } } - func syncLinkArchiveStatus(itemID: String, archived: Bool) { + func syncLinkArchiveStatus(itemID: String, objectID: NSManagedObjectID, archived: Bool) { enum MutationResult { case success(linkId: String) case error(errorCode: Enums.ArchiveLinkErrorCode) @@ -45,7 +48,7 @@ extension DataService { let syncStatus: ServerSyncStatus = data == nil ? .needsUpdate : .isNSync context.perform { - guard let linkedItem = LinkedItem.lookup(byID: itemID, inContext: context) else { return } + guard let linkedItem = context.object(with: objectID) as? LinkedItem else { return } linkedItem.serverSyncStatus = Int64(syncStatus.rawValue) do { diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RemoveLink.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RemoveLink.swift index 48a7fdd22..f914ab326 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RemoveLink.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RemoveLink.swift @@ -1,16 +1,23 @@ import Combine +import CoreData import Foundation import Models import SwiftGraphQL -let setBookmarkedArticleSelection = Selection.Article { - try $0.id() -} +extension DataService { + public func removeLink(objectID: NSManagedObjectID) { + // Update CoreData + backgroundContext.perform { [weak self] in + guard let self = self else { return } + guard let linkedItem = self.backgroundContext.object(with: objectID) as? LinkedItem else { return } + linkedItem.remove(inContext: self.backgroundContext) -public extension DataService { - func removeLinkPublisher( - itemID: String - ) -> AnyPublisher { + // Send update to server + self.syncLinkDeletion(itemID: linkedItem.unwrappedID, objectID: objectID) + } + } + + func syncLinkDeletion(itemID: String, objectID _: NSManagedObjectID) { enum MutationResult { case success(linkId: String) case error(errorCode: Enums.SetBookmarkArticleErrorCode) @@ -20,7 +27,9 @@ public extension DataService { try $0.on( setBookmarkArticleSuccess: .init { .success( - linkId: try $0.bookmarkedArticle(selection: setBookmarkedArticleSelection) + linkId: try $0.bookmarkedArticle(selection: Selection.Article { + try $0.id() + }) ) }, setBookmarkArticleError: .init { .error(errorCode: try $0.errorCodes().first ?? .notFound) } @@ -39,32 +48,24 @@ public extension DataService { let path = appEnvironment.graphqlPath let headers = networker.defaultHeaders + let context = backgroundContext - return Deferred { - Future { promise in - send(mutation, to: path, headers: headers) { result in - switch result { - case let .success(payload): - if payload.errors != nil { - promise(.failure(.message(messageText: "Error removing link"))) - } + send(mutation, to: path, headers: headers) { result in + let data = try? result.get() + let syncStatus: ServerSyncStatus = data == nil ? .needsDeletion : .isNSync - switch payload.data { - case .success: - if let linkedItem = LinkedItem.lookup(byID: itemID, inContext: self.backgroundContext) { - linkedItem.remove(inContext: self.backgroundContext) - } - promise(.success(itemID)) - case .error(errorCode: _): - promise(.failure(.message(messageText: "Error removing link"))) - } - case .failure: - promise(.failure(.message(messageText: "Error removing link"))) - } + context.perform { + guard let linkedItem = LinkedItem.lookup(byID: itemID, inContext: context) else { return } + linkedItem.serverSyncStatus = Int64(syncStatus.rawValue) + + do { + try context.save() + logger.debug("LinkedItem deleted succesfully") + } catch { + context.rollback() + logger.debug("Failed to delete LinkedItem: \(error.localizedDescription)") } } } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() } }