diff --git a/apple/Omnivore.xcodeproj/project.pbxproj b/apple/Omnivore.xcodeproj/project.pbxproj index 0e2288668..6e53e68b2 100644 --- a/apple/Omnivore.xcodeproj/project.pbxproj +++ b/apple/Omnivore.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ 42321E892714E6B00056429F /* views in Resources */ = {isa = PBXBuildFile; fileRef = 42321E842714E6B00056429F /* views */; }; 42321E8A2714E6B00056429F /* views in Resources */ = {isa = PBXBuildFile; fileRef = 42321E842714E6B00056429F /* views */; }; 42704E7328E6BDB000C8C73E /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42704E7228E6BDAF00C8C73E /* SnapshotHelper.swift */; }; + 42B4483A2B1DD8AC00CEC5A0 /* AppIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42B448392B1DD8AC00CEC5A0 /* AppIntents.swift */; }; 42E2BFB428E458E0007F29B2 /* AppStoreScreenshots.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42E2BFB328E458E0007F29B2 /* AppStoreScreenshots.swift */; }; 42FF1B33271154A700B38C38 /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42FF1AEB271154A600B38C38 /* SafariWebExtensionHandler.swift */; }; 42FF1B34271154A700B38C38 /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42FF1AEB271154A600B38C38 /* SafariWebExtensionHandler.swift */; }; @@ -212,6 +213,7 @@ 42321E822714E6B00056429F /* scripts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = scripts; sourceTree = ""; }; 42321E842714E6B00056429F /* views */ = {isa = PBXFileReference; lastKnownFileType = folder; path = views; sourceTree = ""; }; 42704E7228E6BDAF00C8C73E /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotHelper.swift; sourceTree = ""; }; + 42B448392B1DD8AC00CEC5A0 /* AppIntents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppIntents.swift; sourceTree = ""; }; 42E2BFB128E458E0007F29B2 /* AppStoreScreenshots.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppStoreScreenshots.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 42E2BFB328E458E0007F29B2 /* AppStoreScreenshots.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStoreScreenshots.swift; sourceTree = ""; }; 42FF1AEB271154A600B38C38 /* SafariWebExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariWebExtensionHandler.swift; sourceTree = ""; }; @@ -489,6 +491,7 @@ 7C54279684D6252C9CD18562 /* Sources */ = { isa = PBXGroup; children = ( + 42B448392B1DD8AC00CEC5A0 /* AppIntents.swift */, 0480E71B26D95096006CAE2F /* AppDelegate.swift */, D81BE98F0CB588F5FC577A13 /* MainApp.swift */, 42FF1AEA271154A600B38C38 /* SafariExtension */, @@ -1073,6 +1076,7 @@ buildActionMask = 2147483647; files = ( 0480E71C26D95096006CAE2F /* AppDelegate.swift in Sources */, + 42B4483A2B1DD8AC00CEC5A0 /* AppIntents.swift in Sources */, 04920CC6279671EF003EC1B6 /* PushNotificationConfig.swift in Sources */, CA7EE773095F267516D7AC98 /* MainApp.swift in Sources */, ); diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Intents/SavePasteboardToOmnivore.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Intents/SavePasteboardToOmnivore.swift deleted file mode 100644 index ba4307507..000000000 --- a/apple/OmnivoreKit/Sources/App/AppExtensions/Intents/SavePasteboardToOmnivore.swift +++ /dev/null @@ -1,32 +0,0 @@ -#if os(iOS) - import AppIntents - import Services - import SwiftUI - - @available(iOS 16.0, *) - struct SaveLinkToOmnivoreIntent: AppIntent { - static var title: LocalizedStringResource = "Show Sport Progress" - - static var parameterSummary: some ParameterSummary { - Summary("Save \(\.$link) to your Omnivore library.") - } - - @Parameter(title: "link") - var link: URL - - @MainActor - func perform() async throws -> some IntentResult { - do { - let services = Services() - let requestId = UUID().uuidString.lowercased() - _ = try await services.dataService.saveURL(id: requestId, url: link.absoluteString) - - return .result(dialog: "Link saved to Omnivore") - } catch { - print("error saving URL: ", error) - } - return .result(dialog: "Error saving link") - } - } - -#endif diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionViewModel.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionViewModel.swift index bc99263ed..a2759fea8 100644 --- a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/ShareExtensionViewModel.swift @@ -51,8 +51,8 @@ public class ShareExtensionViewModel: ObservableObject { dataService.archiveLink(objectID: objectID, archived: archived) } - func removeLink(dataService: DataService, objectID: NSManagedObjectID) { - dataService.removeLink(objectID: objectID) + func removeLibraryItem(dataService: DataService, objectID: NSManagedObjectID) { + dataService.removeLibraryItem(objectID: objectID) } func submitTitleEdit(dataService: DataService, itemID: String, title: String, description: String) { diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/ShareExtensionView.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/ShareExtensionView.swift index 557124c08..cc5fcb6a6 100644 --- a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/ShareExtensionView.swift +++ b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/ShareExtensionView.swift @@ -220,7 +220,7 @@ public struct ShareExtensionView: View { Button( action: { if let linkedItem = self.viewModel.linkedItem { - self.viewModel.removeLink(dataService: self.viewModel.services.dataService, objectID: linkedItem.objectID) + self.viewModel.removeLibraryItem(dataService: self.viewModel.services.dataService, objectID: linkedItem.objectID) messageText = "Link Removed" DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) { extensionContext?.completeRequest(returningItems: [], completionHandler: nil) diff --git a/apple/OmnivoreKit/Sources/App/AppIntents.swift b/apple/OmnivoreKit/Sources/App/AppIntents.swift new file mode 100644 index 000000000..4ce8ebb0c --- /dev/null +++ b/apple/OmnivoreKit/Sources/App/AppIntents.swift @@ -0,0 +1,49 @@ +#if os(iOS) + import AppIntents + import Services + import SwiftUI + + @available(iOS 16.0, *) + public struct OmnivoreAppShorcuts: AppShortcutsProvider { + @AppShortcutsBuilder public static var appShortcuts: [AppShortcut] { + AppShortcut(intent: SaveToOmnivoreIntent(), phrases: ["Save URL to \(.applicationName)"]) + } + } + +// +// @available(iOS 16.0, *) +// struct ExportAllTransactionsIntent: AppIntent { +// static var title: LocalizedStringResource = "Export all transactions" +// +// static var description = +// IntentDescription("Exports your transaction history as CSV data.") +// } + + @available(iOS 16.0, *) + struct SaveToOmnivoreIntent: AppIntent { + static var title: LocalizedStringResource = "Save to Omnivore" + static var description: LocalizedStringResource = "Save a URL to your Omnivore library" + + static var parameterSummary: some ParameterSummary { + Summary("Save \(\.$link) to your Omnivore library.") + } + + @Parameter(title: "link") + var link: URL + + @MainActor + func perform() async throws -> some IntentResult & ReturnsValue { + do { + let services = Services() + let requestId = UUID().uuidString.lowercased() + _ = try await services.dataService.saveURL(id: requestId, url: link.absoluteString) + + return .result(dialog: "Link saved to Omnivore") + } catch { + print("error saving URL: ", error) + } + return .result(dialog: "Error saving link") + } + } + +#endif diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/Components/FeedCardNavigationLink.swift b/apple/OmnivoreKit/Sources/App/Views/Home/Components/FeedCardNavigationLink.swift index b454125fc..4154a14cc 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/Components/FeedCardNavigationLink.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/Components/FeedCardNavigationLink.swift @@ -51,11 +51,10 @@ struct FeedCardNavigationLink: View { linkedItemObjectID: item.objectID, isPDF: item.isPDF ) - .background(ThemeManager.currentBgColor) }, label: { EmptyView() } - ).opacity(0) + ) } .onAppear { Task { await viewModel.itemAppeared(item: item, dataService: dataService) } diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index 8ae879074..3027bbcf9 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -188,14 +188,13 @@ struct AnimatingCellHeight: AnimatableModifier { } .frame(maxWidth: .infinity, alignment: .bottomLeading) } + ToolbarItem(placement: .barTrailing) { - Button("", action: {}) - .disabled(true) - .overlay { - if viewModel.isLoading, !prefersListLayout, enableGrid { - ProgressView() - } - } + if UIDevice.isIPad, viewModel.folder == "inbox" { + Button(action: { addLinkPresented = true }, label: { + Label("Add Link", systemImage: "plus") + }) + } } ToolbarItem(placement: UIDevice.isIPhone ? .barLeading : .barTrailing) { if enableGrid { diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index 887b73d0d..a71048cf5 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -79,7 +79,7 @@ import Views } func loadFilters(dataService: DataService) async { - switch filterState.folder { + switch folder { case "following": updateFilters(newFilters: InternalFilter.DefaultFollowingFilters, defaultName: "rss") default: diff --git a/apple/OmnivoreKit/Sources/App/Views/LibrarySidebar.swift b/apple/OmnivoreKit/Sources/App/Views/LibrarySidebar.swift new file mode 100644 index 000000000..09a7b8bfa --- /dev/null +++ b/apple/OmnivoreKit/Sources/App/Views/LibrarySidebar.swift @@ -0,0 +1,205 @@ + +import Foundation +import Services +import SwiftUI + +@MainActor struct LibrarySidebar: View { + @ObservedObject var inboxViewModel: HomeFeedViewModel + @ObservedObject var followingViewModel: HomeFeedViewModel + + @EnvironmentObject var dataService: DataService + + @State private var addLinkPresented = false + @State private var showProfile = false + @State private var selection: String? + + @State private var selectedFilter: InternalFilter? + + @AppStorage("inboxActive") private var inboxActive = true + @AppStorage("followingActive") private var followingActive = false + + @AppStorage("inboxMenuState") var inboxMenuState = "open" + @AppStorage("followingMenuState") var followingMenuState = "open" + + func createInboxViewModel(_ filter: InternalFilter) -> HomeFeedViewModel { + let result = HomeFeedViewModel( + folder: "inbox", + fetcher: LibraryItemFetcher(), + listConfig: LibraryListConfig( + hasFeatureCards: true, + leadingSwipeActions: [.pin], + trailingSwipeActions: [.archive, .delete], + cardStyle: .library + ) + ) + result.appliedFilter = filter + return result + } + + func createFollowingViewModel(_ filter: InternalFilter) -> HomeFeedViewModel { + let result = HomeFeedViewModel( + folder: "following", + fetcher: LibraryItemFetcher(), + listConfig: LibraryListConfig( + hasFeatureCards: false, + leadingSwipeActions: [.moveToInbox], + trailingSwipeActions: [.archive, .delete], + cardStyle: .library + ) + ) + result.appliedFilter = filter + return result + } + + var innerBody: some View { + ZStack { + NavigationLink("", destination: HomeView(viewModel: inboxViewModel), isActive: $inboxActive) + NavigationLink("", destination: HomeView(viewModel: followingViewModel), isActive: $followingActive) + + List { + Section { + Button(action: { inboxMenuState = inboxMenuState == "open" ? "closed" : "open" }, label: { + HStack { + Image.tabLibrary + Text("Library") + Spacer() + + if inboxMenuState == "open" { + Image(systemName: "chevron.down") + } else { + Image(systemName: "chevron.right") + } + } + }) + + if inboxMenuState == "open" { + ForEach(inboxViewModel.filters, id: \.self) { filter in + Button(action: { + inboxViewModel.appliedFilter = filter + selectedFilter = filter + followingActive = false + inboxActive = true + }, label: { + HStack { + Spacer().frame(width: 35) + Text(filter.name) + .lineLimit(1) + } + }) + .listRowBackground( + selectedFilter == filter && inboxActive + ? Color.systemBackground.cornerRadius(8) : Color.clear.cornerRadius(8) + ) + } + } + } + + Section { + Button(action: { followingMenuState = followingMenuState == "open" ? "closed" : "open" }, label: { + HStack { + Image.tabLibrary + Text("Following") + Spacer() + + if followingMenuState == "open" { + Image(systemName: "chevron.down") + } else { + Image(systemName: "chevron.right") + } + } + }) + + if followingMenuState == "open" { + ForEach(followingViewModel.filters, id: \.self) { filter in + Button(action: { + followingViewModel.appliedFilter = filter + selectedFilter = filter + inboxActive = false + followingActive = true + }, label: { + HStack { + Spacer().frame(width: 35) + Text(filter.name) + .lineLimit(1) + } + }) + .listRowBackground( + selectedFilter == filter && followingActive + ? Color.systemBackground.cornerRadius(8) : Color.clear.cornerRadius(8) + ) + } + } + } + } + .listStyle(.sidebar) + .dynamicTypeSize(.small ... .large) + .sheet(isPresented: $addLinkPresented) { + NavigationView { + LibraryAddLinkView() + #if os(iOS) + .navigationBarTitleDisplayMode(.inline) + #endif + } + } + } + .sheet(isPresented: $showProfile) { + NavigationView { + ProfileView() + .toolbar { + ToolbarItem(placement: .barTrailing) { + Button(action: { showProfile = false }, label: { + Text("Close") + .bold() + }) + } + } + } + }.onAppear { + Task { + await inboxViewModel.loadFilters(dataService: dataService) + await followingViewModel.loadFilters(dataService: dataService) + + if inboxActive { + selectedFilter = inboxViewModel.appliedFilter + } else { + selectedFilter = followingViewModel.appliedFilter + } + } + }.onChange(of: inboxViewModel.appliedFilter) { filter in + // When the user uses the dropdown menu to change filter we need to update in the sidebar + if inboxActive, filter != selectedFilter { + selectedFilter = filter + } + }.onChange(of: followingViewModel.appliedFilter) { filter in + if followingActive, filter != selectedFilter { + selectedFilter = filter + } + } + } + + var body: some View { + #if os(iOS) + innerBody + .toolbar { + ToolbarItem(placement: .barTrailing) { + Button(action: { showProfile = true }, label: { Image.tabProfile }) + } + } + #elseif os(macOS) + innerBody + .frame(minWidth: 200) + .toolbar { + ToolbarItem { + Button( + action: { + NSApp.keyWindow?.firstResponder?.tryToPerform( + #selector(NSSplitViewController.toggleSidebar(_:)), with: nil + ) + }, + label: { Label(LocalText.navigationSelectSidebarToggle, systemImage: "sidebar.left") } + ) + } + } + #endif + } +} diff --git a/apple/OmnivoreKit/Sources/App/Views/LibrarySplitView.swift b/apple/OmnivoreKit/Sources/App/Views/LibrarySplitView.swift new file mode 100644 index 000000000..f010f039a --- /dev/null +++ b/apple/OmnivoreKit/Sources/App/Views/LibrarySplitView.swift @@ -0,0 +1,56 @@ +import Foundation +import SwiftUI + +@MainActor +public struct LibrarySplitView: View { + @StateObject private var inboxViewModel = HomeFeedViewModel( + folder: "inbox", + fetcher: LibraryItemFetcher(), + listConfig: LibraryListConfig( + hasFeatureCards: true, + leadingSwipeActions: [.pin], + trailingSwipeActions: [.archive, .delete], + cardStyle: .library + ) + ) + + @StateObject private var followingViewModel = HomeFeedViewModel( + folder: "following", + fetcher: LibraryItemFetcher(), + listConfig: LibraryListConfig( + hasFeatureCards: false, + leadingSwipeActions: [.moveToInbox], + trailingSwipeActions: [.archive, .delete], + cardStyle: .library + ) + ) + + #if os(iOS) + public var body: some View { + NavigationView { + LibrarySidebar(inboxViewModel: inboxViewModel, followingViewModel: followingViewModel) + .navigationBarTitleDisplayMode(.inline) + .tag("inbox") + + HomeFeedContainerView(viewModel: inboxViewModel) + .navigationViewStyle(.stack) + .tag("following") + } + .navigationBarTitleDisplayMode(.inline) + .accentColor(.appGrayTextContrast) + .introspectSplitViewController { + $0.preferredPrimaryColumnWidth = 230 + $0.displayModeButtonVisibility = .always + } + } + #endif + + #if os(macOS) + public var body: some View { + NavigationView { + LibraryListView() + Text(LocalText.navigationSelectLink) + } + } + #endif +} diff --git a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift index 8fa4a26bf..d3b66c479 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift @@ -91,8 +91,10 @@ struct LinkItemDetailView: View { pdfContainerView .navigationBarBackButtonHidden(false) } + .navigationViewStyle(.stack) } else if let item = viewModel.item { WebReaderContainerView(item: item, pop: { dismiss() }) + .background(ThemeManager.currentBgColor) } } .ignoresSafeArea(.all, edges: .bottom) diff --git a/apple/OmnivoreKit/Sources/App/Views/PrimaryContentView.swift b/apple/OmnivoreKit/Sources/App/Views/PrimaryContentView.swift index 6ce2f22d4..95ff33563 100644 --- a/apple/OmnivoreKit/Sources/App/Views/PrimaryContentView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/PrimaryContentView.swift @@ -4,11 +4,6 @@ import SwiftUI import Views @MainActor public struct PrimaryContentView: View { - let categories = [ - PrimaryContentCategory.feed, - PrimaryContentCategory.profile - ] - @State var searchTerm: String = "" public var body: some View { @@ -18,8 +13,9 @@ import Views public var innerBody: some View { #if os(iOS) if UIDevice.isIPad { - return AnyView(LibraryTabView()) - // return AnyView(splitView) + return AnyView( + LibrarySplitView() + ) } else { return AnyView( LibraryTabView() @@ -29,99 +25,4 @@ import Views return AnyView(splitView) #endif } - - #if os(macOS) - private var splitView: some View { - NavigationView { - PrimaryContentCategory.feed.destinationView - Text(LocalText.navigationSelectLink) - } - } - #endif - - #if os(iOS) - private var splitView: some View { - NavigationView { - // The first column is the sidebar. - PrimaryContentSidebar(categories: categories) - .navigationBarTitleDisplayMode(.inline) - - // Second column is the Primary Nav Stack - PrimaryContentCategory.feed.destinationView - .navigationBarTitleDisplayMode(.inline) - } - .navigationBarTitleDisplayMode(.inline) - .accentColor(.appGrayTextContrast) - .introspectSplitViewController { - $0.preferredPrimaryColumnWidth = 160 - $0.displayModeButtonVisibility = .always - } - } - #endif -} - -@MainActor struct PrimaryContentSidebar: View { - @State private var addLinkPresented = false - @State private var showProfile = false - @State private var selectedCategory: PrimaryContentCategory? - let categories: [PrimaryContentCategory] - - var innerBody: some View { - List { - NavigationLink( - destination: PrimaryContentCategory.feed.destinationView, - tag: PrimaryContentCategory.feed, - selection: $selectedCategory, - label: { PrimaryContentCategory.feed.listLabel } - ) - .listRowBackground(Color.systemBackground.cornerRadius(8)) - - Button(action: { showProfile = true }, label: { - PrimaryContentCategory.profile.listLabel - }) - - Button(action: { addLinkPresented = true }, label: { - Label("Add Link", systemImage: "plus.circle") - }) - } - .dynamicTypeSize(.small ... .large) - .listStyle(.sidebar) - .sheet(isPresented: $addLinkPresented) { - NavigationView { - LibraryAddLinkView() - #if os(iOS) - .navigationBarTitleDisplayMode(.inline) - #endif - } - } - .sheet(isPresented: $showProfile) { - NavigationView { - PrimaryContentCategory.profile.destinationView - #if os(iOS) - .navigationBarTitleDisplayMode(.inline) - #endif - } - } - } - - var body: some View { - #if os(iOS) - innerBody - #elseif os(macOS) - innerBody - .frame(minWidth: 200) - .toolbar { - ToolbarItem { - Button( - action: { - NSApp.keyWindow?.firstResponder?.tryToPerform( - #selector(NSSplitViewController.toggleSidebar(_:)), with: nil - ) - }, - label: { Label(LocalText.navigationSelectSidebarToggle, systemImage: "sidebar.left") } - ) - } - } - #endif - } } diff --git a/apple/OmnivoreKit/Sources/App/Views/RemoveLibraryItemAction.swift b/apple/OmnivoreKit/Sources/App/Views/RemoveLibraryItemAction.swift index 763b4b191..96aad9260 100644 --- a/apple/OmnivoreKit/Sources/App/Views/RemoveLibraryItemAction.swift +++ b/apple/OmnivoreKit/Sources/App/Views/RemoveLibraryItemAction.swift @@ -25,7 +25,7 @@ func removeLibraryItemAction(dataService: DataService, objectID: NSManagedObject let canceled = Task.isCancelled if !canceled { print("syncing link deletion") - dataService.removeLink(objectID: objectID, sync: true) + dataService.removeLibraryItem(objectID: objectID, sync: true) } } catch { print("error running task: ", error) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderLoadingContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderLoadingContainer.swift index ae05dd6f0..2775571af 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderLoadingContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderLoadingContainer.swift @@ -48,7 +48,7 @@ public struct WebReaderLoadingContainer: View { .navigationBarHidden(true) .navigationViewStyle(.stack) .accentColor(.appGrayTextContrast) - .task { viewModel.trackReadEvent() } + .onAppear { viewModel.trackReadEvent() } #else if let pdfURL = pdfItem.pdfURL { PDFWrapperView(pdfURL: pdfURL) @@ -60,17 +60,19 @@ public struct WebReaderLoadingContainer: View { .navigationViewStyle(.stack) #endif .accentColor(.appGrayTextContrast) - .task { viewModel.trackReadEvent() } + .onAppear { viewModel.trackReadEvent() } } } else if let errorMessage = viewModel.errorMessage { Text(errorMessage) } else { ProgressView() - .task { - if let username = dataService.currentViewer?.username { - await viewModel.loadItem(dataService: dataService, username: username, requestID: requestID) - } else { - viewModel.errorMessage = "You are not logged in." + .onAppear { + Task { + if let username = dataService.currentViewer?.username { + await viewModel.loadItem(dataService: dataService, username: username, requestID: requestID) + } else { + viewModel.errorMessage = "You are not logged in." + } } } } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RemoveLink.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RemoveLink.swift index eeccc08da..660d08fda 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RemoveLink.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/RemoveLink.swift @@ -4,7 +4,7 @@ import Models import SwiftGraphQL public extension DataService { - func removeLink(objectID: NSManagedObjectID, sync: Bool = true) { + func removeLibraryItem(objectID: NSManagedObjectID, sync: Bool = true) { // First try to get the item synchronously, this is used later to delete files // Then we can async update core data and make the API call to sync the deletion diff --git a/apple/Sources/AppIntents.swift b/apple/Sources/AppIntents.swift new file mode 100644 index 000000000..2152529bb --- /dev/null +++ b/apple/Sources/AppIntents.swift @@ -0,0 +1,74 @@ +#if os(iOS) + import App + import AppIntents + import Firebase + import FirebaseMessaging + import Foundation + import Models + import Services + import UIKit + import Utils + + @available(iOS 16.0, *) + public struct OmnivoreAppShorcuts: AppShortcutsProvider { + @AppShortcutsBuilder public static var appShortcuts: [AppShortcut] { + AppShortcut(intent: SaveToOmnivoreIntent(), phrases: ["Save URL to \(.applicationName)"]) + } + } + +// +// @available(iOS 16.0, *) +// struct ExportAllTransactionsIntent: AppIntent { +// static var title: LocalizedStringResource = "Export all transactions" +// +// static var description = +// IntentDescription("Exports your transaction history as CSV data.") +// } + + @available(iOS 16.0, *) + struct SaveToOmnivoreIntent: AppIntent { + static var title: LocalizedStringResource = "Save to Omnivore" + static var description: LocalizedStringResource = "Save a URL to your Omnivore library" + + static var parameterSummary: some ParameterSummary { + Summary("Save \(\.$link) to your Omnivore library.") + } + + @Parameter(title: "link") + var link: URL + + @MainActor + func perform() async throws -> some IntentResult & ProvidesDialog { + do { + let requestId = UUID().uuidString.lowercased() + _ = try? await Services().dataService.saveURL(id: requestId, url: link.absoluteString) + return .result(dialog: "Link saved to Omnivore") + } catch { + print("error saving URL: ", error) + } + return .result(dialog: "Error saving link") + } + } + + @available(iOS 16.4, *) + struct ReadInOmnivoreIntent: ForegroundContinuableIntent { + static var title: LocalizedStringResource = "Save and read a URL in Omnivore" + static var openAppWhenRun: Bool = false + + @Parameter(title: "link") + var link: URL + + @MainActor + func perform() async throws -> some IntentResult & ProvidesDialog { + let requestId = UUID().uuidString.lowercased() + _ = try? await Services().dataService.saveURL(id: requestId, url: link.absoluteString) + + throw needsToContinueInForegroundError("Please continue to open the app.") { + UIApplication.shared.open(URL(string: "omnivore://read/\(requestId)")!) + } + + return .result(dialog: "I opened the app.") + } + } + +#endif