diff --git a/Reader/SceneDelegate.swift b/Reader/SceneDelegate.swift index 828b17b..5b8c2a5 100644 --- a/Reader/SceneDelegate.swift +++ b/Reader/SceneDelegate.swift @@ -13,6 +13,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? private(set) var fervorController: FervorController! + private(set) var toggleReadBarButtonItem: UIBarButtonItem? private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "SceneDelegate") @@ -144,23 +145,31 @@ extension SceneDelegate: HomeViewControllerDelegate { #if targetEnvironment(macCatalyst) extension NSToolbarItem.Identifier { static let toggleItemRead = NSToolbarItem.Identifier("ToggleItemRead") + static let shareItem = NSToolbarItem.Identifier("ShareItem") } extension SceneDelegate: NSToolbarDelegate { func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? { - let item = NSToolbarItem(itemIdentifier: .toggleItemRead) - item.target = nil - item.action = #selector(AppSplitViewController.toggleItemRead) - item.image = UIImage(systemName: "checkmark.circle") - return item + if itemIdentifier == .toggleItemRead { + // need an item bar button item to make the size of the image match the share button + let item = NSToolbarItem(itemIdentifier: .toggleItemRead, barButtonItem: UIBarButtonItem(image: nil, style: .plain, target: nil, action: nil)) + item.image = UIImage(systemName: "checkmark.circle") + item.target = nil + item.action = #selector(ReadViewController.toggleItemRead(_:)) + return item + } else if itemIdentifier == .shareItem { + return NSSharingServicePickerToolbarItem(itemIdentifier: .shareItem) + } else { + return nil + } } func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { - return [.toggleItemRead] + return [.shareItem, .toggleItemRead] } func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { - return [.toggleItemRead] + return [.shareItem, .toggleItemRead] } } #endif diff --git a/Reader/Screens/AppSplitViewController.swift b/Reader/Screens/AppSplitViewController.swift index 8472de4..1b1542d 100644 --- a/Reader/Screens/AppSplitViewController.swift +++ b/Reader/Screens/AppSplitViewController.swift @@ -51,55 +51,12 @@ class AppSplitViewController: UISplitViewController { let nav = AppNavigationController(rootViewController: home) setViewController(nav, for: .compact) } - - #if targetEnvironment(macCatalyst) - @objc func toggleItemRead(_ item: NSToolbarItem) { - guard let nav = viewController(for: .secondary) as? UINavigationController, - let read = nav.topViewController as? ReadViewController else { - return - } - Task { - await fervorController.markItem(read.item, read: !read.item.read) - updateImage(toolbarItem: item) - } - } - - private func updateImage(toolbarItem: NSToolbarItem) { - if let nav = viewController(for: .secondary) as? UINavigationController, - let read = nav.topViewController as? ReadViewController { - toolbarItem.image = UIImage(systemName: read.item.read ? "checkmark.circle.fill" : "checkmark.circle") - } else { - toolbarItem.image = UIImage(systemName: "checkmark.circle") - } - } - #endif } extension AppSplitViewController: ItemsViewControllerDelegate { func showReadItem(_ item: Item) { secondaryNav.setViewControllers([ReadViewController(item: item, fervorController: fervorController)], animated: false) - - #if targetEnvironment(macCatalyst) - if let titlebar = view.window?.windowScene?.titlebar, - let toggleRead = titlebar.toolbar?.items.first(where: { $0.itemIdentifier == .toggleItemRead }) { - updateImage(toolbarItem: toggleRead) - } - #endif } } -#if targetEnvironment(macCatalyst) -extension AppSplitViewController { - override func responds(to aSelector: Selector!) -> Bool { - if aSelector == #selector(toggleItemRead) { - guard let nav = viewController(for: .secondary) as? UINavigationController else { - return false - } - return nav.topViewController is ReadViewController - } else { - return super.responds(to: aSelector) - } - } -} -#endif diff --git a/Reader/Screens/Items/ItemCollectionViewCell.swift b/Reader/Screens/Items/ItemCollectionViewCell.swift index 2e6fc6d..ac7bcde 100644 --- a/Reader/Screens/Items/ItemCollectionViewCell.swift +++ b/Reader/Screens/Items/ItemCollectionViewCell.swift @@ -87,18 +87,21 @@ class ItemCollectionViewCell: UICollectionViewListCell { } func setRead(_ read: Bool, animated: Bool) { - guard self.item.read != read else { return } - Task { - await self.delegate?.fervorController.markItem(self.item, read: read) - - if animated { - // i don't know why .transition works but .animate doesn't - UIView.transition(with: self, duration: 0.2, options: .transitionCrossDissolve) { - self.updateColors() - } - } else { - updateColors() + guard item.read != read else { return } + + item.read = read + + if animated { + // i don't know why .transition works but .animate doesn't + UIView.transition(with: self, duration: 0.2, options: .transitionCrossDissolve) { + self.updateColors() } + } else { + updateColors() + } + + Task { + await delegate?.fervorController.markItem(item, read: read) } } diff --git a/Reader/Screens/Items/ItemsViewController.swift b/Reader/Screens/Items/ItemsViewController.swift index 0906bbd..a384fc2 100644 --- a/Reader/Screens/Items/ItemsViewController.swift +++ b/Reader/Screens/Items/ItemsViewController.swift @@ -127,16 +127,21 @@ extension ItemsViewController: UICollectionViewDelegate { return UIContextMenuConfiguration(identifier: nil, previewProvider: { ReadViewController(item: item, fervorController: self.fervorController) }, actionProvider: { _ in - var children: [UIAction] = [] + var children: [UIMenuElement] = [] if let url = item.url { children.append(UIAction(title: "Open in Safari", image: UIImage(systemName: "safari"), handler: { [weak self] _ in let vc = SFSafariViewController(url: url) vc.preferredControlTintColor = .appTintColor self?.present(vc, animated: true) })) + #if targetEnvironment(macCatalyst) + self.activityItemsConfiguration = UIActivityItemsConfiguration(objects: [url as NSURL]) + children.append(UICommand(title: "Share…", action: Selector(("unused")), propertyList: UICommandTagShare)) + #else children.append(UIAction(title: "Share", image: UIImage(systemName: "square.and.arrow.up"), handler: { [weak self] _ in self?.present(UIActivityViewController(activityItems: [url], applicationActivities: nil), animated: true) })) + #endif } if item.read { children.append(UIAction(title: "Mark as Unread", image: UIImage(systemName: "checkmark.circle"), handler: { [unowned self] _ in diff --git a/Reader/Screens/Read/ReadViewController.swift b/Reader/Screens/Read/ReadViewController.swift index fe5b8a6..df5517a 100644 --- a/Reader/Screens/Read/ReadViewController.swift +++ b/Reader/Screens/Read/ReadViewController.swift @@ -22,6 +22,10 @@ class ReadViewController: UIViewController { let fervorController: FervorController let item: Item + #if targetEnvironment(macCatalyst) + private var itemReadObservation: NSKeyValueObservation? + #endif + override var prefersStatusBarHidden: Bool { navigationController?.isNavigationBarHidden ?? false } @@ -68,6 +72,15 @@ class ReadViewController: UIViewController { webView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) + if let url = item.url { + activityItemsConfiguration = UIActivityItemsConfiguration(objects: [url as NSURL]) + } + + #if targetEnvironment(macCatalyst) + itemReadObservation = item.observe(\.read) { [unowned self] _, _ in + self.updateToggleReadToolbarImage() + } + #endif } private static let css = try! String(contentsOf: Bundle.main.url(forResource: "read", withExtension: "css")!) @@ -125,6 +138,24 @@ class ReadViewController: UIViewController { vc.preferredControlTintColor = .appTintColor return vc } + + #if targetEnvironment(macCatalyst) + @objc func toggleItemRead(_ item: NSToolbarItem) { + Task { + await fervorController.markItem(self.item, read: !self.item.read) + updateToggleReadToolbarImage() + } + } + + private func updateToggleReadToolbarImage() { + guard let titlebar = view.window?.windowScene?.titlebar, + let item = titlebar.toolbar?.items.first(where: { $0.itemIdentifier == .toggleItemRead }) else { + return + } + + item.image = UIImage(systemName: self.item.read ? "checkmark.circle.fill" : "checkmark.circle") + } + #endif } @@ -181,18 +212,21 @@ extension ReadViewController: StretchyMenuInteractionDelegate { guard let url = item.url else { return [] } - return [ + var items = [ StretchyMenuItem(title: "Open in Safari", subtitle: nil, action: { [unowned self] in self.present(createSafariVC(url: url), animated: true) }), - StretchyMenuItem(title: "Share", subtitle: nil, action: { [unowned self] in - self.present(UIActivityViewController(activityItems: [url], applicationActivities: nil), animated: true) - }), StretchyMenuItem(title: item.read ? "Mark as Unread" : "Mark as Read", subtitle: nil, action: { [unowned self] in Task { await self.fervorController.markItem(item, read: !item.read) } }), ] + #if !targetEnvironment(macCatalyst) + items.insert(StretchyMenuItem(title: "Share", subtitle: nil, action: { [unowned self] in + self.present(UIActivityViewController(activityItems: [url], applicationActivities: nil), animated: true) + }), at: 1) + #endif + return items } }