From 31068f90a021f0ac3e6cdb8f2f74b0ce327a1048 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 14 Jul 2020 17:10:53 -0500 Subject: [PATCH] Implement macOS share button --- .../Shared/SceneNavigationView.swift | 16 ++++-- .../Article/SharingServiceDelegate.swift | 32 +++++++++++ .../SharingServicePickerDelegate.swift | 54 ++++++++++++++++++ .../macOS/Article/SharingServiceView.swift | 56 +++++++++++++++++++ NetNewsWire.xcodeproj/project.pbxproj | 12 ++++ 5 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 Multiplatform/macOS/Article/SharingServiceDelegate.swift create mode 100644 Multiplatform/macOS/Article/SharingServicePickerDelegate.swift create mode 100644 Multiplatform/macOS/Article/SharingServiceView.swift diff --git a/Multiplatform/Shared/SceneNavigationView.swift b/Multiplatform/Shared/SceneNavigationView.swift index 708d12e0d..1fffd445e 100644 --- a/Multiplatform/Shared/SceneNavigationView.swift +++ b/Multiplatform/Shared/SceneNavigationView.swift @@ -11,7 +11,8 @@ import SwiftUI struct SceneNavigationView: View { @StateObject private var sceneModel = SceneModel() - @State private var showSheet: Bool = false + @State private var showSheet = false + @State private var showShareSheet = false @State private var sheetToShow: ToolbarSheets = .none #if os(iOS) @@ -148,9 +149,16 @@ struct SceneNavigationView: View { .help("Open in Browser") } ToolbarItem { - Button { - } label: { - AppAssets.shareImage + ZStack { + if showShareSheet { + SharingServiceView(articles: sceneModel.selectedArticles, showing: $showShareSheet) + .frame(width: 20, height: 20) + } + Button { + showShareSheet = true + } label: { + AppAssets.shareImage + } } .disabled(sceneModel.shareButtonState == nil) .help("Share") diff --git a/Multiplatform/macOS/Article/SharingServiceDelegate.swift b/Multiplatform/macOS/Article/SharingServiceDelegate.swift new file mode 100644 index 000000000..01e78fc3f --- /dev/null +++ b/Multiplatform/macOS/Article/SharingServiceDelegate.swift @@ -0,0 +1,32 @@ +// +// SharingServiceDelegate.swift +// NetNewsWire +// +// Created by Maurice Parker on 9/7/18. +// Copyright © 2018 Ranchero Software. All rights reserved. +// + +import AppKit + +@objc final class SharingServiceDelegate: NSObject, NSSharingServiceDelegate { + + weak var window: NSWindow? + + init(_ window: NSWindow?) { + self.window = window + } + + func sharingService(_ sharingService: NSSharingService, willShareItems items: [Any]) { + sharingService.subject = items + .compactMap { item in + let writer = item as? ArticlePasteboardWriter + return writer?.article.title + } + .joined(separator: ", ") + } + + func sharingService(_ sharingService: NSSharingService, sourceWindowForShareItems items: [Any], sharingContentScope: UnsafeMutablePointer) -> NSWindow? { + return window + } + +} diff --git a/Multiplatform/macOS/Article/SharingServicePickerDelegate.swift b/Multiplatform/macOS/Article/SharingServicePickerDelegate.swift new file mode 100644 index 000000000..19ea8782c --- /dev/null +++ b/Multiplatform/macOS/Article/SharingServicePickerDelegate.swift @@ -0,0 +1,54 @@ +// +// SharingServicePickerDelegate.swift +// NetNewsWire +// +// Created by Brent Simmons on 2/17/18. +// Copyright © 2018 Ranchero Software. All rights reserved. +// + +import AppKit +import RSCore + +@objc final class SharingServicePickerDelegate: NSObject, NSSharingServicePickerDelegate { + + private let sharingServiceDelegate: SharingServiceDelegate + private let completion: (() -> Void)? + + init(_ window: NSWindow?, completion: (() -> Void)?) { + self.sharingServiceDelegate = SharingServiceDelegate(window) + self.completion = completion + } + + func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, sharingServicesForItems items: [Any], proposedSharingServices proposedServices: [NSSharingService]) -> [NSSharingService] { + return proposedServices + SharingServicePickerDelegate.customSharingServices(for: items) + } + + func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, delegateFor sharingService: NSSharingService) -> NSSharingServiceDelegate? { + return sharingServiceDelegate + } + + func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, didChoose service: NSSharingService?) { + completion?() + } + + static func customSharingServices(for items: [Any]) -> [NSSharingService] { + let customServices = ExtensionPointManager.shared.activeSendToCommands.compactMap { (sendToCommand) -> NSSharingService? in + + guard let object = items.first else { + return nil + } + + guard sendToCommand.canSendObject(object, selectedText: nil) else { + return nil + } + + let image = sendToCommand.image ?? NSImage() + return NSSharingService(title: sendToCommand.title, image: image, alternateImage: nil) { + sendToCommand.sendObject(object, selectedText: nil) + } + } + return customServices + } +} + + diff --git a/Multiplatform/macOS/Article/SharingServiceView.swift b/Multiplatform/macOS/Article/SharingServiceView.swift new file mode 100644 index 000000000..71eb7e7a0 --- /dev/null +++ b/Multiplatform/macOS/Article/SharingServiceView.swift @@ -0,0 +1,56 @@ +// +// SharingServiceView.swift +// Multiplatform macOS +// +// Created by Maurice Parker on 7/14/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import SwiftUI +import AppKit +import Articles + +class SharingServiceController: NSViewController { + + var sharingServicePickerDelegate: SharingServicePickerDelegate? = nil + var articles = [Article]() + var completion: (() -> Void)? = nil + + override func loadView() { + view = NSView() + } + + override func viewDidAppear() { + guard let anchor = view.superview?.superview else { return } + + sharingServicePickerDelegate = SharingServicePickerDelegate(self.view.window, completion: completion) + + let sortedArticles = articles.sortedByDate(.orderedAscending) + let items = sortedArticles.map { ArticlePasteboardWriter(article: $0) } + + let sharingServicePicker = NSSharingServicePicker(items: items) + sharingServicePicker.delegate = sharingServicePickerDelegate + + sharingServicePicker.show(relativeTo: anchor.bounds, of: anchor, preferredEdge: .minY) + } + +} + +struct SharingServiceView: NSViewControllerRepresentable { + + var articles: [Article] + @Binding var showing: Bool + + func makeNSViewController(context: Context) -> SharingServiceController { + let controller = SharingServiceController() + controller.articles = articles + controller.completion = { + showing = false + } + return controller + } + + func updateNSViewController(_ nsViewController: SharingServiceController, context: Context) { + } + +} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index bd2211b0b..186886ccb 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -354,6 +354,9 @@ 51B80EDD24BD296700C6C32D /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B80EDC24BD296700C6C32D /* ArticleActivityItemSource.swift */; }; 51B80EDF24BD298900C6C32D /* TitleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B80EDE24BD298900C6C32D /* TitleActivityItemSource.swift */; }; 51B80EE124BD3E9600C6C32D /* FindInArticleActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B80EE024BD3E9600C6C32D /* FindInArticleActivity.swift */; }; + 51B80F1F24BE531200C6C32D /* SharingServiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B80F1E24BE531200C6C32D /* SharingServiceView.swift */; }; + 51B80F4224BE588200C6C32D /* SharingServicePickerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B80F4124BE588200C6C32D /* SharingServicePickerDelegate.swift */; }; + 51B80F4424BE58BF00C6C32D /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B80F4324BE58BF00C6C32D /* SharingServiceDelegate.swift */; }; 51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */; }; 51BB7C312335ACDE008E8144 /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = 51BB7C302335ACDE008E8144 /* page.html */; }; 51BC4AFF247277E0000A6ED8 /* URL-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */; }; @@ -2004,6 +2007,9 @@ 51B80EDC24BD296700C6C32D /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = ""; }; 51B80EDE24BD298900C6C32D /* TitleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleActivityItemSource.swift; sourceTree = ""; }; 51B80EE024BD3E9600C6C32D /* FindInArticleActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindInArticleActivity.swift; sourceTree = ""; }; + 51B80F1E24BE531200C6C32D /* SharingServiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceView.swift; sourceTree = ""; }; + 51B80F4124BE588200C6C32D /* SharingServicePickerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServicePickerDelegate.swift; sourceTree = ""; }; + 51B80F4324BE58BF00C6C32D /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = ""; }; 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = ""; }; 51BB7C302335ACDE008E8144 /* page.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = page.html; sourceTree = ""; }; 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL-Extensions.swift"; sourceTree = ""; }; @@ -2763,6 +2769,9 @@ children = ( 51B54ABB24B5BEF20014348B /* ArticleView.swift */, 51B54A6824B54A490014348B /* IconView.swift */, + 51B80F4324BE58BF00C6C32D /* SharingServiceDelegate.swift */, + 51B80F4124BE588200C6C32D /* SharingServicePickerDelegate.swift */, + 51B80F1E24BE531200C6C32D /* SharingServiceView.swift */, 51B54B6624B6A7960014348B /* WebStatusBarView.swift */, 51B54AB524B5B33C0014348B /* WebViewController.swift */, ); @@ -5152,6 +5161,7 @@ 51E498FA24A808BA00B667CB /* SingleFaviconDownloader.swift in Sources */, 51E4993F24A8713B00B667CB /* ArticleStatusSyncTimer.swift in Sources */, 51E4993724A8680E00B667CB /* Reachability.swift in Sources */, + 51B80F4424BE58BF00C6C32D /* SharingServiceDelegate.swift in Sources */, 51B54AB624B5B33C0014348B /* WebViewController.swift in Sources */, 51E4994B24A8734C00B667CB /* SendToMicroBlogCommand.swift in Sources */, 51E4996F24A8764C00B667CB /* ActivityType.swift in Sources */, @@ -5174,8 +5184,10 @@ 51E4992224A8095600B667CB /* URL-Extensions.swift in Sources */, 51E4990424A808C300B667CB /* WebFeedIconDownloader.swift in Sources */, 51E498CB24A8085D00B667CB /* TodayFeedDelegate.swift in Sources */, + 51B80F1F24BE531200C6C32D /* SharingServiceView.swift in Sources */, 17D232A924AFF10A0005F075 /* AddWebFeedModel.swift in Sources */, 51E4993324A867E700B667CB /* AppNotifications.swift in Sources */, + 51B80F4224BE588200C6C32D /* SharingServicePickerDelegate.swift in Sources */, 51E4990624A808C300B667CB /* ImageDownloader.swift in Sources */, 51E4994F24A8734C00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */, 51E498CA24A8085D00B667CB /* SmartFeedDelegate.swift in Sources */,