diff --git a/Multiplatform/Shared/Article/ArticleToolbarModifier.swift b/Multiplatform/Shared/Article/ArticleToolbarModifier.swift index afcff96aa..6a0bfe203 100644 --- a/Multiplatform/Shared/Article/ArticleToolbarModifier.swift +++ b/Multiplatform/Shared/Article/ArticleToolbarModifier.swift @@ -11,6 +11,7 @@ import SwiftUI struct ArticleToolbarModifier: ViewModifier { @EnvironmentObject private var sceneModel: SceneModel + @State private var showActivityView = false func body(content: Content) -> some View { content @@ -98,11 +99,17 @@ struct ArticleToolbarModifier: ViewModifier { ToolbarItem(placement: .bottomBar) { Button { + showActivityView.toggle() } label: { AppAssets.shareImage.font(.title3) } .disabled(sceneModel.shareButtonState == nil) .help("Share") + .sheet(isPresented: $showActivityView) { + if let article = sceneModel.selectedArticles.first, let link = article.preferredLink, let url = URL(string: link) { + ActivityViewController(title: article.title, url: url) + } + } } #endif diff --git a/Multiplatform/Shared/SceneModel.swift b/Multiplatform/Shared/SceneModel.swift index f3cfad46a..8833e2148 100644 --- a/Multiplatform/Shared/SceneModel.swift +++ b/Multiplatform/Shared/SceneModel.swift @@ -24,6 +24,10 @@ final class SceneModel: ObservableObject { @Published var openInBrowserButtonState: Bool? @Published var shareButtonState: Bool? + var selectedArticles: [Article] { + timelineModel.selectedArticles + } + private var refreshProgressModel: RefreshProgressModel? = nil private var articleIconSchemeHandler: ArticleIconSchemeHandler? = nil diff --git a/Multiplatform/iOS/Article/ActivityViewController.swift b/Multiplatform/iOS/Article/ActivityViewController.swift new file mode 100644 index 000000000..9f4497b1f --- /dev/null +++ b/Multiplatform/iOS/Article/ActivityViewController.swift @@ -0,0 +1,33 @@ +// +// ArticleShareView.swift +// Multiplatform iOS +// +// Created by Maurice Parker on 7/13/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit +import SwiftUI +import Articles + +extension UIActivityViewController { + convenience init(url: URL, title: String?, applicationActivities: [UIActivity]?) { + let itemSource = ArticleActivityItemSource(url: url, subject: title) + let titleSource = TitleActivityItemSource(title: title) + self.init(activityItems: [titleSource, itemSource], applicationActivities: applicationActivities) + } +} + +struct ActivityViewController: UIViewControllerRepresentable { + + var title: String? + var url: URL + + func makeUIViewController(context: Context) -> UIActivityViewController { + return UIActivityViewController(url: url, title: title, applicationActivities: [FindInArticleActivity(), OpenInSafariActivity()]) + } + + func updateUIViewController(_ controller: UIActivityViewController, context: Context) { + } + +} diff --git a/Multiplatform/iOS/Article/ArticleActivityItemSource.swift b/Multiplatform/iOS/Article/ArticleActivityItemSource.swift new file mode 100644 index 000000000..f87258a9d --- /dev/null +++ b/Multiplatform/iOS/Article/ArticleActivityItemSource.swift @@ -0,0 +1,33 @@ +// +// ArticleActivityItemSource.swift +// Multiplatform iOS +// +// Created by Maurice Parker on 7/13/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit + +class ArticleActivityItemSource: NSObject, UIActivityItemSource { + + private let url: URL + private let subject: String? + + init(url: URL, subject: String?) { + self.url = url + self.subject = subject + } + + func activityViewControllerPlaceholderItem(_ : UIActivityViewController) -> Any { + return url + } + + func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { + return url + } + + func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String { + return subject ?? "" + } + +} diff --git a/Multiplatform/iOS/Article/ArticleViewController.swift b/Multiplatform/iOS/Article/ArticleViewController.swift index 355d1cba3..51508ed82 100644 --- a/Multiplatform/iOS/Article/ArticleViewController.swift +++ b/Multiplatform/iOS/Article/ArticleViewController.swift @@ -156,3 +156,8 @@ private extension ArticleViewController { } } + +public extension Notification.Name { + static let FindInArticle = Notification.Name("FindInArticle") + static let EndFindInArticle = Notification.Name("EndFindInArticle") +} diff --git a/Multiplatform/iOS/Article/FindInArticleActivity.swift b/Multiplatform/iOS/Article/FindInArticleActivity.swift new file mode 100644 index 000000000..334a142fb --- /dev/null +++ b/Multiplatform/iOS/Article/FindInArticleActivity.swift @@ -0,0 +1,40 @@ +// +// FindInArticleActivity.swift +// NetNewsWire-iOS +// +// Created by Brian Sanders on 5/7/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit + +class FindInArticleActivity: UIActivity { + override var activityTitle: String? { + NSLocalizedString("Find in Article", comment: "Find in Article") + } + + override var activityType: UIActivity.ActivityType? { + UIActivity.ActivityType(rawValue: "com.ranchero.NetNewsWire.find") + } + + override var activityImage: UIImage? { + UIImage(systemName: "magnifyingglass", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular)) + } + + override class var activityCategory: UIActivity.Category { + .action + } + + override func canPerform(withActivityItems activityItems: [Any]) -> Bool { + true + } + + override func prepare(withActivityItems activityItems: [Any]) { + + } + + override func perform() { + NotificationCenter.default.post(Notification(name: .FindInArticle)) + activityDidFinish(true) + } +} diff --git a/Multiplatform/iOS/Article/OpenInSafariActivity.swift b/Multiplatform/iOS/Article/OpenInSafariActivity.swift new file mode 100644 index 000000000..5f76c768d --- /dev/null +++ b/Multiplatform/iOS/Article/OpenInSafariActivity.swift @@ -0,0 +1,49 @@ +// +// OpenInSafariActivity.swift +// Multiplatform iOS +// +// Created by Maurice Parker on 7/13/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit + +class OpenInSafariActivity: UIActivity { + + private var activityItems: [Any]? + + override var activityTitle: String? { + return NSLocalizedString("Open in Safari", comment: "Open in Safari") + } + + override var activityImage: UIImage? { + return UIImage(systemName: "safari", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular)) + } + + override var activityType: UIActivity.ActivityType? { + return UIActivity.ActivityType(rawValue: "com.rancharo.NetNewsWire-Evergreen.safari") + } + + override class var activityCategory: UIActivity.Category { + return .action + } + + override func canPerform(withActivityItems activityItems: [Any]) -> Bool { + return true + } + + override func prepare(withActivityItems activityItems: [Any]) { + self.activityItems = activityItems + } + + override func perform() { + guard let url = activityItems?.first(where: { $0 is URL }) as? URL else { + activityDidFinish(false) + return + } + + UIApplication.shared.open(url, options: [:], completionHandler: nil) + activityDidFinish(true) + } + +} diff --git a/Multiplatform/iOS/Article/TitleActivityItemSource.swift b/Multiplatform/iOS/Article/TitleActivityItemSource.swift new file mode 100644 index 000000000..2c90d2dc4 --- /dev/null +++ b/Multiplatform/iOS/Article/TitleActivityItemSource.swift @@ -0,0 +1,40 @@ +// +// TitleActivityItemSource.swift +// Multiplatform iOS +// +// Created by Maurice Parker on 7/13/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit + +class TitleActivityItemSource: NSObject, UIActivityItemSource { + + private let title: String? + + init(title: String?) { + self.title = title + } + + func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any { + return title as Any + } + + func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { + guard let activityType = activityType, + let title = title else { + return NSNull() + } + + switch activityType.rawValue { + case "com.omnigroup.OmniFocus3.iOS.QuickEntry", + "com.culturedcode.ThingsiPhone.ShareExtension", + "com.tapbots.Tweetbot4.shareextension", + "com.buffer.buffer.Buffer": + return title + default: + return NSNull() + } + } + +} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 5071956cc..bd2211b0b 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -349,6 +349,11 @@ 51B5C8E623F4BBFA00032075 /* ExtensionFeedAddRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B5C87A23F2317700032075 /* ExtensionFeedAddRequest.swift */; }; 51B5C8E723F4BBFA00032075 /* ExtensionFeedAddRequestFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B5C8BF23F3866C00032075 /* ExtensionFeedAddRequestFile.swift */; }; 51B62E68233186730085F949 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B62E67233186730085F949 /* IconView.swift */; }; + 51B80EB824BD1F8B00C6C32D /* ActivityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B80EB724BD1F8B00C6C32D /* ActivityViewController.swift */; }; + 51B80EDB24BD225200C6C32D /* OpenInSafariActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B80EDA24BD225200C6C32D /* OpenInSafariActivity.swift */; }; + 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 */; }; 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 */; }; @@ -1994,6 +1999,11 @@ 51B5C8BC23F3780900032075 /* ShareDefaultContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareDefaultContainer.swift; sourceTree = ""; }; 51B5C8BF23F3866C00032075 /* ExtensionFeedAddRequestFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionFeedAddRequestFile.swift; sourceTree = ""; }; 51B62E67233186730085F949 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; + 51B80EB724BD1F8B00C6C32D /* ActivityViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityViewController.swift; sourceTree = ""; }; + 51B80EDA24BD225200C6C32D /* OpenInSafariActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInSafariActivity.swift; sourceTree = ""; }; + 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 = ""; }; 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 = ""; }; @@ -2732,12 +2742,17 @@ 5177470B24B2FF2C00EB0F74 /* Article */ = { isa = PBXGroup; children = ( + 51B80EB724BD1F8B00C6C32D /* ActivityViewController.swift */, + 51B80EDC24BD296700C6C32D /* ArticleActivityItemSource.swift */, 5177470D24B2FF6F00EB0F74 /* ArticleView.swift */, 5177470F24B3029400EB0F74 /* ArticleViewController.swift */, + 51B80EE024BD3E9600C6C32D /* FindInArticleActivity.swift */, 5177471724B3812200EB0F74 /* IconView.swift */, 5177471B24B387AC00EB0F74 /* ImageScrollView.swift */, 5177471D24B387E100EB0F74 /* ImageTransition.swift */, 5177471F24B3882600EB0F74 /* ImageViewController.swift */, + 51B80EDA24BD225200C6C32D /* OpenInSafariActivity.swift */, + 51B80EDE24BD298900C6C32D /* TitleActivityItemSource.swift */, 5177471124B37C5400EB0F74 /* WebViewController.swift */, ); path = Article; @@ -5021,6 +5036,7 @@ 51E4993624A867E800B667CB /* UserInfoKey.swift in Sources */, 51E4990924A808C500B667CB /* WebFeedIconDownloader.swift in Sources */, 51E498F524A8085D00B667CB /* TodayFeedDelegate.swift in Sources */, + 51B80EDB24BD225200C6C32D /* OpenInSafariActivity.swift in Sources */, 172199F124AB716900A31D04 /* SidebarToolbarModifier.swift in Sources */, 65CBAD5A24AE03C20006DD91 /* ColorPaletteContainerView.swift in Sources */, 5177470924B2F87600EB0F74 /* SidebarListStyleModifier.swift in Sources */, @@ -5055,8 +5071,10 @@ 5177471024B3029400EB0F74 /* ArticleViewController.swift in Sources */, 172199C924AB228900A31D04 /* SettingsView.swift in Sources */, 17D232A824AFF10A0005F075 /* AddWebFeedModel.swift in Sources */, + 51B80EB824BD1F8B00C6C32D /* ActivityViewController.swift in Sources */, 51E4994224A8713C00B667CB /* ArticleStatusSyncTimer.swift in Sources */, 51E498F624A8085D00B667CB /* SearchFeedDelegate.swift in Sources */, + 51B80EE124BD3E9600C6C32D /* FindInArticleActivity.swift in Sources */, 6586A5F724B632F8002BCF4F /* SettingsDetailAccountModel.swift in Sources */, 51E498F224A8085D00B667CB /* SmartFeedsController.swift in Sources */, 51919FB624AABCA100541E64 /* IconImageView.swift in Sources */, @@ -5079,6 +5097,7 @@ 5171B4F624B7BABA00FB8D3B /* MarkStatusCommand.swift in Sources */, 65422D1724B75CD1008A2FA2 /* SettingsAddAccountModel.swift in Sources */, 5177471424B37D4000EB0F74 /* PreloadedWebView.swift in Sources */, + 51B80EDD24BD296700C6C32D /* ArticleActivityItemSource.swift in Sources */, 517B2EBC24B3E62A001AC46C /* WrapperScriptMessageHandler.swift in Sources */, 51919FB324AAB97900541E64 /* FeedIconImageLoader.swift in Sources */, 5177472024B3882600EB0F74 /* ImageViewController.swift in Sources */, @@ -5108,6 +5127,7 @@ 51919FF724AB8B7700541E64 /* TimelineView.swift in Sources */, 51E4993D24A870F800B667CB /* UserNotificationManager.swift in Sources */, 5177470324B2657F00EB0F74 /* TimelineToolbarModifier.swift in Sources */, + 51B80EDF24BD298900C6C32D /* TitleActivityItemSource.swift in Sources */, 51E4991524A808FF00B667CB /* ArticleStringFormatter.swift in Sources */, 51919FEE24AB85E400541E64 /* TimelineContainerView.swift in Sources */, FFA2BBD624AF751100B3149D /* PreviewProvider+RefreshProgressModel.swift in Sources */,