diff --git a/Localization/app.json b/Localization/app.json index 1e0f6eaa5..a43e56c10 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -44,6 +44,8 @@ "sign_up": "Sign Up", "see_more": "See More", "preview": "Preview", + "share": "Share", + "share_user": "Share %s", "open_in_safari": "Open in Safari" }, "status": { @@ -73,6 +75,7 @@ "pending": "Pending", "block": "Block", "block_user": "Block %s", + "block_domain": "Block %s", "unblock": "Unblock", "unblock_user": "Unblock %s", "blocked": "Blocked", diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 353578ba3..36d2e0e77 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -219,6 +219,7 @@ DB71FD5225F8CCAA00512AE1 /* APIService+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71FD5125F8CCAA00512AE1 /* APIService+Status.swift */; }; DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */; }; DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */; }; + DB73B490261F030A002E9E9F /* SafariActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB73B48F261F030A002E9E9F /* SafariActivity.swift */; }; DB789A0B25F9F2950071ACA0 /* ComposeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */; }; DB789A1225F9F2CC0071ACA0 /* ComposeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */; }; DB789A1C25F9F76A0071ACA0 /* ComposeStatusContentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A1B25F9F76A0071ACA0 /* ComposeStatusContentCollectionViewCell.swift */; }; @@ -595,6 +596,7 @@ DB71FD5125F8CCAA00512AE1 /* APIService+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Status.swift"; sourceTree = ""; }; DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewController.swift; sourceTree = ""; }; DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewModel.swift; sourceTree = ""; }; + DB73B48F261F030A002E9E9F /* SafariActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariActivity.swift; sourceTree = ""; }; DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewController.swift; sourceTree = ""; }; DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewModel.swift; sourceTree = ""; }; DB789A1B25F9F76A0071ACA0 /* ComposeStatusContentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusContentCollectionViewCell.swift; sourceTree = ""; }; @@ -1142,8 +1144,8 @@ DB084B5125CBC56300F898ED /* CoreDataStack */ = { isa = PBXGroup; children = ( - DB45FAE225CA7181005A8AC7 /* MastodonUser.swift */, DB084B5625CBC56C00F898ED /* Status.swift */, + DB45FAE225CA7181005A8AC7 /* MastodonUser.swift */, DB9D6C3725E508BE0051B173 /* Attachment.swift */, ); path = CoreDataStack; @@ -1233,6 +1235,7 @@ DB9E0D6925EDFFE500CFDD76 /* Helper */, DB8AF56225C138BC002E6C99 /* Extension */, 2D5A3D0125CF8640002347D6 /* Vender */, + DB73B495261F030D002E9E9F /* Activity */, DB5086CB25CC0DB400C2C187 /* Preference */, 2D69CFF225CA9E2200C3A1B2 /* Protocol */, DB98338425C945ED00AD9700 /* Generated */, @@ -1372,6 +1375,14 @@ path = ServerRules; sourceTree = ""; }; + DB73B495261F030D002E9E9F /* Activity */ = { + isa = PBXGroup; + children = ( + DB73B48F261F030A002E9E9F /* SafariActivity.swift */, + ); + path = Activity; + sourceTree = ""; + }; DB789A1025F9F29B0071ACA0 /* Compose */ = { isa = PBXGroup; children = ( @@ -2219,6 +2230,7 @@ 5DDDF1A92617489F00311060 /* Mastodon+Entity+History.swift in Sources */, DB59F11825EFA35B001F1DAB /* StripProgressView.swift in Sources */, DB59F10425EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift in Sources */, + DB73B490261F030A002E9E9F /* SafariActivity.swift in Sources */, DB9A48962603685D008B817C /* MastodonAttachmentService+UploadState.swift in Sources */, DB66728C25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift in Sources */, DBE0822425CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift in Sources */, diff --git a/Mastodon/Activity/SafariActivity.swift b/Mastodon/Activity/SafariActivity.swift new file mode 100644 index 000000000..e10a0b082 --- /dev/null +++ b/Mastodon/Activity/SafariActivity.swift @@ -0,0 +1,62 @@ +// +// SafariActivity.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-4-8. +// + +import UIKit +import SafariServices + +final class SafariActivity: UIActivity { + + weak var sceneCoordinator: SceneCoordinator? + var url: NSURL? + + init(sceneCoordinator: SceneCoordinator) { + self.sceneCoordinator = sceneCoordinator + } + + override var activityType: UIActivity.ActivityType? { + return UIActivity.ActivityType("org.joinmastodon.Mastodon.safari-activity") + } + + override var activityTitle: String? { + return L10n.Common.Controls.Actions.openInSafari + } + + override var activityImage: UIImage? { + return UIImage(systemName: "safari") + } + + override func canPerform(withActivityItems activityItems: [Any]) -> Bool { + for item in activityItems { + guard let _ = item as? NSURL, sceneCoordinator != nil else { continue } + return true + } + + return false + } + + override func prepare(withActivityItems activityItems: [Any]) { + for item in activityItems { + guard let url = item as? NSURL else { continue } + self.url = url + } + } + + override var activityViewController: UIViewController? { + return nil + } + + override func perform() { + guard let url = url else { + activityDidFinish(false) + return + } + + sceneCoordinator?.present(scene: .safari(url: url as URL), from: nil, transition: .safariPresent(animated: true, completion: nil)) + activityDidFinish(true) + } + +} diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 6ad34ec30..36d42745e 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -33,8 +33,8 @@ extension SceneCoordinator { case custom(transitioningDelegate: UIViewControllerTransitioningDelegate) case customPush case safariPresent(animated: Bool, completion: (() -> Void)? = nil) - case activityViewControllerPresent(animated: Bool, completion: (() -> Void)? = nil) case alertController(animated: Bool, completion: (() -> Void)? = nil) + case activityViewControllerPresent(animated: Bool, completion: (() -> Void)? = nil) } enum Scene { @@ -59,8 +59,9 @@ extension SceneCoordinator { case favorite(viewModel: FavoriteViewModel) // misc - case alertController(alertController: UIAlertController) case safari(url: URL) + case alertController(alertController: UIAlertController) + case activityViewController(activityViewController: UIActivityViewController, sourceView: UIView?, barButtonItem: UIBarButtonItem?) #if DEBUG case publicTimeline @@ -170,11 +171,11 @@ extension SceneCoordinator { viewController.modalPresentationCapturesStatusBarAppearance = true presentingViewController.present(viewController, animated: animated, completion: completion) - case .activityViewControllerPresent(let animated, let completion): + case .alertController(let animated, let completion): viewController.modalPresentationCapturesStatusBarAppearance = true presentingViewController.present(viewController, animated: animated, completion: completion) - case .alertController(let animated, let completion): + case .activityViewControllerPresent(let animated, let completion): viewController.modalPresentationCapturesStatusBarAppearance = true presentingViewController.present(viewController, animated: animated, completion: completion) } @@ -237,6 +238,12 @@ private extension SceneCoordinator { let _viewController = FavoriteViewController() _viewController.viewModel = viewModel viewController = _viewController + case .safari(let url): + guard let scheme = url.scheme?.lowercased(), + scheme == "http" || scheme == "https" else { + return nil + } + viewController = SFSafariViewController(url: url) case .alertController(let alertController): if let popoverPresentationController = alertController.popoverPresentationController { assert( @@ -246,12 +253,10 @@ private extension SceneCoordinator { ) } viewController = alertController - case .safari(let url): - guard let scheme = url.scheme?.lowercased(), - scheme == "http" || scheme == "https" else { - return nil - } - viewController = SFSafariViewController(url: url) + case .activityViewController(let activityViewController, let sourceView, let barButtonItem): + activityViewController.popoverPresentationController?.sourceView = sourceView + activityViewController.popoverPresentationController?.barButtonItem = barButtonItem + viewController = activityViewController #if DEBUG case .publicTimeline: let _viewController = PublicTimelineViewController() diff --git a/Mastodon/Extension/CoreDataStack/MastodonUser.swift b/Mastodon/Extension/CoreDataStack/MastodonUser.swift index 4e2138306..bb99b15d4 100644 --- a/Mastodon/Extension/CoreDataStack/MastodonUser.swift +++ b/Mastodon/Extension/CoreDataStack/MastodonUser.swift @@ -63,3 +63,21 @@ extension MastodonUser { } } + +extension MastodonUser { + + var profileURL: URL { + if let urlString = self.url, + let url = URL(string: urlString) { + return url + } else { + return URL(string: "https://\(self.domain)/@\(username)")! + } + } + + var activityItems: [Any] { + var items: [Any] = [] + items.append(profileURL) + return items + } +} diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index d20ef1b25..7e50253b4 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -78,6 +78,12 @@ internal enum L10n { internal static let savePhoto = L10n.tr("Localizable", "Common.Controls.Actions.SavePhoto") /// See More internal static let seeMore = L10n.tr("Localizable", "Common.Controls.Actions.SeeMore") + /// Share + internal static let share = L10n.tr("Localizable", "Common.Controls.Actions.Share") + /// Share %@ + internal static func shareUser(_ p1: Any) -> String { + return L10n.tr("Localizable", "Common.Controls.Actions.ShareUser", String(describing: p1)) + } /// Sign In internal static let signIn = L10n.tr("Localizable", "Common.Controls.Actions.SignIn") /// Sign Up @@ -90,6 +96,10 @@ internal enum L10n { internal enum Firendship { /// Block internal static let block = L10n.tr("Localizable", "Common.Controls.Firendship.Block") + /// Block %@ + internal static func blockDomain(_ p1: Any) -> String { + return L10n.tr("Localizable", "Common.Controls.Firendship.BlockDomain", String(describing: p1)) + } /// Blocked internal static let blocked = L10n.tr("Localizable", "Common.Controls.Firendship.Blocked") /// Block %@ diff --git a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift index 04297772b..b5f4dd32f 100644 --- a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift +++ b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift @@ -139,7 +139,10 @@ extension UserProviderFacade { for mastodonUser: MastodonUser, isMuting: Bool, isBlocking: Bool, - provider: UserProvider + needsShareAction: Bool, + provider: UserProvider, + sourceView: UIView?, + barButtonItem: UIBarButtonItem? ) -> UIMenu { var children: [UIMenuElement] = [] let name = mastodonUser.displayNameWithFallback @@ -198,7 +201,32 @@ extension UserProviderFacade { children.append(blockMenu) } + if needsShareAction { + let shareAction = UIAction(title: L10n.Common.Controls.Actions.shareUser(name), image: UIImage(systemName: "square.and.arrow.up"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in + guard let provider = provider else { return } + let activityViewController = createActivityViewControllerForMastodonUser(mastodonUser: mastodonUser, dependency: provider) + provider.coordinator.present( + scene: .activityViewController( + activityViewController: activityViewController, + sourceView: sourceView, + barButtonItem: barButtonItem + ), + from: provider, + transition: .activityViewControllerPresent(animated: true, completion: nil) + ) + } + children.append(shareAction) + } + return UIMenu(title: "", options: [], children: children) } + static func createActivityViewControllerForMastodonUser(mastodonUser: MastodonUser, dependency: NeedsDependency) -> UIActivityViewController { + let activityViewController = UIActivityViewController( + activityItems: mastodonUser.activityItems, + applicationActivities: [SafariActivity(sceneCoordinator: dependency.coordinator)] + ) + return activityViewController + } + } diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index 853c75778..64b62233f 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -24,11 +24,14 @@ Please check your internet connection."; "Common.Controls.Actions.Save" = "Save"; "Common.Controls.Actions.SavePhoto" = "Save photo"; "Common.Controls.Actions.SeeMore" = "See More"; +"Common.Controls.Actions.Share" = "Share"; +"Common.Controls.Actions.ShareUser" = "Share %@"; "Common.Controls.Actions.SignIn" = "Sign In"; "Common.Controls.Actions.SignUp" = "Sign Up"; "Common.Controls.Actions.TakePhoto" = "Take photo"; "Common.Controls.Actions.TryAgain" = "Try Again"; "Common.Controls.Firendship.Block" = "Block"; +"Common.Controls.Firendship.BlockDomain" = "Block %@"; "Common.Controls.Firendship.BlockUser" = "Block %@"; "Common.Controls.Firendship.Blocked" = "Blocked"; "Common.Controls.Firendship.EditInfo" = "Edit info"; diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 0eba0085e..f070f72c6 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -325,7 +325,8 @@ extension ProfileViewController { } let isMuting = relationshipActionOptionSet.contains(.muting) let isBlocking = relationshipActionOptionSet.contains(.blocking) - self.moreMenuBarButtonItem.menu = UserProviderFacade.createProfileActionMenu(for: mastodonUser, isMuting: isMuting, isBlocking: isBlocking, provider: self) + let needsShareAction = self.viewModel.isMeBarButtonItemsHidden.value + self.moreMenuBarButtonItem.menu = UserProviderFacade.createProfileActionMenu(for: mastodonUser, isMuting: isMuting, isBlocking: isBlocking, needsShareAction: needsShareAction, provider: self, sourceView: nil, barButtonItem: self.moreMenuBarButtonItem) } .store(in: &disposeBag) viewModel.isRelationshipActionButtonHidden @@ -446,6 +447,17 @@ extension ProfileViewController { @objc private func shareBarButtonItemPressed(_ sender: UIBarButtonItem) { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + guard let mastodonUser = viewModel.mastodonUser.value else { return } + let activityViewController = UserProviderFacade.createActivityViewControllerForMastodonUser(mastodonUser: mastodonUser, dependency: self) + coordinator.present( + scene: .activityViewController( + activityViewController: activityViewController, + sourceView: nil, + barButtonItem: sender + ), + from: self, + transition: .activityViewControllerPresent(animated: true, completion: nil) + ) } @objc private func favoriteBarButtonItemPressed(_ sender: UIBarButtonItem) {