mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Fixes for the splitview and app intents
This commit is contained in:
parent
d6b30b593d
commit
f48a36de79
16 changed files with 415 additions and 156 deletions
|
|
@ -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 = "<group>"; };
|
||||
42321E842714E6B00056429F /* views */ = {isa = PBXFileReference; lastKnownFileType = folder; path = views; sourceTree = "<group>"; };
|
||||
42704E7228E6BDAF00C8C73E /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotHelper.swift; sourceTree = "<group>"; };
|
||||
42B448392B1DD8AC00CEC5A0 /* AppIntents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppIntents.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
42FF1AEB271154A600B38C38 /* SafariWebExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariWebExtensionHandler.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -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 */,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
49
apple/OmnivoreKit/Sources/App/AppIntents.swift
Normal file
49
apple/OmnivoreKit/Sources/App/AppIntents.swift
Normal file
|
|
@ -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
|
||||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
205
apple/OmnivoreKit/Sources/App/Views/LibrarySidebar.swift
Normal file
205
apple/OmnivoreKit/Sources/App/Views/LibrarySidebar.swift
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
56
apple/OmnivoreKit/Sources/App/Views/LibrarySplitView.swift
Normal file
56
apple/OmnivoreKit/Sources/App/Views/LibrarySplitView.swift
Normal file
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
74
apple/Sources/AppIntents.swift
Normal file
74
apple/Sources/AppIntents.swift
Normal file
|
|
@ -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
|
||||
Loading…
Reference in a new issue