diff --git a/apple/OmnivoreKit/Sources/App/Views/InformationalSnackBar.swift b/apple/OmnivoreKit/Sources/App/Views/InformationalSnackBar.swift new file mode 100644 index 000000000..045e40c20 --- /dev/null +++ b/apple/OmnivoreKit/Sources/App/Views/InformationalSnackBar.swift @@ -0,0 +1,39 @@ +import SwiftUI +import Views + +struct InformationalSnackbar: View { + let message: String? + let undoAction: (() -> Void)? + + var body: some View { + VStack { + HStack { + if let message = message { + Text(message) + } + Spacer() + + if let undoAction = self.undoAction { + Button(action: { + undoAction() + }, label: { + Text("Undo") + .bold() + .foregroundColor(.blue) + + }) + .padding(.trailing, 2) + } + } + .padding(10) + .frame(height: 50) + .frame(maxWidth: 380) + .background(Color(hex: "2A2A2A")) + .foregroundColor(Color(hex: "EBEBEB")) + .cornerRadius(4.0) + } + .padding(.bottom, 60) + .padding(.horizontal, 10) + .ignoresSafeArea(.all, edges: .bottom) + } +} diff --git a/apple/OmnivoreKit/Sources/App/Views/PDFContainerView.swift b/apple/OmnivoreKit/Sources/App/Views/PDFContainerView.swift new file mode 100644 index 000000000..a8b9043fa --- /dev/null +++ b/apple/OmnivoreKit/Sources/App/Views/PDFContainerView.swift @@ -0,0 +1,66 @@ +import Combine +import Models +import SwiftUI +import Utils +import Services + +@MainActor final class PDFContainerViewModel: ObservableObject { + func trackReadEvent(item: Models.LibraryItem, reader: String) { + let itemID = item.unwrappedID + let slug = item.unwrappedSlug + let originalArticleURL = item.unwrappedPageURLString + + EventTracker.track( + .linkRead( + linkID: itemID, + slug: slug, + reader: reader, + originalArticleURL: originalArticleURL + ) + ) + } +} + +struct PDFContainerView: View { + let item: Models.LibraryItem + let pdfItem: PDFItem? + + + @EnvironmentObject var dataService: DataService + @StateObject private var viewModel = PDFContainerViewModel() + + init(item: Models.LibraryItem) { + self.item = item + self.pdfItem = PDFItem.make(item: item) + } + + var body: some View { + NavigationView { + pdfContainerView + .navigationBarBackButtonHidden(false) + } + .navigationViewStyle(.stack) + .ignoresSafeArea(.all, edges: .bottom) + .onAppear { + viewModel.trackReadEvent(item: item, reader: "PDF") + } + } + + @ViewBuilder private var pdfContainerView: some View { + if let pdfItem = pdfItem, let pdfURL = pdfItem.pdfURL { + #if os(iOS) + PDFViewer(viewModel: PDFViewerViewModel(pdfItem: pdfItem)) + .navigationBarTitleDisplayMode(.inline) + #elseif os(macOS) + PDFWrapperView(pdfURL: pdfURL) + #endif + } else { + HStack(alignment: .center) { + Spacer() + Text("Loading") + Spacer() + } + } + } + +} diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/EmptyTrash.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/EmptyTrash.swift new file mode 100644 index 000000000..6be9a7fb0 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/EmptyTrash.swift @@ -0,0 +1,79 @@ +import CoreData +import Foundation +import Models +import SwiftGraphQL + +public extension DataService { + func emptyTrash() async -> Bool { + enum MutationResult { + case result(success: Bool) + case error(errorMessage: String) + } + + let selection = Selection { + try $0.on( + emptyTrashError: .init { .error(errorMessage: try $0.errorCodes().first?.rawValue ?? "Unknown Error") }, + emptyTrashSuccess: .init { + .result(success: try $0.success() ?? false) + } + ) + } + + let mutation = Selection.Mutation { + try $0.emptyTrash(selection: selection) + } + + let path = appEnvironment.graphqlPath + let headers = networker.defaultHeaders + let context = backgroundContext + + return await withCheckedContinuation { continuation in + send(mutation, to: path, headers: headers) { queryResult in + guard let payload = try? queryResult.get() else { + print("network error emptying trash") + continuation.resume(returning: false) + return + } + + switch (payload.data) { + case let .result(success): + if !success { + print("server did not return success for emptying trash") + continuation.resume(returning: false) + return + } + default: + print("server did not return success for emptying trash") + continuation.resume(returning: false) + return + } + + do { + try context.performAndWait { + let fetchRequest = LibraryItem.fetchRequest() + fetchRequest.predicate = NSPredicate( + format: "%K == %i OR %K == \"DELETED\"", + #keyPath(Models.LibraryItem.serverSyncStatus), Int64(ServerSyncStatus.needsDeletion.rawValue), + #keyPath(Models.LibraryItem.state) + ) + for object in try context.fetch(fetchRequest) { + context.delete(object) + } + do { + try context.save() + logger.debug("Empty trash completed") + continuation.resume(returning: true) + } catch { + context.rollback() + logger.debug("Failed to sync library item move: \(error.localizedDescription)") + continuation.resume(returning: false) + } + } + } catch { + print("error emptying trash", error) + continuation.resume(returning: false) + } + } + } + } +}